Last updated on 17th March 2026

Managing Large Files with Git LFS

If you've ever watched a git clone crawl to a halt because someone committed a 500MB design asset three years ago, you already understand the problem Git Large File Storage solves. Git LFS replaces large binary files in your repository with lightweight pointer files, while storing the actual content on a dedicated LFS server. The result is a lean repository that clones in seconds instead of minutes.

This tutorial won't rehash the basics — the official Git LFS documentation covers installation and setup thoroughly. Instead, we'll focus on what happens when repositories that use Git LFS meet deployment pipelines, where things go wrong, and how to structure your workflow so large files don't derail your releases.


How Git LFS Actually Works (The Short Version)

Git LFS operates as a Git filter. When you track a file type with git lfs track "*.psd", Git stores a small pointer file in the repository and uploads the actual binary to your LFS server (hosted by GitHub, GitLab, Bitbucket, or a self-hosted endpoint).

A pointer file looks like this:

version https://git-lfs.github.com/spec/v1
oid sha256:4d7a214614ab2935c943f9e0ff69d22eadbb8f32b1258daaa5e2ca24d17e2393
size 12345678

The critical detail for deployments: a standard git clone fetches pointer files by default. The actual LFS objects are only downloaded when you run git lfs pull or when the Git LFS smudge filter is properly configured in the environment doing the checkout.

This distinction is the root cause of most deployment failures involving LFS.


The Deployment Problem with Large Files

Without LFS, deploying a repository with large binaries means every deployment transfers the full history of every large file. A 2GB repository takes 2GB of bandwidth per clone, whether you changed one line of CSS or not.

With LFS, deployments become faster — but only if the deployment environment knows how to resolve LFS pointers. If it doesn't, you end up deploying pointer files instead of actual assets, which leads to broken images, missing fonts, or application crashes.

flowchart TD
    A["Developer pushes to remote"] --> B["Repository contains LFS pointers"]
    B --> C{"Deployment tool clones repo"}
    C -->|"LFS-aware"| D["Fetches pointer files + resolves LFS objects"]
    C -->|"Not LFS-aware"| E["Fetches pointer files only"]
    D --> F["Full assets deployed ✓"]
    E --> G["Broken assets deployed ✗"]
    G --> H["Images show as text files"]
    G --> I["Compiled binaries fail to execute"]
    G --> J["Build pipeline errors on missing files"]

Which Projects Actually Need LFS in Their Deploy Pipeline?

Not every project with LFS-tracked files needs those files at deployment time. Understanding this distinction saves significant complexity.

LFS files needed at deploy time:

  • Web projects with committed assets — hero images, icon sets, or PDF downloads stored in the repo rather than a CDN
  • Game or interactive projects — compiled WebAssembly binaries, texture atlases, audio files
  • Documentation sites — diagrams, screenshots, and media embedded directly in the build
  • Applications with bundled binaries — SQLite databases, ML model weights, or compiled extensions

LFS files NOT needed at deploy time:

  • Design source files.psd, .sketch, .fig files used by designers but not served to users
  • Raw video/audio — pre-production media that gets transcoded before deployment
  • Test fixtures — large datasets used in CI but irrelevant to production
  • Archived builds — previous release artifacts kept for reference

If your LFS files fall into the second category, you can skip LFS resolution entirely during deployment and save significant time.


Configuring .gitattributes for Real Projects

A well-structured .gitattributes file is the foundation of any LFS workflow. Here are practical configurations for common deployment scenarios:

Web application with committed media assets:

# Images served directly from the repo
*.png filter=lfs diff=lfs merge=lfs -text
*.jpg filter=lfs diff=lfs merge=lfs -text
*.gif filter=lfs diff=lfs merge=lfs -text
*.webp filter=lfs diff=lfs merge=lfs -text
*.svg filter=lfs diff=lfs merge=lfs -text

# Downloadable files
*.pdf filter=lfs diff=lfs merge=lfs -text
*.zip filter=lfs diff=lfs merge=lfs -text

# Design source files (not needed at deploy time)
*.psd filter=lfs diff=lfs merge=lfs -text
*.sketch filter=lfs diff=lfs merge=lfs -text
*.ai filter=lfs diff=lfs merge=lfs -text

Static site with build artifacts:

