
September 10, 2026
13 min read
By Kokil Thapa | Last reviewed: September 2026
Binary assets bloat Git repositories fast. PSD exports, video clips, database dumps, and compiled build artefacts turn every clone into a multi-gigabyte download. Git LFS for large files solves this by storing lightweight pointer files in Git while keeping the actual blobs on a separate LFS server. On production web development projects — Laravel apps with media libraries, WooCommerce themes with heavy assets, or legal-tech portals with PDF templates — I treat LFS as a repo hygiene tool, not a substitute for proper object storage.
git lfs track on file patterns, commit .gitattributes, and push normally — Git stores pointers while LFS stores the real files remotely.What is Git LFS for Large Files and when should you use it?
Git was built for source code — text files that diff cleanly and compress well. Drop a 120 MB video or a 40 MB ZIP into a commit and Git stores the full blob forever. Every future clone downloads that weight, even if the file was deleted three years ago.
Git Large File Storage (LFS) splits the problem. Git records a tiny pointer file (~130 bytes). The real binary lives on an LFS-compatible remote — GitHub, GitLab, Bitbucket, or a self-hosted LFS server. When you checkout or pull, the LFS client downloads only the blobs your working tree needs.
A pointer file looks like this inside your working tree after checkout:
version https://git-lfs.github.com/spec/v1
oid sha256:4d7a214614…
size 48329347 Use Git LFS when binaries must live in the same repo as application code. Common cases include design source files, demo videos, font packs, and seed data too large for plain Git. Do not use LFS for user-uploaded content — that belongs in S3, R2, or local disk via your application layer, as covered in guides on Laravel S3 file storage and Laravel file uploads with object storage.
File types worth tracking
- Images above 1 MB — high-res hero banners, raw photography, PSD/AI exports
- Video and audio — MP4 demos, podcast assets, screen recordings
- Archives — ZIP backups, compiled frontend bundles you must version
- Fonts — OTF/TTF/WOFF packs bundled with themes
- Database dumps — anonymised staging snapshots (prefer automation over manual dumps)
GitHub provides 1 GB free LFS storage and 1 GB/month bandwidth on free plans. GitLab and Bitbucket offer similar quotas. Exceed them and you pay per gigabyte — roughly USD 5/month per 50 GB on GitHub as of 2026. For a Nepal agency billing in NPR, that is about Rs 670/month — cheap until you store hundreds of gigabytes.
How do you install and configure Git LFS for Large Files?
Installation takes two minutes on Ubuntu 22/24, macOS, or Windows. The LFS client hooks into Git's smudge and clean filters automatically after you run git lfs install once per machine.
Install on Ubuntu and macOS
# Ubuntu / Debian
curl -s https://packagecloud.io/install/repositories/github/git-lfs/script.deb.sh | sudo bash
sudo apt-get install git-lfs
# macOS with Homebrew
brew install git-lfs
# One-time setup per user account
git lfs install Verify with git lfs version. You should see something like git-lfs/3.6.0. The official project lives at git-lfs.com, and the source repository is on GitHub.
Enable LFS in an existing repository
- Navigate to your project root.
- Run
git lfs installif you have not done so globally. - Define patterns with
git lfs track. - Commit the generated
.gitattributesfile. - Push as normal — LFS uploads happen during push.
cd /var/www/my-laravel-app
git lfs install
git lfs track "*.psd"
git lfs track "*.mp4"
git lfs track "public/assets/fonts/**"
git add .gitattributes
git commit -m "Configure Git LFS tracking patterns" The .gitattributes file is the contract for your team. Commit it early. Without it, new clones will not know which extensions belong to LFS. This is the same class of problem as a broken .gitignore configuration — silent until someone clones fresh.
Per-repository versus global install
git lfs install sets up global clean/smudge filters in ~/.gitconfig. Use git lfs install --local inside CI containers where you do not want system-wide changes. On shared Linux servers managed for clients, I install LFS globally for the deploy user and verify it in the CI pipeline before the first asset-heavy deploy.
How do you track and push large files with Git LFS in daily workflow?
After patterns are defined, your daily workflow barely changes. Add files, commit, push. The LFS client intercepts matching paths during commit and replaces them with pointers.
Essential commands
# List tracked patterns
git lfs track
# See which working-tree files are LFS-managed
git lfs ls-files
# Fetch LFS objects without checking out a branch
git lfs fetch --all
# Download LFS blobs for current checkout
git lfs pull
# Clone with LFS in one step
git lfs clone https://github.com/org/repo.git
# Check LFS disk usage locally
git lfs env On a WooCommerce florist project with heavy product photography, LFS tracked *.jpg files above theme defaults. Clones dropped from 2.1 GB to 180 MB. Product images still arrived after git lfs pull, which our GitLab CI step ran before the asset build — similar to how we wire Git hooks for automated checks.
CI/CD integration
CI runners need the LFS client installed. A typical GitLab CI job looks like this:
before_script:
- apt-get update -qq && apt-get install -y git-lfs
- git lfs install
- git lfs pull
build:
script:
- npm ci
- npm run build GitHub Actions sets lfs: true on checkout:
- uses: actions/checkout@v4
with:
lfs: true Skip the LFS pull step and your build gets pointer files instead of real images. The failure mode is confusing — webpack or Vite compiles fine, but the deployed site shows broken assets. I've seen this on a client project where the runner image lacked LFS. The fix was one line in the pipeline, but debugging took an hour because the error looked like a path problem.
What are the best alternatives to Git LFS for Large Files?
LFS is not always the right tool. Match the storage layer to who needs the files and how often they change.
| Approach | Best for | Clone size | Cost model | Drawback |
|---|---|---|---|---|
| Git LFS | Versioned design assets tied to code releases | Small Git, LFS fetched separately | Host LFS quotas + bandwidth | History rewrites are painful; host lock-in |
| S3 / R2 / local disk | User uploads, CMS media, runtime files | Git stays tiny | Pay per GB stored + egress | Not versioned with Git commits |
| Git submodules | Separate asset repo with own lifecycle | Main repo small; submodule optional | Same as host | Submodule UX frustrates many teams |
| Plain Git (no LFS) | Files under ~10 MB total | Grows forever | Included in host plan | Repo bloat; slow clones |
| Release artefacts (CI) | Compiled builds, not source | Source repo lean | CI storage minutes | Assets detached from commit history |
For Laravel production apps, Spatie Media Library with S3 is the default pattern on projects like Adventure Third Pole Trek. Git LFS fits the design repo or a shared assets monorepo — not the live user upload directory. On legal-tech portals such as Mijar Law Associates, client PDFs never touch Git. They flow through encrypted storage with access policies, aligned with file upload security practices.
Submodule-based asset repos work when designers need full Git history but developers want a thin checkout. The trade-off is operational: submodule updates add friction to every release, especially under trunk-based branching.
How do you migrate an existing repository to Git LFS?
Adding LFS today only affects new commits. Old blobs remain embedded in history until you rewrite it. That is the hardest part of adopting Git LFS for large files on a mature repo.
Step-by-step migration
- Install LFS and define all patterns before touching history.
- Run
git lfs migrate importto rewrite commits. - Force-push to a new branch first — never straight to main.
- Have every teammate re-clone or hard-reset — normal pulls will not fix pointer mismatches.
- Update CI to include LFS install and pull steps.
# Preview what would change (no writes)
git lfs migrate info --above=1MB
# Rewrite history on current branch — destructive
git lfs migrate import --include="*.mp4,*.psd,*.zip" --everything
# Safer: rewrite only recent commits on a feature branch
git checkout -b lfs-migration
git lfs migrate import --include="*.mp4" --above=5MB The git lfs migrate command uses git filter-repo mechanics under the hood. It replaces matching blobs in every commit with LFS pointers and uploads blobs to your LFS remote. Expect the operation to take minutes on repos with thousands of commits and gigabytes of binaries.
Before migrating production repos, read the official migration guide at Git LFS server discovery docs. Coordinate with your team the same way you would for a history rewrite with rebase. If something goes wrong, git reflog may still save local work — but only on machines that have not garbage-collected yet.
Removing files from history without LFS
Sometimes the answer is deletion, not LFS. If a 500 MB dump was committed by mistake, use git filter-repo or BFG Repo-Cleaner to purge it. Then add the path to .gitignore. Scan for secrets afterward — large dumps often contain credentials, as covered in secrets scanning with Gitleaks.
What common Git LFS mistakes break clones and CI pipelines?
Most LFS failures are configuration gaps, not LFS bugs. These patterns show up repeatedly on client repos I audit during support and maintenance engagements.
Forgetting .gitattributes in the commit
You ran git lfs track "*.mp4" locally but forgot to commit .gitattributes. Your MP4 lands in Git as a raw blob. The file is already bloated before anyone notices. Always commit .gitattributes in the same commit as your first LFS-tracked file.
Committing LFS files before tracking
Files added before tracking stay in Git history as plain blobs. Fix with git lfs migrate import or remove and re-add after configuring patterns. There is no automatic retroactive conversion on a simple recommit.
Partial clone without LFS pull
GIT_LFS_SKIP_SMUDGE=1 git clone … skips downloading blobs — useful on bandwidth-limited VPS hosts. You must run git lfs pull before builds that need assets. On a small VPS, pair this with guidance from swap file optimisation if large checkouts strain RAM.
Exceeding host LFS quotas
Push succeeds until bandwidth or storage caps hit. GitHub returns HTTP 402 or a generic LFS error. Monitor usage in your host dashboard. Offload old release assets to object storage and reference them in documentation instead of keeping every historical binary in LFS.
Mixing LFS with deployment tools
Deployer 7 and similar tools clone or rsync from Git. If the server deploy user lacks LFS, your release directory gets pointer files. Install LFS on the server or fetch assets in CI and rsync the built artefact. On sister sites sharing our Deployer pipeline, we build assets in GitLab CI — the production server never needs LFS at all.
Validate JSON config files in your pipeline with a JSON formatter tool if LFS-tracked config templates feed automated builds. Broken JSON fails silently until runtime.
Key Takeaways
- Git LFS for large files stores pointers in Git and binaries on an LFS remote — ideal for versioned design assets, not user uploads.
- Run
git lfs installonce, define patterns withgit lfs track, and commit.gitattributesbefore adding binaries. - CI must install LFS and run
git lfs pull— otherwise builds receive pointer files instead of real assets. - Migrating old blobs requires
git lfs migrate import, which rewrites history and forces team re-clones. - For runtime media in Laravel or WordPress, prefer S3/R2 object storage over LFS — see S3 upload patterns instead.
- Monitor LFS storage and bandwidth quotas on your Git host to avoid surprise billing.
People Also Ask
Does Git LFS work with GitHub, GitLab, and Bitbucket?
Yes. All three major hosts support Git LFS natively. Enable it in repository settings, install the client locally and in CI, and pushes upload LFS objects to the host's LFS backend automatically. Self-hosted GitLab needs the LFS object storage configured in gitlab.rb.
What is the default file size threshold for Git LFS?
There is no automatic threshold — you define patterns explicitly with git lfs track. GitHub warns when pushing files above 50 MB and blocks files above 100 MB unless LFS handles them. Use git lfs migrate info --above=1MB to find offenders in existing history.
Can you use Git LFS offline?
Partially. Commits work offline because pointers are stored locally. Push and pull of LFS blobs need network access to the LFS server. Clone with GIT_LFS_SKIP_SMUDGE=1 to defer downloads until you are back online.
Is Git LFS better than storing files in Amazon S3?
They solve different problems. LFS version-controls binaries alongside source code. S3 serves runtime uploads and CDN-delivered media. Production Laravel apps typically use both — LFS for the design repo, S3 for live user content through packages like Spatie Media Library.
Ship lean repositories without losing the assets you need
Git LFS for large files is a practical fix when binaries belong in version control but should not ride along in every clone. Install the client, commit your tracking rules, wire CI correctly, and keep user-generated content on object storage where it belongs. If your repo is already bloated, plan a migration branch and coordinate the history rewrite with your team before force-pushing.
Need help cleaning up a bloated repo, wiring LFS into GitLab CI, or moving media to S3 on a Laravel stack? See our custom software development services or browse the portfolio for examples. For repo audits and pipeline fixes, contact us with your stack details and current clone size — that number tells the story faster than any spec document.
Frequently Asked Questions
0 Comments
Leave a comment
Your email is not published. Comments appear once they have been read. Sign in to have your details filled in.

