GitHub for Code, S3/R2 for LFS: A Team Workflow That Actually Works
If your team uses Git LFS for images, videos, model artifacts, or other large files, GitHub LFS costs can creep up fast.
The setup that worked best for us is simple:
- Keep GitHub for source code and collaboration.
- Store LFS objects in your own S3-compatible bucket (AWS S3, Cloudflare R2, etc.).
- Use named profiles so developers don’t reconfigure credentials for every repo.
This post walks through that workflow.
Why this setup is better for teams
Most DIY Git LFS + S3 setups are repo-by-repo and annoying to maintain.
For teams, the better pattern is:
- Configure credentials once per machine/user.
- Reuse those credentials via a profile name across many repos.
- Keep per-repo storage organization in repo config (for example:
org/repopath prefixes).
That gives you:
- Lower ongoing costs.
- Cleaner onboarding.
- More consistent bucket structure.
- Less copy-paste infra drift between repos.
Prerequisites
gitgit-lfs- Node.js + npm
- An S3-compatible bucket (AWS S3 / Cloudflare R2 / others)
Install Git LFS if needed:
git lfs install1) Install s3-lfs
npm i -g s3-lfs2) Set up a profile once
s3-lfs setupDuring setup, enter your bucket/provider credentials.
This supports AWS S3 and S3-compatible providers like Cloudflare R2.
You only do this once per profile (my-profile, prod-assets, r2-main, etc.).
3) Track large files in Git LFS
In each repository, define what should go through LFS:
git lfs track "*.png"
git lfs track "*.jpg"
git lfs track "*.mp4"Commit .gitattributes:
git add .gitattributes
git commit -m "Track large assets with Git LFS"4) Configure repo-level LFS destination args
Now tell this repo which profile/path to use:
git config --add lfs.customtransfer.s3-lfs.args '--profile=my-profile --root_path=githubORG/githubRepo'This is the key team ergonomics piece:
- Credentials stay in profile setup (not repeated per repo).
- Repo chooses its own namespace/path.
- Bucket stays organized by org/repo (or any convention you use).
5) Commit and push normally
git add path/to/large-file.png
git commit -m "Add large asset"
git pushFrom here on, your normal Git workflow remains unchanged. Code is in GitHub; LFS objects are stored in your S3/R2 bucket.
Suggested team conventions
Use conventions early to avoid storage chaos later.
Profile naming:
r2-prodr2-stagingaws-prod-assets
Root path format:
org/repoteam/projectenv/org/repo(if you want explicit environment split)
Example:
--profile=r2-prod --root_path=myorg/web-appSecurity notes (important)
- Use least-privilege bucket credentials.
- Rotate access keys periodically.
- Don’t commit credentials or secrets to repos.
- Keep profile setup local to developers/CI environments.
- In CI, use secure secret injection (not plaintext in scripts).
Cloudflare R2 vs AWS S3 (quick note)
Both work. If your priority is cost reduction for LFS-heavy teams, R2 is often attractive. If you’re already standardized on AWS IAM, S3 may be simpler operationally.
Pick based on your existing infra and access model, not hype.
TL;DR
If your team uses Git LFS across multiple repositories:
- Install
s3-lfs. - Run
s3-lfs setuponce per machine/profile. - Track files with
git lfs track. - Set repo args with profile + root path.
- Keep pushing as usual.
You get a cheaper, cleaner, and more scalable Git LFS workflow without making every repo a custom snowflake.
© 2026 Ashutosh Kumar.Back to Portfolio