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:
- Open the repository, then Settings → General → Git LFS.
- Turn on Enable LFS for this repository.
- 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:
- The Batch API rewrites any oversized object to a
507error entry, so the client receives transfer actions only for the objects that fit. - The upload
PUTchecks the declaredContent-Lengthagainst the quota before reading the body. - The upload
PUTre-checks the actual byte total after buffering, which catches a client that understatesContent-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 |
Related pages
- Get started
- CLI reference — tokens and authentication
- .anvil.yml — CI configuration
- Runners — self-hosted CI workers
- Registry — OCI container storage
- Webhooks — event delivery