# Generated assets tracked in LFS
public/fonts/*.woff2 filter=lfs diff=lfs merge=lfs -text
public/fonts/*.woff filter=lfs diff=lfs merge=lfs -text
static/videos/*.mp4 filter=lfs diff=lfs merge=lfs -text

# Vendor binaries
bin/hugo filter=lfs diff=lfs merge=lfs -text
bin/tailwindcss filter=lfs diff=lfs merge=lfs -text

Application with ML models or datasets:

# Model weights
models/*.onnx filter=lfs diff=lfs merge=lfs -text
models/*.pt filter=lfs diff=lfs merge=lfs -text

# Training data (CI only, not deployed)
data/training/*.csv filter=lfs diff=lfs merge=lfs -text
data/fixtures/*.sql filter=lfs diff=lfs merge=lfs -text

How Deployment Tools Handle LFS

When a deployment tool pulls your repository, the LFS behaviour depends entirely on how that tool performs its Git operations. There are three common patterns:

Pattern 1: Full Clone with LFS Support

The deployment tool runs a full git clone and has Git LFS installed in its environment. The smudge filter runs automatically, and LFS objects are downloaded during checkout. This is the ideal scenario — everything just works.

How to verify this works: After a deployment, check that deployed files are actual binaries, not 130-byte text files starting with version https://git-lfs.github.com/spec/v1.

Pattern 2: Shallow Clone Without LFS

Many deployment tools use git clone --depth 1 for speed. This fetches only the latest commit but may not trigger the LFS smudge filter, depending on the Git and LFS versions installed. The result is pointer files where binaries should be.

The fix: Add a post-checkout or pre-build step that runs:

git lfs install --local
git lfs pull

Pattern 3: Archive-Based Deployment

Some tools download a tarball or zip archive of the repository (e.g., GitHub's archive downloads) rather than performing a Git clone. Archives never include LFS objects — they contain only the pointer files. This is a hard limitation of how Git archives work.

The fix: If your deployment tool uses archive downloads, you need to either: - Switch to a clone-based deployment method - Use a build pipeline that resolves LFS objects as a build step - Host large files externally (CDN, S3) instead of committing them to the repo


Build Pipelines and LFS

If your deployment workflow includes a build step — running npm run build, compiling assets, or generating static files — the build environment needs access to LFS objects that the build process references.

A build pipeline that compiles a static site, for example, needs the actual image files if they're referenced during the build. Pointer files will cause the build to fail or produce a broken output.

A typical build pipeline with LFS resolution looks like this:

# Ensure LFS is available in the build environment
git lfs install

# Pull LFS objects for the current checkout
git lfs pull

# Now run the actual build
npm install
npm run build

The git lfs pull command only downloads LFS objects referenced by the current commit — not the entire LFS history. This keeps build times reasonable even for repositories with extensive LFS usage.


Common Deployment Pitfalls with Git LFS

1. Deploying Pointer Files Instead of Actual Assets

Symptom: Images appear broken, downloads serve 130-byte text files, compiled binaries won't execute.

Cause: The deployment environment doesn't have Git LFS installed or the smudge filter didn't run.

Diagnosis:

# On the deployed server, check a file that should be binary
file public/images/hero.png
# If it says "ASCII text" instead of "PNG image data", you have pointer files

# Or check the file size — pointer files are always small
ls -la public/images/hero.png
# A 130-byte "image" is definitely a pointer file

Fix: Install Git LFS in the deployment environment and run git lfs pull before or after checkout.

2. LFS Authentication Failures During Deployment

Symptom: Deployment fails with Unauthorized or 403 Forbidden errors when fetching LFS objects.

Cause: The deployment environment's Git credentials don't have LFS access, or the LFS server requires separate authentication.

Diagnosis:

# Test LFS access explicitly
GIT_TRACE=1 git lfs pull 2>&1 | grep "HTTP"

Fix: Ensure the deploy key or access token has LFS read permissions. On GitHub, deploy keys automatically have LFS access for the repository. On GitLab, you may need to configure the lfs_url in .lfsconfig:

[lfs]
    url = https://gitlab.com/your-org/your-repo.git/info/lfs

3. LFS Storage Quota Exhaustion

Symptom: Pushes fail with storage limit errors, or LFS objects silently stop uploading.

Cause: Most Git hosting providers have LFS storage quotas (GitHub: 1GB free, GitLab: 5GB free).

Diagnosis:

# Check what's consuming LFS storage
git lfs ls-files --size

# Check total LFS usage
git lfs ls-files --size | awk '{sum += $NF} END {print sum/1024/1024 " MB"}'

Fix: Prune old LFS objects, upgrade your hosting plan, or migrate the largest files to external storage.

4. Slow Deployments Despite Using LFS

Symptom: Deployments take longer than expected even though the repository itself is small.

Cause: LFS objects are being downloaded on every deployment, even when they haven't changed.

Fix: Configure LFS caching in your deployment environment. If the deployment tool preserves the working directory between deployments, LFS objects are cached automatically. If it does a fresh clone each time, you'll re-download LFS objects on every deploy.

# Check which LFS objects would be downloaded
git lfs ls-files --all | wc -l

# Prune unreferenced LFS objects locally
git lfs prune

5. Missing LFS Objects After Branch Switch

Symptom: After deploying a different branch, some assets are missing or corrupted.

Cause: The LFS cache only contains objects for previously checked-out commits. Switching to a branch with different LFS-tracked files requires fetching new objects.

Fix:

# Fetch LFS objects for the target branch before switching
git lfs fetch origin target-branch
git checkout target-branch
git lfs checkout

LFS and CI/CD Integration

If your deployment pipeline includes CI/CD stages (testing, building, deploying), each stage that needs LFS objects must resolve them independently.

GitHub Actions example:

- uses: actions/checkout@v4
  with:
    lfs: true  # This is critical — defaults to false

GitLab CI example:

variables:
  GIT_LFS_SKIP_SMUDGE: "0"  # Ensure LFS objects are fetched

Bitbucket Pipelines:

clone:
  lfs: true

If your CI runs tests that reference LFS-tracked files (integration tests with fixture data, visual regression tests with reference screenshots), omitting LFS resolution will cause intermittent, confusing failures.


When to Skip LFS Entirely

LFS adds operational complexity. Before adopting it, consider whether your project actually benefits:

Use LFS when: - Binary files >10MB are committed regularly - Repository clone time exceeds 60 seconds due to binary bloat - Multiple team members work with large assets that change frequently

Skip LFS when: - Large files are static and rarely change — the one-time clone cost is acceptable - You can host assets externally (CDN, S3, asset management system) - Your deployment tool doesn't support LFS and adding a build step is impractical

For projects where external asset hosting makes more sense, consider tools like AWS S3, a CDN, or a dedicated asset management system. Your deployment pipeline stays simpler, and you avoid LFS-related edge cases entirely.


Maintenance and Housekeeping

LFS repositories accumulate storage over time. Regular maintenance keeps deployments fast and hosting costs down.

Audit current LFS usage:

# List all LFS-tracked files with sizes
git lfs ls-files --size

# Find the largest LFS objects
git lfs ls-files --size | sort -k3 -n -r | head -20

# Check which file types consume the most storage
git lfs ls-files --size | awk '{print $NF, $3}' | sort -k2 -n -r

Clean up unreferenced objects:

# Remove LFS objects not referenced by any local branch
git lfs prune

# Dry run first to see what would be removed
git lfs prune --dry-run

Migrate files out of LFS if they no longer need tracking:

# Stop tracking a file type
git lfs untrack "*.psd"

# Convert existing pointer files back to regular files
git lfs migrate export --include="*.psd"

Key Takeaways

  • Git LFS solves repository bloat but introduces deployment complexity
  • Not all LFS files are needed at deploy time — identify which ones matter
  • Deployment failures with LFS almost always come down to missing git lfs pull in the deployment environment
  • Build pipelines need explicit LFS resolution before the build step runs
  • Archive-based deployments never include LFS objects — you need a clone-based approach
  • Regular LFS maintenance (pruning, auditing) keeps deployments fast and storage costs low

If you're setting up automated deployments for a project that uses Git LFS, consider how your deployment tool handles Git operations. The right configuration means LFS works invisibly. The wrong configuration means broken assets in production.

Ready to streamline your deployment workflow? Get started with DeployHQ and take the complexity out of shipping code to your servers.


If you have questions about deploying projects with Git LFS or any other deployment challenge, reach out to us at support@deployhq.com or find us on Twitter/X.