I was hitting GitHub’s Git LFS bandwidth limits on every build. My Godot project has hundreds of 3D models, textures, and audio files—each push/pull in CI/CD was eating through the 1GB/month free bandwidth.
GitHub’s LFS pricing: $5/month for 50GB storage + 50GB bandwidth. Sounds reasonable until you realize CI/CD runs pull those assets on every build. With 10 builds/day, you burn through bandwidth fast.
After researching alternatives, I found the perfect solution: DVC (Data Version Control) + Cloudflare R2. Cost dropped to ~€0.015/GB/month for storage with zero egress fees.
This guide shows you how to migrate from Git LFS to DVC+R2 for Godot (or any game engine) while keeping your GitHub Actions workflow intact.
Git LFS (GitHub):
DVC + Cloudflare R2:
For a 50GB asset library with 300 CI/CD pulls/month:
DVC benefits:
Cloudflare R2 benefits:
Before (Git LFS):
[Your Machine] ←→ [GitHub LFS] ←→ [GitHub Actions]
$$$ bandwidth $$$
After (DVC + R2):
[Your Machine] ←→ [Git (metadata only)] ←→ [GitHub Actions]
↓ ↓
[Cloudflare R2] ←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←
(assets stored here, free egress)
Git only contains:
.gd scripts.tscn scenes.tres resourcesproject.godot.dvc metadata files (tiny text files)All assets (3D models, textures, audio, fonts) live in R2, versioned via DVC.
godot-project-assets (or your project name)https://<account-id>.r2.cloudflarestorage.com)pip install dvc dvc-s3
DVC uses the S3 protocol to communicate with R2 (R2 is S3-compatible).
cd your-godot-project
dvc init
This creates .dvc/ directory and updates .gitignore.
dvc remote add -d r2storage s3://godot-project-assets/
dvc remote modify r2storage endpointurl https://<account-id>.r2.cloudflarestorage.com
Replace:
godot-project-assets with your bucket name<account-id> with your Cloudflare account ID (from R2 dashboard)dvc remote modify r2storage access_key_id <YOUR_ACCESS_KEY_ID>
dvc remote modify r2storage secret_access_key <YOUR_SECRET_ACCESS_KEY>
Or use environment variables (recommended):
export AWS_ACCESS_KEY_ID=<YOUR_ACCESS_KEY_ID>
export AWS_SECRET_ACCESS_KEY=<YOUR_SECRET_ACCESS_KEY>
git add .dvc/.gitignore .dvc/config .dvcignore
git commit -m "feat: initialize DVC with R2 remote"
git lfs uninstall
git lfs untrack "*"
rm .gitattributes
git add .gitattributes
git commit -m "chore: remove Git LFS tracking"
Typical Godot project structure:
assets/
├── models/ # .glb, .gltf, .obj
├── textures/ # .png, .jpg, .exr
├── audio/ # .wav, .ogg, .mp3
├── fonts/ # .ttf, .otf
└── animations/ # .anim files
# Add entire assets directory
dvc add assets/
# Or add subdirectories individually
dvc add assets/models/
dvc add assets/textures/
dvc add assets/audio/
DVC creates .dvc files (metadata) for each tracked directory:
assets.dvc # Tracks assets/ directory
.gitignore # Updated to ignore actual assets
git add assets.dvc .gitignore
git commit -m "feat: migrate all assets to DVC"
dvc push
This uploads all assets to Cloudflare R2. First push takes time depending on asset size.
Verify in R2 dashboard: You should see files appearing in your bucket.
If assets were previously committed to Git, clean history:
# Install git-filter-repo
pip install git-filter-repo
# Remove assets from entire history
git filter-repo --path assets/ --invert-paths
# Force push (WARNING: rewrites history)
git push origin --force --all
⚠️ Warning: This rewrites Git history. Coordinate with team before running.
Update your .github/workflows/build.yml:
name: Build Godot Project
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Setup Python
uses: actions/setup-python@v4
with:
python-version: '3.10'
- name: Install DVC
run: |
pip install dvc dvc-s3
- name: Pull assets from R2
env:
AWS_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.R2_SECRET_ACCESS_KEY }}
run: |
dvc pull
- name: Setup Godot
uses: chickensoft-games/setup-godot@v1
with:
version: 4.2.1
- name: Build project
run: |
godot --headless --export-release "Linux/X11" build/game.x86_64
- name: Upload build artifact
uses: actions/upload-artifact@v3
with:
name: godot-build
path: build/
R2_ACCESS_KEY_ID, Value: Your R2 Access Key IDR2_SECRET_ACCESS_KEY, Value: Your R2 Secret Access Key# Clone repository
git clone https://github.com/yourusername/godot-project.git
cd godot-project
# Install DVC
pip install dvc dvc-s3
# Set R2 credentials
export AWS_ACCESS_KEY_ID=<key>
export AWS_SECRET_ACCESS_KEY=<secret>
# Pull all assets
dvc pull
Pull latest assets:
git pull
dvc pull
Add new asset:
# Add file to assets directory
cp new_model.glb assets/models/
# Update DVC tracking
dvc add assets/models/
# Commit metadata
git add assets/models.dvc .gitignore
git commit -m "feat: add new model"
# Push asset to R2
dvc push
# Push metadata to GitHub
git push
Update existing asset:
# Modify file in assets/
# DVC detects changes
dvc add assets/models/
git add assets/models.dvc
git commit -m "fix: update character model"
dvc push
git push
Rollback to previous asset version:
# Checkout old commit
git checkout <commit-hash> assets/models.dvc
# Pull assets from that version
dvc checkout
# To keep changes
git add assets/models.dvc
git commit -m "revert: rollback to previous model"
DVC automatically updates .gitignore, but verify:
# .gitignore
/assets/
This ensures actual asset files never get committed to Git.
Add asset workflow documentation:
## Asset Management
This project uses DVC + Cloudflare R2 for asset versioning.
### Setup
1. Install DVC: `pip install dvc dvc-s3`
2. Set credentials: `export AWS_ACCESS_KEY_ID=...` (get from team)
3. Pull assets: `dvc pull`
### Daily Workflow
- Pull latest: `git pull && dvc pull`
- Add asset: `dvc add path/to/asset && dvc push`
- Commit: `git add *.dvc .gitignore && git commit && git push`
### Important
- Git tracks: code, scenes, metadata
- DVC tracks: models, textures, audio, fonts
- Never commit assets directly to Git
Git LFS (Before):
Actually hit the free tier quickly, then bought packs:
DVC + R2 (After):
Savings: $29.25-$49.25/month
| Assets | Git LFS (Storage + Bandwidth) | DVC + R2 (Storage Only) |
|---|---|---|
| 10GB | $5-10/month | €0.15/month |
| 50GB | $30-50/month | €0.75/month |
| 100GB | $60-100/month | €1.50/month |
| 500GB | $300+/month | €7.50/month |
dvc pull Fails in CI/CDCause: Missing or incorrect R2 credentials.
Solution:
dvc pull --verbosedvc pullCause: Local cache out of sync.
Solution:
dvc pull --force
dvc checkout
dvc pushCause: Network timeout or large file size.
Solution:
# Increase timeout
dvc config cache.timeout 3600
# Push with retry
dvc push --remote r2storage --verbose
Cause: Assets in Git history.
Solution:
# Clean Git history (destructive)
git filter-repo --path assets/ --invert-paths
git push origin --force --all
DVC can automate asset pipelines (e.g., texture compression, model optimization):
# dvc.yaml
stages:
optimize_textures:
cmd: python scripts/optimize_textures.py
deps:
- assets/textures/raw/
outs:
- assets/textures/optimized/
Run with:
dvc repro
Use Git LFS instead if:
Use DVC + R2 if:
Migrating from Git LFS to DVC + Cloudflare R2 dropped my Godot project’s asset costs from $30-50/month to €0.75/month while improving CI/CD reliability.
Key benefits:
If you’re hitting Git LFS bandwidth limits or paying for LFS packs, this setup pays for itself immediately.
Next steps:
pip install dvc dvc-s3dvc add assets/Your game assets are now versioned like code, stored cheaply, and deployed for free.
Now go version your game assets without breaking the bank! 🎮