Kokil Thapa - Professional Web Developer in Nepal
Freelancer Web Developer in Nepal with 15+ Years of Experience

Kokil Thapa is an experienced full-stack web developer focused on building fast, secure, and scalable web applications. He helps businesses and individuals create SEO-friendly, user-focused digital platforms designed for long-term growth.

Git LFS for Large Files

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.

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.

Git LFS for Large Files — Storage SplitGit RepositorySource code + historyLFS pointer files onlyLFS Remote StoreVideos, images, PDFsZIPs, fonts, binariesOID refDevelopergit clone / pullSmall repo fastLFS ClientSmudge / cleanFetch on demandCI RunnerLFS pull stepBuild assets
Git LFS for large files keeps Git history lean by storing binary blobs on a dedicated LFS remote

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

  1. Navigate to your project root.
  2. Run git lfs install if you have not done so globally.
  3. Define patterns with git lfs track.
  4. Commit the generated .gitattributes file.
  5. 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.

Git LFS Daily Workflowgit lfs trackSet patternsgit addStage filesgit commitPointer storedgit pushBlob uploadedTeammate or CI: git clone + git lfs pullClone repoPointers only firstLFS smudgeDownload blobsWorking treeReal files ready
Daily Git LFS workflow: track patterns, commit pointers, push blobs, then smudge on clone

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.

ApproachBest forClone sizeCost modelDrawback
Git LFSVersioned design assets tied to code releasesSmall Git, LFS fetched separatelyHost LFS quotas + bandwidthHistory rewrites are painful; host lock-in
S3 / R2 / local diskUser uploads, CMS media, runtime filesGit stays tinyPay per GB stored + egressNot versioned with Git commits
Git submodulesSeparate asset repo with own lifecycleMain repo small; submodule optionalSame as hostSubmodule UX frustrates many teams
Plain Git (no LFS)Files under ~10 MB totalGrows foreverIncluded in host planRepo bloat; slow clones
Release artefacts (CI)Compiled builds, not sourceSource repo leanCI storage minutesAssets detached from commit history
Where Should Large Files Live?Large file needed?User-generatedupload at runtimeDev asset tiedto code versionSmall staticunder 1 MB eachS3 / R2 / diskSpatie Media LibraryGit LFSTrack in repoPlain GitNo extra tooling
Decision guide: user uploads go to object storage; versioned dev assets suit Git LFS for large files

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

  1. Install LFS and define all patterns before touching history.
  2. Run git lfs migrate import to rewrite commits.
  3. Force-push to a new branch first — never straight to main.
  4. Have every teammate re-clone or hard-reset — normal pulls will not fix pointer mismatches.
  5. 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.

LFS History MigrationBefore: Bloated HistoryEvery commit carries full blobsClone: 3.2 GBgit lfs migrate importAfter: Lean HistoryCommits hold pointers onlyClone: 95 MB + LFS pullBlobs on LFS remoteMigration gotchasAll commit SHAs change — open PRs breakTeammates must re-clone, not pullRun git lfs migrate info first to preview
Git LFS migration rewrites history — clone size drops but every commit hash changes

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 install once, define patterns with git lfs track, and commit .gitattributes before 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

Git LFS replaces heavy binary blobs in Git with tiny pointer files (~130 bytes). Git stores the pointers; the real files live on an LFS-compatible remote. On checkout or pull, the LFS client downloads only the blobs your working tree needs.

Use Git LFS when binaries must live in the same repo as application code — design source files, demo videos, font packs, and large seed data. On Laravel apps with media libraries or WooCommerce themes with heavy assets, LFS keeps clone sizes manageable. Do not use it for user-uploaded content; that belongs in S3, R2, or local disk via your application layer, as with Spatie Media Library on production Laravel projects.

GitHub provides 1 GB free LFS storage and 1 GB/month bandwidth on free plans. Exceeding quotas costs roughly USD 5/month per 50 GB — about Rs 670/month for Nepal-based teams.

