Environment Variables
club is configured primarily through environment variables. Every setting can be controlled via an environment variable, and environment variables always take precedence over values in the YAML config file.
Priority order: Environment variable > YAML config file > Default value
Server
| Variable | Required | Default | Description |
|---|---|---|---|
SERVER_URL | Yes | — | Public URL of the club server |
HOST | No | 0.0.0.0 | IP address to bind the HTTP server to |
LISTEN_PORT | No | 8080 | Port the server listens on inside the container |
LOG_LEVEL | No | info | Log verbosity |
SERVER_URL
The public URL where clients will reach the server. Used to construct archive_url in API responses, upload redirect URLs, and the implicitly-trusted Origin for browser requests.
SERVER_URL=https://packages.example.comHOST
The IP address to bind the HTTP listener to.
HOST=0.0.0.0 # Listen on all interfaces (default)HOST=127.0.0.1 # Localhost only (when behind a reverse proxy on the same machine)LISTEN_PORT
The port the server listens on inside the container (or host, for a from-source install).
LISTEN_PORT=8080 # DefaultLISTEN_PORT=3000 # Custom portLOG_LEVEL
Controls log verbosity. Valid values: debug, info, warning, error.
LOG_LEVEL=info # Default: normal operationLOG_LEVEL=debug # Verbose logging for development/debuggingLOG_LEVEL=warning # Quiet mode, only warnings and errorsAuthentication and sessions
| Variable | Required | Default | Description |
|---|---|---|---|
JWT_SECRET | Yes | — | HMAC secret used by the auth subsystem (>= 32 chars). Name is historical — sessions themselves are opaque tokens, not JWTs. |
SESSION_TTL_HOURS | No | 1 | Session token sliding time-to-live in hours |
TOKEN_EXPIRY_DAYS | No | 365 | Default API token expiry in days |
BCRYPT_COST | No | 12 | bcrypt hashing cost factor (10-14 recommended) |
SIGNUP_ENABLED | No | false | Enable /signup and public POST /api/auth/signup |
TRUST_PROXY | No | false | Trust X-Forwarded-Proto / X-Forwarded-For headers |
ALLOWED_ORIGINS | No | — | CSV of extra browser origins permitted on public state-changing endpoints |
JWT_SECRET
HMAC secret used by the auth subsystem. Must be at least 32 characters. (The name is historical — session tokens themselves are opaque values stored in the database, not JSON Web Tokens.)
# Generate a secure secretJWT_SECRET=$(openssl rand -hex 32)SESSION_TTL_HOURS
How long web UI session tokens remain valid, in hours.
SESSION_TTL_HOURS=1 # Default: 1 hourSESSION_TTL_HOURS=8 # Longer sessions for internal useTOKEN_EXPIRY_DAYS
Default expiry for newly created API tokens, in days. Users can override this per-token.
TOKEN_EXPIRY_DAYS=365 # Default: 1 yearTOKEN_EXPIRY_DAYS=90 # Shorter expiry for tighter securityBCRYPT_COST
The bcrypt cost factor for password hashing. Higher values are more secure but slower.
BCRYPT_COST=12 # Default: good balance of security and speedBCRYPT_COST=14 # Higher security, ~4x slower than 12BCRYPT_COST=10 # Faster, acceptable for developmentSIGNUP_ENABLED
When true, exposes the /signup page and POST /api/auth/signup endpoint so anyone can self-register. New signups are created with the member role so they can publish immediately. Default: false (closed / invite-only).
SIGNUP_ENABLED=trueTRUST_PROXY
When true, the server honours X-Forwarded-Proto (to decide whether to set the Secure attribute on session cookies) and X-Forwarded-For (to record the real client IP in audit logs). Enable only when club is behind a reverse proxy that strips any client-supplied copies of these headers.
TRUST_PROXY=trueSee Trusting your proxy for the full rationale.
ALLOWED_ORIGINS
Comma-separated list of additional browser origins allowed to POST to public, unauthenticated endpoints (/api/auth/login, /api/auth/signup, /api/setup/*). SERVER_URL is always trusted implicitly.
ALLOWED_ORIGINS=https://www.packages.example.com,https://packages.internal.example.comDatabase (Metadata Store)
| Variable | Required | Default | Description |
|---|---|---|---|
DB_BACKEND | No | sqlite | Database backend: sqlite or postgres |
SQLITE_PATH | No | /data/club.db | Path to SQLite database file |
POSTGRES_URL | Conditional | — | PostgreSQL connection URL (required when DB_BACKEND=postgres) |
DB_BACKEND=sqlite # Default: zero-dependency, great for small/medium deploymentsDB_BACKEND=postgres # Larger deployments; requires POSTGRES_URLSQLITE_PATH
Path to the SQLite database file. Only used when DB_BACKEND=sqlite.
SQLITE_PATH=/data/club.db # Default (Docker)SQLITE_PATH=/var/lib/club/club.db # Custom pathPOSTGRES_URL
Standard PostgreSQL connection URL. Required when DB_BACKEND=postgres.
POSTGRES_URL=postgres://club:secret@localhost:5432/clubPOSTGRES_URL=postgres://club:secret@db.example.com:5432/club?sslmode=requireBlob Storage
| Variable | Required | Default | Description |
|---|---|---|---|
BLOB_BACKEND | No | filesystem | filesystem, s3, or gcs |
BLOB_PATH | No | /data/packages | Root directory for filesystem-backed storage |
S3_BUCKET | Conditional | — | S3 bucket name (required for s3) |
S3_REGION | Conditional | — | S3 region (required for s3) |
S3_ACCESS_KEY | Conditional | — | S3 access key ID (required for s3) |
S3_SECRET_KEY | Conditional | — | S3 secret access key (required for s3) |
S3_ENDPOINT | Optional | — | S3-compatible endpoint (MinIO, R2, Spaces, B2, GCS interop) |
GCS_BUCKET | Conditional | — | GCS bucket name (required for gcs) |
GCS_CREDENTIALS_FILE | Optional | — | Path to service-account JSON |
GCS_CREDENTIALS_JSON | Optional | — | Inline service-account JSON |
BLOB_BACKEND
Where to store package .tar.gz archives.
BLOB_BACKEND=filesystem # Default: local diskBLOB_BACKEND=s3 # AWS S3 / R2 / MinIO / Spaces / B2 / GCS S3-interopBLOB_BACKEND=gcs # Native Google Cloud Storage (service account / ADC)BLOB_PATH
Root directory for package tarballs when BLOB_BACKEND=filesystem.
BLOB_PATH=/data/packages # Default (Docker)S3 Configuration
S3_BUCKET, S3_REGION, S3_ACCESS_KEY, S3_SECRET_KEY are required when BLOB_BACKEND=s3. S3_ENDPOINT is optional for AWS but required for anything else.
# AWS S3BLOB_BACKEND=s3S3_BUCKET=club-packagesS3_REGION=us-east-1S3_ACCESS_KEY=AKIAIOSFODNN7EXAMPLES3_SECRET_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
# MinIO or other S3-compatible (Cloudflare R2, DO Spaces, Backblaze B2, GCS interop)S3_ENDPOINT=http://minio:9000See Docker with S3 storage for provider-specific guidance.
GCS Configuration
GCS_BUCKET is required when BLOB_BACKEND=gcs. Auth priority: GCS_CREDENTIALS_FILE > GCS_CREDENTIALS_JSON > Application Default Credentials.
BLOB_BACKEND=gcsGCS_BUCKET=my-project.appspot.comGCS_CREDENTIALS_FILE=/secrets/sa.json# or:# GCS_CREDENTIALS_JSON='{"type":"service_account",...}'# or: leave both unset to use ADC (GCE/GKE/Cloud Run metadata server)Search
| Variable | Required | Default | Description |
|---|---|---|---|
SEARCH_BACKEND | No | sqlite | sqlite (FTS5) or meilisearch |
MEILISEARCH_URL | Conditional | — | Meilisearch URL (required when meilisearch) |
MEILISEARCH_KEY | Conditional | — | Meilisearch API key (required when meilisearch) |
SEARCH_BACKEND=sqlite # Default: SQLite FTS5, zero dependenciesSEARCH_BACKEND=meilisearch # Advanced search features or very large catalogsMEILISEARCH_URL=http://meilisearch:7700MEILISEARCH_KEY=your-meilisearch-api-keyUpload
| Variable | Required | Default | Description |
|---|---|---|---|
TEMP_DIR | No | /tmp/club-uploads | Temporary directory for upload processing |
MAX_UPLOAD_BYTES | No | 104857600 | Maximum tarball upload size in bytes (100 MiB) |
DARTDOC_PATH | No | /data/docs | Output directory for generated dartdoc |
MAX_UPLOAD_BYTES=104857600 # Default: 100 MiBMAX_UPLOAD_BYTES=524288000 # 500 MiB for large packagesMAX_UPLOAD_BYTES=10485760 # 10 MiB for strict limitsScoring (pana Analysis)
Scoring is managed entirely from the admin UI — no environment variables needed. Go to Admin > Settings > Scoring to download a Flutter SDK and enable scoring. In Docker, the Flutter SDK and pub cache live under /data/sdks and /data/caches/pub-cache. See the package scoring guide for details.
Static files
| Variable | Required | Default | Description |
|---|---|---|---|
STATIC_FILES_PATH | No | auto-detect | Path to the SvelteKit static build output |
Auto-detection probes, in order:
/app/static/web(Docker default)packages/club_web/build(local dev, from repo root)
Override only if neither default matches your layout.
Config file
| Variable | Required | Default | Description |
|---|---|---|---|
CONFIG | No | /etc/club/config.yaml | Path to an optional YAML config file |
CONFIG=/etc/club/config.yaml # Explicit pathIf unset, club checks for /etc/club/config.yaml; if the file doesn’t exist, YAML loading is simply skipped. See YAML Config File for the file format.
Admin account
There are no ADMIN_EMAIL / ADMIN_PASSWORD environment variables. On first boot, club prints a one-time setup code to the server logs and exposes a web wizard at /setup. Visit that URL, paste the code, and create the initial admin account through the browser.
[INFO] Setup code: XXXX-XXXX-XXXX-XXXX[INFO] Finish setup at: https://packages.example.com/setupOnce an admin exists, the setup wizard closes and the code is no longer valid.
Validation
The server validates configuration at startup and fails fast with a clear error message if anything is wrong:
| Rule | Error Message |
|---|---|
JWT_SECRET not set | JWT_SECRET is required. |
JWT_SECRET < 32 chars | JWT_SECRET must be at least 32 characters. |
DB_BACKEND=postgres without URL | POSTGRES_URL must be set when DB_BACKEND=postgres. |
BLOB_BACKEND=s3 without bucket/keys | S3_ACCESS_KEY and S3_SECRET_KEY are required when BLOB_BACKEND=s3. |
BLOB_BACKEND=gcs without bucket | GCS_BUCKET is required when BLOB_BACKEND=gcs. |
SEARCH_BACKEND=meilisearch without URL | MEILISEARCH_URL must be set when SEARCH_BACKEND=meilisearch. |
Quick Reference by Profile
Minimal (Development)
SERVER_URL=http://localhost:8080JWT_SECRET=dev-secret-at-least-32-characters-longFinish admin setup via the one-time code printed to the logs.
Docker Default (SQLite + Filesystem)
SERVER_URL=https://packages.example.comJWT_SECRET=$(openssl rand -hex 32)TRUST_PROXY=true # when behind a reverse proxyProduction (PostgreSQL + S3)
SERVER_URL=https://packages.example.comJWT_SECRET=$(openssl rand -hex 32)TRUST_PROXY=true
DB_BACKEND=postgresPOSTGRES_URL=postgres://club:secret@db.example.com:5432/club
BLOB_BACKEND=s3S3_BUCKET=club-packagesS3_REGION=us-east-1S3_ACCESS_KEY=AKIA...S3_SECRET_KEY=wJalrXUtnFEMI...