TranscendCode

Ditch Git LFS: Use DVC + Cloudflare R2 for Game Assets (€0.015/GB vs GitHub's Bandwidth Bills)

Bryce Mc Williams
#godot#game-development#dvc#cloudflare-r2#git-lfs#version-control#devops#ci-cd#github-actions#cost-optimization

The Git LFS Problem for Game Developers

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.

Why DVC + Cloudflare R2 Wins

The Cost Comparison

Git LFS (GitHub):

DVC + Cloudflare R2:

For a 50GB asset library with 300 CI/CD pulls/month:

Technical Advantages

DVC benefits:

Cloudflare R2 benefits:

What This Setup Does

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:

All assets (3D models, textures, audio, fonts) live in R2, versioned via DVC.

Prerequisites

Step 1: Setup Cloudflare R2 Bucket

  1. Log in to Cloudflare Dashboard
  2. Navigate to R2 Object Storage
  3. Click Create bucket
  4. Name: godot-project-assets (or your project name)
  5. Region: Choose closest to you or CI/CD runners

Generate API Credentials

  1. Go to R2 → Manage R2 API Tokens
  2. Click Create API Token
  3. Permissions: Object Read & Write
  4. Apply to: Specific buckets → select your bucket
  5. Save these values:
    • Access Key ID
    • Secret Access Key
    • Endpoint URL (format: https://<account-id>.r2.cloudflarestorage.com)

Step 2: Install and Configure DVC

Install DVC Locally

pip install dvc dvc-s3

DVC uses the S3 protocol to communicate with R2 (R2 is S3-compatible).

Initialize DVC in Your Repository

cd your-godot-project
dvc init

This creates .dvc/ directory and updates .gitignore.

Configure R2 as Remote Storage

dvc remote add -d r2storage s3://godot-project-assets/
dvc remote modify r2storage endpointurl https://<account-id>.r2.cloudflarestorage.com

Replace:

Set Credentials (Local Development)

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>

Commit DVC Configuration

git add .dvc/.gitignore .dvc/config .dvcignore
git commit -m "feat: initialize DVC with R2 remote"

Step 3: Migrate Assets from Git LFS to DVC

Remove Git LFS Tracking (If Using LFS)

git lfs uninstall
git lfs untrack "*"
rm .gitattributes
git add .gitattributes
git commit -m "chore: remove Git LFS tracking"

Identify Asset Directories

Typical Godot project structure:

assets/
├── models/       # .glb, .gltf, .obj
├── textures/     # .png, .jpg, .exr
├── audio/        # .wav, .ogg, .mp3
├── fonts/        # .ttf, .otf
└── animations/   # .anim files

Add Assets to DVC

# 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

Stage DVC Metadata Files

git add assets.dvc .gitignore
git commit -m "feat: migrate all assets to DVC"

Push Assets to R2

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.

Step 4: Configure GitHub Actions CI/CD

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/

Add R2 Credentials to GitHub Secrets

  1. Go to your GitHub repo → Settings → Secrets and variables → Actions
  2. Click New repository secret
  3. Add two secrets:
    • Name: R2_ACCESS_KEY_ID, Value: Your R2 Access Key ID
    • Name: R2_SECRET_ACCESS_KEY, Value: Your R2 Secret Access Key

Step 5: Developer Workflow

New Developer Setup

# 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

Daily Workflow

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"

Step 6: Repository Cleanup

Update .gitignore

DVC automatically updates .gitignore, but verify:

# .gitignore
/assets/

This ensures actual asset files never get committed to Git.

Update README

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

Cost Analysis

My Project (50GB Assets, 300 CI/CD Pulls/Month)

Git LFS (Before):

Actually hit the free tier quickly, then bought packs:

DVC + R2 (After):

Savings: $29.25-$49.25/month

Scaling Comparison

AssetsGit 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

Troubleshooting

Problem: dvc pull Fails in CI/CD

Cause: Missing or incorrect R2 credentials.

Solution:

Problem: Assets Not Updating After dvc pull

Cause: Local cache out of sync.

Solution:

dvc pull --force
dvc checkout

Problem: Large Assets Timeout During dvc push

Cause: Network timeout or large file size.

Solution:

# Increase timeout
dvc config cache.timeout 3600

# Push with retry
dvc push --remote r2storage --verbose

Problem: Git Repository Still Large

Cause: Assets in Git history.

Solution:

# Clean Git history (destructive)
git filter-repo --path assets/ --invert-paths
git push origin --force --all

Advanced: DVC Pipelines for Asset Processing

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

When NOT to Use This Setup

Use Git LFS instead if:

Use DVC + R2 if:

Conclusion

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:

  1. Create Cloudflare R2 bucket (free tier)
  2. Install DVC: pip install dvc dvc-s3
  3. Migrate assets: dvc add assets/
  4. Update GitHub Actions workflow
  5. Delete Git LFS tracking

Your game assets are now versioned like code, stored cheaply, and deployed for free.

Resources

Now go version your game assets without breaking the bank! 🎮

← Back to Blog