Git LFS (Large File Storage) keeps large binaries (images, video, model weights, datasets, PSDs) out of the packfile. Git history stores a small text pointer; the file content lives in Anvil’s LFS backend and transfers over the Batch API. Anvil implements the Git LFS v1 spec: the Batch API, the basic transfer adapter (upload, download, verify), and the Locks API.

Enable LFS on a repository

LFS is configured per repository. A repository admin turns it on:

  1. Open the repository, then Settings → General → Git LFS.
  2. Turn on Enable LFS for this repository.
  3. Optionally set a storage quota in bytes. A repository with no quota is unlimited.

While LFS is off for a repository, its LFS routes return 404 with the body {"message":"LFS not enabled for this repo"}, so the endpoints are indistinguishable from being unmounted.

git lfs authenticates with the same credentials as your git remote. A personal access token that works for git push and git pull over HTTPS also works for LFS — pass the token as the password in HTTP Basic auth (the username is ignored). See the CLI reference for token setup. Pushes over SSH mint a short-lived, per-repository LFS token automatically, so no extra setup is needed for the SSH remote.

Track files

Once LFS is on for the repository, install the LFS hooks locally and tell git which paths go to LFS:

git lfs install # one-time per machine
git lfs track "*.psd" # route *.psd through LFS
git add .gitattributes
git commit -m "track *.psd with LFS"
git push # commits the pointer; git lfs push sends the content

git lfs track writes a rule into .gitattributes. Commit .gitattributes so collaborators inherit the same tracking rules. On git push, the LFS client uploads the file content to Anvil and commits the pointer blob in its place.

What the pointer file looks like

The committed blob for a tracked file is a small text pointer, not the binary. For a 2,097,152-byte file whose SHA-256 is the OID below, the pointer is exactly:

version https://git-lfs.github.com/spec/v1
oid sha256:9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08
size 2097152

The three lines are fixed:

Line Meaning
version The LFS pointer spec URL, always https://git-lfs.github.com/spec/v1
oid sha256: followed by the 64-character lowercase hex digest of the file content
size The content length in bytes, a non-negative decimal integer

Each line ends with a single \n, and the blob ends with a trailing \n. To inspect the pointer for a path without checking out the binary:

git show HEAD:path/to/big.psd # prints the three pointer lines
git lfs pointer --file big.psd # generate the canonical pointer for a local file

Verify content uploaded

After a push, confirm the binary reached the backend rather than only the pointer.

# Show which LFS objects the current ref needs and whether they are present.
git lfs ls-files
# Re-send any objects the server is missing (no-op if all present).
git lfs push origin main
# Fetch all referenced objects and check each downloaded OID matches its hash.
git lfs fetch --all
git lfs fsck

git lfs fetch downloads from GET /<org>/<repo>.git/info/lfs/objects/<oid>; git lfs fsck recomputes the SHA-256 of each fetched object and fails if it does not match the OID, which proves the content stored under that OID is correct. Anvil independently re-checks the SHA-256 on upload and rejects a mismatch with 422.

A repository admin can also confirm usage from Settings → General → Git LFS, which shows used bytes against the quota.

Lock files

Locks prevent two people from editing the same un-mergeable binary at once. A lock is path-unique across the repository.

git lfs lock path/to/big.psd
git lfs locks # list current locks
git lfs unlock path/to/big.psd

Releasing a lock you own requires write access. A repository admin can review and force-release any lock from Settings → LFS Locks; a forced unlock requires admin permission. LFS activity — uploads, downloads, lock operations, and garbage collection — is recorded and viewable by repository admins at Settings → LFS Audit.

Quotas and over-quota pushes

If a repository has an LFS quota set, an upload that would push total used bytes over the quota is rejected with 507 Insufficient Storage. Anvil enforces the quota at three points:

  1. The Batch API rewrites any oversized object to a 507 error entry, so the client receives transfer actions only for the objects that fit.
  2. The upload PUT checks the declared Content-Length against the quota before reading the body.
  3. The upload PUT re-checks the actual byte total after buffering, which catches a client that understates Content-Length.

When a push fails with 507, ask a repository admin to raise the quota (Settings → General → Git LFS) or to reclaim storage from deleted content. When LFS is on and a repository holds orphaned objects — content no longer referenced by any pointer reachable from a branch or tag — an admin can reclaim them with the Run GC action in the same settings panel.

A repository with no quota set is unlimited.

HTTP endpoints

All LFS routes are mounted under the repository’s git HTTP path, /<org>/<repo>.git/info/lfs. For example, the Batch endpoint for fangorn/anvil is https://anvil.fangorn.io/fangorn/anvil.git/info/lfs/objects/batch.

Method Path Purpose
POST /info/lfs/objects/batch Batch API — negotiate transfer actions
GET /info/lfs/objects/:oid Download object content
PUT /info/lfs/objects/:oid Upload object content
POST /info/lfs/verify Verify an uploaded object’s size
GET /info/lfs/locks List locks (optional path or id filter)
POST /info/lfs/locks Create a lock
POST /info/lfs/locks/verify List locks split into yours and others’
POST /info/lfs/locks/:id/unlock Release a lock ("force": true to force-release)

JSON requests and responses use the content type application/vnd.git-lfs+json. Downloaded object content is served as application/octet-stream. The Batch API reports "transfer": "basic" and "hash_algo": "sha256". Status codes you may encounter:

Status Meaning
200 Success (batch, download, upload, verify, list/unlock locks)
201 Lock created
400 Bad request (for example an invalid OID)
401 Authentication required or rejected
403 Authenticated but not permitted for this operation
404 Repository not found, or LFS disabled for the repository
409 Lock conflict — the path is already locked
422 OID or size mismatch on upload or verify
507 LFS quota exceeded