Skip to content

unknwon/git-lfs-server

Repository files navigation

Git LFS Server

Bring Your Own Forge (BYOF) Git LFS server with affordable storage.

Price comparison

Assuming 1 TiB is uploaded and stored for a full month, and 3 TiB is downloaded in the same month:

Provider Storage Egress Yearly total Savings vs. GitLab LFS
GitLab LFS 109 x 10 GB-month x $5 x 12 = $6,540.00 Included $6,540.00 Baseline
GitHub LFS 1,014 GiB x $0.07 x 12 = $851.76 36,744 GiB x $0.0875 = $3,215.10 $4,066.86 $2,473.14 (37.81%)
DigitalOcean Spaces ($5 + 774 GiB x $0.02) x 12 = $245.76 24,576 GiB x $0.01 = $245.76 $491.52 $6,048.48 (92.48%)
Cloudflare R2 Standard 1,090 GB-month x $0.015 x 12 = $196.20 Included $196.20 $6,343.80 (97.00%)
Backblaze B2 1 TB x $6.95 x 12 = $83.40 Free up to 3x storage $83.40 $6,456.60 (98.72%)

Example setup

Server

The server loads its built-in defaults from internal/embed/config.ini and then layers a custom file on top. The custom file path is read from LFSD_CONFIG_PATH and defaults to config.ini in the working directory:

LFSD_CONFIG_PATH=/etc/lfsd/config.ini lfsd

Values of the form ${NAME} in the config are expanded from the process environment at load time. A minimal override that serves GitHub.com repositories from Cloudflare R2 at https://lfs.example.com looks like:

[server]
EXTERNAL_URL = https://lfs.example.com

[forge "github.com"]
TYPE = github
STORAGE = r2

[storage "r2"]
TYPE = s3-presign
SCHEME = r2://
BUCKET = lfs-objects
ACCESS_KEY_ID = ${LFSD_R2_ACCESS_KEY_ID}
SECRET_ACCESS_KEY = ${LFSD_R2_SECRET_ACCESS_KEY}
ENDPOINT = https://${LFSD_R2_ACCOUNT_ID}.r2.cloudflarestorage.com

EXTERNAL_URL is the public origin clients reach the server at. It is used as the base for the object download, upload, and verify URLs the server returns to the client, so it must match what the client sees (typically the public HTTPS URL terminated by your reverse proxy).

At least one [forge "{host}"] section must be configured. Each forge references a [storage "{name}"] section by name. Multiple forges may share a single storage backend. Supported storage types are filesystem and s3-presign (compatible with S3, R2, DigitalOcean Spaces, etc.).

Client

Git LFS resolves the LFS endpoint per repository. To point a clone at lfsd, set lfs.url in the repository's .lfsconfig (committed so collaborators inherit it) to EXTERNAL_URL joined with the forge host, the repository path, and the /info/lfs suffix:

git config -f .lfsconfig lfs.url https://x-access-token:$GH_TOKEN@lfs.example.com/github.com/myorg/myrepo/info/lfs

The client sends the same forge token it would send to GitHub directly (configured via your usual Git credential helper) as the HTTP Basic Auth password.

Authentication

The server does not maintain its own user database. Clients authenticate to lfsd over HTTP Basic Auth using the same forge token they would use against the upstream forge as the password, and lfsd defers the access check to the forge.

A token that the forge grants push (or equivalent) on the target repository gives write access (uploads and downloads). A token with read-only access gives read access (downloads only). Anything else is rejected. Successful authorizations are cached in memory. Tokens with a known expiry are cached up to that expiry, others fall back to a short default.

Supported forges

Authentication and authorization are delegated to the upstream forge per request. The following forges are supported:

  • GitHub.com
  • GitHub Enterprise Server
  • GitLab.com
  • GitLab Self-Managed

For any other forge, set SKIP_AUTH = true on its [forge "{host}"] section and gate authentication in front of lfsd with a reverse proxy.

Repo allowlist

A forge section may restrict which repositories the server will serve via REPO_ALLOWLIST. The value is a comma-separated list matched case-insensitively against the path portion of the URL after the host. An empty or unset list accepts every repository the forge authorizes.

Each entry is one of:

  • A literal repo path, e.g., myorg/my-repo, which matches that path exactly.
  • A <prefix>/** pattern, e.g., myorg/**, which matches any non-empty suffix under the prefix.

The glob ** is only allowed as the final segment. Bare ** is rejected. To allow all repos, leave the key empty. Example:

[forge "github.com"]
TYPE = github
STORAGE = r2
REPO_ALLOWLIST = myorg/**, otherorg/specific-repo

The allowlist is enforced before the forge token check, so a request for a disallowed repo is rejected without a round-trip to the forge.

Local development

This project uses moon as its task runner. Install it with Homebrew:

brew install moon

Common tasks:

moon run :install   # Tidy Go modules.
moon run :lint      # Run golangci-lint.
moon run :test      # Run the test suite.
moon run :build     # Compile all packages.
moon run :dev       # Run lfsd locally against config.ini.

moon run :dev expects a config.ini in the working directory. See Example setup for a minimum example.

License

This project is under the MIT License. See the LICENSE file for the full license text.

About

Bring Your Own Forge (BYOF) Git LFS server

Topics

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors