ADR-006: S3-Compatible Storage Abstraction
Status: Accepted Date: 2026-01-15
Context
The SiliconGhetto platform needs artifact storage for published game bundles, screenshots, and release assets. Storage must be cheap, reliable, and accessible via HTTP for serving game content to players. We want to avoid vendor lock-in while keeping the initial deployment simple.
Options Considered
1. S3-compatible abstraction with local fallback (chosen)
- Define an
ArtifactStoretrait insg_platform_contracts - Production: Hetzner Object Storage (~€0.005/GB/month) or any S3-compatible service
- Development: Local filesystem implementation
- Swap providers without changing application code
2. Local filesystem only
- Simplest possible implementation
- No CDN, no geographic distribution
- Single disk is a single point of failure
- Doesn’t scale beyond one server’s storage
3. Direct AWS S3
- Reliable and well-documented
- Vendor lock-in to AWS
- More expensive than alternatives for small projects
- AWS SDK adds significant dependency weight
4. Git LFS
- Version-controlled assets
- Not designed for serving web content
- Complex deployment
- Doesn’t support direct HTTP serving with custom headers
Decision
Define an ArtifactStore trait that abstracts storage operations (put, get, delete, public_url). Implement two backends:
- S3-compatible for production (Hetzner Object Storage initially)
- Local filesystem for development
Consequences
Positive
- Swap storage providers without code changes
- Local development requires no cloud account
- Hetzner Object Storage is extremely affordable
- S3-compatible API is a de facto standard — many providers available
- Objects served directly via HTTP with proper CORS/CORP headers
Negative
- Abstraction adds a layer of indirection
- S3 API has subtleties (eventual consistency, multipart uploads)
- Must configure CORS on the storage bucket for cross-origin access
- No built-in CDN — must add separately if needed
Mitigations
- Keep the trait minimal — only the operations we actually use
- Document required bucket CORS/CORP configuration
- Consider Cloudflare R2 (S3-compatible, built-in CDN, no egress fees) as a future option
- Test against MinIO locally for S3 compatibility
Trait Definition
pub trait ArtifactStore {
type Error;
fn put(&self, key: &str, data: &[u8], content_type: &str)
-> impl Future<Output = Result<String, Self::Error>>;
fn get(&self, key: &str)
-> impl Future<Output = Result<Vec<u8>, Self::Error>>;
fn delete(&self, key: &str)
-> impl Future<Output = Result<(), Self::Error>>;
fn public_url(&self, key: &str) -> String;
}