Storage Backends
club uses three independent storage layers, each behind an abstract interface. You can mix and match backends freely — for example, SQLite for metadata with S3 for blob storage.
Three Storage Layers
| Layer | Purpose | Default | Alternatives |
|---|---|---|---|
| Metadata Store | Relational data (packages, versions, users, tokens, publishers, audit log) | SQLite | PostgreSQL |
| Blob Store | Binary .tar.gz package archives | Filesystem | S3-compatible, GCS (native) |
| Search Index | Full-text package search | SQLite FTS5 | Meilisearch |
Each layer is configured independently via a single environment variable:
DB_BACKEND=sqlite # or postgresBLOB_BACKEND=filesystem # or s3 | gcsSEARCH_BACKEND=sqlite # or meilisearchSee Environment Variables for the full list of keys each backend accepts.
Metadata Store: SQLite vs PostgreSQL
SQLite is the default metadata store. It requires zero external dependencies and stores everything in a single file.
Pros:
- Zero setup — works out of the box
- Single-file database, easy to backup and restore
- WAL mode enables concurrent reads during writes
- Excellent performance for small to medium deployments (hundreds of packages)
- No external process to manage
Cons:
- Single-writer model (one write at a time, with WAL queuing)
- Not ideal for high-concurrency write workloads
- No built-in replication
Configuration:
DB_BACKEND=sqliteSQLITE_PATH=/data/club.dbSQLite pragmas set on every connection:
PRAGMA journal_mode = WAL;PRAGMA synchronous = NORMAL;PRAGMA foreign_keys = ON;PRAGMA busy_timeout = 5000;PostgreSQL is recommended for larger deployments or when you need concurrent write support, replication, or advanced query features.
Pros:
- True concurrent reads and writes
- Built-in replication and high availability
- Better for high-concurrency workloads
- Rich ecosystem of monitoring and management tools
- Supports large datasets efficiently
Cons:
- Requires running and managing a PostgreSQL server
- More complex backup procedures
- Higher resource usage
Configuration:
DB_BACKEND=postgresPOSTGRES_URL=postgres://club:secret@db.example.com:5432/clubWhen to Choose PostgreSQL
Consider switching from SQLite to PostgreSQL when:
- You have more than 10 concurrent publishing users
- You need database replication or failover
- You are running multiple club server instances behind a load balancer
- Your package catalog exceeds 1,000 packages with frequent updates
Blob Store: Filesystem vs S3 vs GCS
Stores package tarballs as files on the local filesystem.
Pros:
- Zero setup
- Fast local reads (no network latency)
- Easy to backup with standard tools (
tar,rsync) - Simple to inspect and debug
Cons:
- Tied to a single server’s disk
- Disk space limited by the host
- No built-in redundancy
- Cannot be shared across multiple server instances
Configuration:
BLOB_BACKEND=filesystemBLOB_PATH=/data/packagesFile layout:
/data/packages/├── my_package/│ ├── 1.0.0.tar.gz│ ├── 1.1.0.tar.gz│ └── 2.0.0.tar.gz└── other_package/ └── 0.1.0.tar.gzFiles are written atomically using a temp-file-then-rename pattern to prevent partial writes.
Stores package tarballs in an S3-compatible object store. Works with AWS S3, MinIO, DigitalOcean Spaces, and other compatible services.
Pros:
- Virtually unlimited storage
- Built-in redundancy and durability (99.999999999% for AWS S3)
- Shared across multiple server instances
- Pre-signed URLs for direct client downloads (reduces server bandwidth)
Cons:
- Requires an S3-compatible service
- Network latency on reads (mitigated by pre-signed URL redirects)
- More complex to set up and debug
Configuration (AWS S3):
BLOB_BACKEND=s3S3_BUCKET=club-packagesS3_REGION=us-east-1S3_ACCESS_KEY=AKIA...S3_SECRET_KEY=wJalrXUtnFEMI...Configuration (MinIO / R2 / Spaces / B2 / GCS interop):
BLOB_BACKEND=s3S3_ENDPOINT=http://minio:9000S3_BUCKET=club-packagesS3_REGION=us-east-1S3_ACCESS_KEY=minioadminS3_SECRET_KEY=minioadminObject layout:
s3://club-packages/├── my_package/1.0.0.tar.gz├── my_package/1.1.0.tar.gz├── my_package/2.0.0.tar.gz└── other_package/0.1.0.tar.gzWhen a client downloads a package, the server issues a 302 redirect to a pre-signed S3 URL. The client downloads directly from S3, reducing server bandwidth.
See Docker with S3 storage for provider-specific examples.
Uses the native Google Cloud Storage XML/JSON API with a service-account JSON or Application Default Credentials. Preferred on GCP-hosted deployments (GCE / GKE / Cloud Run).
Pros:
- Idiomatic GCP auth — no HMAC key management
- Works seamlessly with workload identity / metadata server (ADC)
- V4 signed URLs for direct client downloads when a private key is present
Cons:
- Requires Google Cloud — not portable
- Under ADC (no private key in process), the server must proxy bytes instead of redirecting
Configuration:
BLOB_BACKEND=gcsGCS_BUCKET=my-project.appspot.comGCS_CREDENTIALS_FILE=/secrets/sa.json # preferred# GCS_CREDENTIALS_JSON='{"type":"service_account",...}'# (unset both to use Application Default Credentials)Auth priority: GCS_CREDENTIALS_FILE > GCS_CREDENTIALS_JSON > ADC.
If you prefer HMAC keys over a service-account JSON, use the s3 backend with S3_ENDPOINT=https://storage.googleapis.com instead — see Docker with S3 storage.
When to Choose S3
Consider switching from filesystem to S3 when:
- You need more storage than a single disk provides
- You are running multiple club instances behind a load balancer
- You want built-in redundancy without managing RAID or replication
- You are already using AWS or a cloud provider with S3-compatible storage
Search Index: SQLite FTS5 vs Meilisearch
Uses SQLite’s built-in FTS5 full-text search engine. Runs in the same process as the server with no external dependencies.
Pros:
- Zero setup — built into SQLite
- Fast for small to medium catalogs
- No external service to manage
- Supports prefix matching and tag filters
Cons:
- Limited ranking/relevance tuning
- No typo tolerance
- Performance degrades with very large catalogs (10,000+ packages)
Configuration:
SEARCH_BACKEND=sqliteThe FTS5 index is maintained automatically via database triggers whenever packages are created, updated, or deleted.
Uses Meilisearch as an external search engine. Provides advanced features like typo tolerance and better relevance ranking.
Pros:
- Typo-tolerant search
- Better relevance ranking
- Handles large catalogs efficiently
- Rich faceting and filtering
Cons:
- Requires running and managing a Meilisearch instance
- Additional infrastructure complexity
- Slight index latency (search results may lag a few seconds behind publishes)
Configuration:
SEARCH_BACKEND=meilisearchMEILISEARCH_URL=http://meilisearch:7700MEILISEARCH_KEY=your-api-keyWhen to Choose Meilisearch
Consider switching from SQLite FTS5 to Meilisearch when:
- You have a large package catalog (1,000+ packages)
- You want typo-tolerant search (e.g., “htttp” still finds “http”)
- You need advanced faceting or filtering beyond what FTS5 provides
Switching Backends
Switching the Metadata Store
To switch from SQLite to PostgreSQL:
- Set up a PostgreSQL database
- Update your configuration:
Terminal window DB_BACKEND=postgresPOSTGRES_URL=postgres://club:secret@db.example.com:5432/club - Restart the server — migrations run automatically on startup and create the schema
Switching the Blob Store
To switch from filesystem to S3:
- Set up an S3 bucket (or MinIO instance)
- Copy existing tarballs to the S3 bucket preserving the path structure:
Terminal window aws s3 sync /data/packages/ s3://club-packages/ - Update your configuration:
Terminal window BLOB_BACKEND=s3S3_BUCKET=club-packagesS3_REGION=us-east-1S3_ACCESS_KEY=...S3_SECRET_KEY=... - Restart the server
Switching the Search Backend
To switch from SQLite FTS5 to Meilisearch:
- Start a Meilisearch instance
- Update your configuration:
Terminal window SEARCH_BACKEND=meilisearchMEILISEARCH_URL=http://meilisearch:7700MEILISEARCH_KEY=your-api-key - Restart the server — the search index is rebuilt automatically from the metadata store
Recommended Configurations
Small Team (up to 50 packages)
DB_BACKEND=sqliteBLOB_BACKEND=filesystemSEARCH_BACKEND=sqliteZero external dependencies. Everything runs in a single container.
Medium Team (50-500 packages)
DB_BACKEND=sqliteBLOB_BACKEND=s3SEARCH_BACKEND=sqliteOffload blob storage to S3 for durability and shared access, but keep SQLite for simplicity.
Large Organization (500+ packages)
DB_BACKEND=postgresBLOB_BACKEND=s3SEARCH_BACKEND=meilisearchFull external stack for maximum scalability, concurrency, and search quality.