title: "Storage Adapters"
OpenCodeHub stores git objects, LFS files, package-registry blobs (npm/OCI),
and any artifacts produced by the platform on a pluggable storage backend.
Two backends are supported:
local— the server's filesystem. The default. Suitable for
single-host deployments and for development.s3— any S3-compatible object store. The same code path serves
AWS S3, MinIO, Cloudflare R2, Garage, SeaweedFS, Ceph RGW, Wasabi,
Backblaze B2, and any other S3-v4 implementation.
Removed backends. Previous releases also supported Google Drive,
Microsoft OneDrive, Dropbox, FTP, Google Cloud Storage, Azure Blob
Storage, and an rclone-as-adapter option. They were removed because
none of them support the S3-style multipart semantics OpenCodeHub
needs for efficient large-blob operations; rclone as a backup target
remains as a separate, optional utility (/api/admin/sync,scripts/sync-storage.ts).
📂 Local storage (default)
Data is written under the directory pointed to by DATA_DIR (e.g., /data/storage). A .meta sidecar file is written next to each uploaded blob to persist content-type metadata.
STORAGE_TYPE=local
# If using the 1-click install, DATA_DIR handles this automatically.
# Otherwise, manually set:
DATA_DIR=./data
# (Which internally resolves STORAGE_PATH to ./data/storage)
When to choose local: single-server deployments, self-hosting on a home server or NAS, development, and CI runners that do not need distributed blob storage.
☁️ S3-compatible storage
The s3 driver speaks the S3 v4 API. Any vendor that implements it
works by setting STORAGE_ENDPOINT to the vendor's endpoint URL andSTORAGE_REGION to its documented region string. Path-style addressing
is enabled automatically when an endpoint is provided, which is required
by MinIO, Garage, SeaweedFS, and most self-hosted stacks.
Common configuration
STORAGE_TYPE=s3
STORAGE_BUCKET=opencodehub
STORAGE_REGION=us-east-1
STORAGE_ENDPOINT= # leave empty for AWS S3
STORAGE_ACCESS_KEY_ID=...
STORAGE_SECRET_ACCESS_KEY=...
Preset configurations
AWS S3 (managed)
STORAGE_TYPE=s3
STORAGE_BUCKET=opencodehub
STORAGE_REGION=us-east-1
# STORAGE_ENDPOINT left empty
STORAGE_ACCESS_KEY_ID=AKIA...
STORAGE_SECRET_ACCESS_KEY=...
Create the bucket and an IAM user with s3:ListBucket, s3:GetObject,s3:PutObject, s3:DeleteObject, and s3:GetObjectVersion
permissions. Enable bucket versioning for cheap backups.
MinIO (self-hosted)
MinIO is the most popular S3-compatible server; it works as a drop-in
replacement for AWS S3 and is easy to run on a NAS, home server, or
Kubernetes cluster.
STORAGE_TYPE=s3
STORAGE_BUCKET=opencodehub
STORAGE_REGION=us-east-1
STORAGE_ENDPOINT=http://minio.local:9000
STORAGE_ACCESS_KEY_ID=minioadmin
STORAGE_SECRET_ACCESS_KEY=minioadmin
The bundled docker-compose.yml includes a MinIO service under thewith-minio profile:
docker compose --profile with-minio up -d
The MinIO web console is exposed on :9001; create the bucketopencodehub and an access key before starting the app.
Cloudflare R2
R2 is S3-compatible, has zero egress fees, and is well-suited to
self-hosters who already use Cloudflare for DNS / Tunnel.
STORAGE_TYPE=s3
STORAGE_BUCKET=opencodehub
STORAGE_REGION=auto
STORAGE_ENDPOINT=https://<account_id>.r2.cloudflarestorage.com
STORAGE_ACCESS_KEY_ID=...
STORAGE_SECRET_ACCESS_KEY=...
Create an R2 API token with Object Read & Write scope on the bucket
in the Cloudflare dashboard.
Garage (self-hosted, lightweight)
Garage is an S3-compatible
distributed object store designed for home labs and small clusters.
It's lighter than MinIO and well suited to multi-NAS setups.
STORAGE_TYPE=s3
STORAGE_BUCKET=opencodehub
STORAGE_REGION=garage
STORAGE_ENDPOINT=http://garage.local:3900
STORAGE_ACCESS_KEY_ID=...
STORAGE_SECRET_ACCESS_KEY=...
SeaweedFS (self-hosted, very lightweight)
SeaweedFS is an S3-compatible
distributed object store optimised for small files; great for NAS
deployments where every byte counts.
STORAGE_TYPE=s3
STORAGE_BUCKET=opencodehub
STORAGE_REGION=us-east-1
STORAGE_ENDPOINT=http://seaweedfs.local:8333
STORAGE_ACCESS_KEY_ID=...
STORAGE_SECRET_ACCESS_KEY=...
Ceph RGW (S3 gateway)
If you already run Ceph for block or file storage, the RADOS Gateway
provides a fully S3-compatible HTTP frontend.
STORAGE_TYPE=s3
STORAGE_BUCKET=opencodehub
STORAGE_REGION=default
STORAGE_ENDPOINT=https://rgw.example.com
STORAGE_ACCESS_KEY_ID=...
STORAGE_SECRET_ACCESS_KEY=...
Wasabi, Backblaze B2, DigitalOcean Spaces
All expose an S3-compatible endpoint. Use the S3 access key the vendor
issues and the endpoint URL it documents.
# Wasabi
STORAGE_ENDPOINT=https://s3.wasabisys.com
STORAGE_REGION=us-east-1
# Backblaze B2 (S3-compatible API)
STORAGE_ENDPOINT=https://s3.<region>.backblazeb2.com
STORAGE_REGION=us-west-004
# DigitalOcean Spaces
STORAGE_ENDPOINT=https://<region>.digitaloceanspaces.com
STORAGE_REGION=nyc3
⚠️ Performance Considerations
:::caution[Important for Production]
Object storage (S3/R2/MinIO) introduces significant latency compared to local disk storage for serving live git traffic. This is a fundamental architectural trade-off you should understand before deploying.
:::
Latency Comparison
| Operation | Local SSD | S3/R2 Object Storage |
|---|---|---|
| Repository page load | ~50-100ms | 5-30 seconds |
| Clone (small repo) | ~100ms | 2-10 seconds |
| Push (10 files) | ~200ms | 1-5 seconds |
| File browser navigation | ~30ms | 500ms-2s |
Why Does This Happen?
When using object storage, every Git operation requires multiple HTTP requests:
- List objects - 1 API call
- Download each file - 40+ API calls for a typical repo
- Each request has ~50-100ms latency (network + S3 processing)
A repository page load might download:
HEAD,config,refs/heads/*(branch info)objects/pack/*.idx,*.pack(git objects)- All hook files, info files, etc.
40 files × 100ms = 4+ seconds minimum
Recommendations by Use Case
🏢 Production (Self-Hosted)
Best: Local SSD storage + S3 for backups only
STORAGE_TYPE=local
STORAGE_PATH=/var/lib/opencodehub/repos
Use a cron job or background process to sync to S3 for disaster recovery.
☁️ Serverless (Vercel, Cloudflare Workers)
Trade-off: Accept latency or implement caching
STORAGE_TYPE=s3
# Enable local cache (coming soon)
ENABLE_REPO_CACHE=true
CACHE_TTL_SECONDS=300
How GitHub/GitLab Solve This
| Company | Approach |
|---|---|
| GitHub | Repos on local NVMe clusters, S3 for backups only |
| GitLab | Gitaly (dedicated Git servers with local storage) |
| Bitbucket | Sharded storage servers, repos cached in memory |
Key insight: Major Git hosts never serve live traffic from object storage. They use local disk + memory caching for hot repos.
Future Improvements (Roadmap)
We're working on:
- Local disk cache with TTL-based invalidation
- Parallel S3 downloads (70% faster initial loads)
- Redis metadata cache (instant branch/tree info)
- Gitaly-like architecture for enterprise deployments
🔄 Migrating Between Storage Types
To migrate from local to S3:
# 1. Backup current repos
tar -czf repos-backup.tar.gz data/repos/
# 2. Upload to S3
aws s3 sync data/repos/ s3://your-bucket/repos/ --endpoint-url $STORAGE_ENDPOINT
# 3. Update .env
STORAGE_TYPE=s3
STORAGE_BUCKET=your-bucket
# 4. Clear local cache
rm -rf .tmp/repos/
To migrate from S3 to local:
# 1. Download from S3
aws s3 sync s3://your-bucket/repos/ data/repos/ --endpoint-url $STORAGE_ENDPOINT
# 2. Update .env
STORAGE_TYPE=local
STORAGE_PATH=./data/repos