On Ubuntu or Debian, install via the packagecloud script and apt-get install git-lfs. On macOS, run brew install git-lfs. Run git lfs install once per user account to hook clean and smudge filters into Git. Verify with git lfs version — you should see something like git-lfs/3.6.0. In an existing repo, define patterns with git lfs track, commit the generated .gitattributes file, then push normally.

Track images above 1 MB such as high-res hero banners and PSD exports, video and audio like MP4 demos and screen recordings, archives including ZIP backups and compiled frontend bundles, font packs bundled with themes, and anonymised database dumps for staging. Prefer automation over manual dumps where possible. The article treats LFS as repo hygiene for versioned dev assets, not a substitute for proper object storage at runtime.

After checkout, a pointer file replaces the real binary in your working tree. It is a small text file (~130 bytes) containing the LFS spec version, a sha256 oid, and the original file size. Git history records this lightweight pointer instead of the full blob, which is why clones stay lean while the actual binary lives on the LFS remote until the client smudges it back on pull.

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 LFS backend automatically. Self-hosted GitLab requires LFS object storage configured in gitlab.rb. On sister sites sharing a Deployer 7 and GitLab CI pipeline, we often build assets in CI so the production server never needs LFS at all.

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.

Match storage to who needs the files and how often they change. S3 or R2 suits user uploads and CMS media with a pay-per-GB model but no Git versioning. Git submodules work for a separate asset repo but add operational friction. Plain Git without LFS fits repos under roughly 10 MB total. CI release artefacts keep compiled builds out of source history. For Laravel production apps, object storage handles live uploads; LFS fits design repos or shared asset monorepos.

Adding LFS today only affects new commits — old blobs stay embedded until you rewrite history. Install LFS, define all patterns, then preview with git lfs migrate info --above=1MB. Run git lfs migrate import with your include patterns on a feature branch first, never straight to main. Force-push the migration branch, have every teammate re-clone or hard-reset, and update CI to install LFS and run git lfs pull. Expect commit hashes to change across the rewritten history.

CI runners need the LFS client installed and must run git lfs pull after checkout. Without it, builds receive pointer files instead of real images — webpack or Vite compiles fine but deployed sites show broken assets. In GitLab CI, install git-lfs in before_script, run git lfs install, then git lfs pull. In GitHub Actions, set lfs: true on actions/checkout@v4. I have seen this failure on a client project where the runner image lacked LFS; one pipeline line fixed it after an hour of debugging.

User uploads belong in S3, R2, or local disk via your application layer — not Git LFS. On legal-tech portals, client PDFs flow through encrypted storage with access policies, never through Git. Production Laravel apps typically use Spatie Media Library with S3 for runtime media while LFS handles versioned design assets in a separate repo. LFS version-controls binaries alongside source code; object storage serves CDN-delivered media that changes at runtime.

Forgetting to commit .gitattributes after git lfs track leaves MP4s as raw Git blobs. Files committed before tracking stay in history as plain blobs until you migrate. Cloning with GIT_LFS_SKIP_SMUDGE=1 without a later git lfs pull breaks builds. Exceeding host quotas causes HTTP 402 errors on push. Deployer 7 and similar tools deliver pointer files if the deploy user lacks LFS — install LFS on the server or fetch assets in CI and rsync the built artefact instead.

Partially. Commits work offline because pointers are stored locally. Push and pull of LFS blobs require network access to the LFS server. Clone with GIT_LFS_SKIP_SMUDGE=1 to defer blob downloads until you are back online, but you must run git lfs pull before any build that needs the real assets. On bandwidth-limited VPS hosts this saves transfer cost, though large checkouts may still strain RAM on small servers.

They solve different problems, so production apps often use both. LFS version-controls binaries tied to code releases — design PSDs, demo videos, font packs — while keeping Git clones small. S3 serves runtime uploads and CDN-delivered media with pay-per-GB storage and egress. LFS adds host quota costs and painful history rewrites; S3 lacks Git commit linkage. For a WooCommerce florist project, LFS tracked heavy product photography in the theme repo while live customer content stayed on object storage.

Share this article

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.

Quick Contact Options
Choose how you want to connect me: