Skip to content

YAML Config File

The YAML config file provides a convenient way to set all club options in a single file instead of individual environment variables. Environment variables always take precedence over values in the config file.

Priority order: Environment variable > YAML config file > Default value

File Location

club looks for the config file in this order:

  1. The path specified by the CONFIG environment variable
  2. /etc/club/config.yaml (default location)
Terminal window
# Explicit path
export CONFIG=/opt/club/config.yaml
# Or use the default location
# /etc/club/config.yaml

Configuration Loading Order

When the server starts, configuration is loaded in this sequence:

  1. If CONFIG is set, load the YAML file it points to
  2. Otherwise, check for /etc/club/config.yaml (skipped silently if absent)
  3. Apply environment variables as overrides
  4. Validate required fields and backend-specific requirements
  5. Fail fast with a clear error message if misconfigured

Keeping Secrets Out of the Config File

The config file does not perform any placeholder substitution: values are read literally as written. To keep secrets (the JWT secret, database passwords, S3 keys) out of a committed config file, leave those keys out of the YAML entirely and supply them as environment variables instead. Environment variables always override the config file, so a partial YAML file plus a handful of secret environment variables is the recommended pattern.

# config.yaml — safe to commit (no secrets)
server_url: "https://packages.example.com"
db:
backend: postgres
blob:
backend: s3
s3:
bucket: "club-packages"
region: "us-east-1"
Terminal window
# Secrets supplied via the environment, never written to the file
JWT_SECRET=$(openssl rand -hex 32)
POSTGRES_URL=postgres://club:secret@db.example.com:5432/club
S3_ACCESS_KEY=AKIA...
S3_SECRET_KEY=wJalrXUtnFEMI...

Key shape

The loader expects flat snake_case keys at the top level of the file. Every key in the table below is a top-level key.

db_backend: postgres
sqlite_path: /data/db/club.db
blob_backend: s3
s3_bucket: club-packages

Full Annotated Example

# =============================================================================
# club configuration file
# =============================================================================
# Values are read literally — there is no placeholder substitution.
# Environment variables always override values in this file, so leave
# secrets out of the file and supply them via the environment instead.
# =============================================================================
# ---------------------------------------------------------------------------
# Server
# ---------------------------------------------------------------------------
# Public URL of the club server (required).
# Must include scheme (https://). No trailing slash.
server_url: "https://packages.example.com"
# IP address to bind the HTTP server to. Default: 0.0.0.0
host: "0.0.0.0"
# Port the server listens on inside the container. Default: 8080
# (Named "port" here; the equivalent env var is LISTEN_PORT, intentionally
# distinct from docker-compose's host-side PORT convention.)
port: 8080
# Log verbosity: debug, info, warning, error. Default: info
log_level: info
# ---------------------------------------------------------------------------
# Authentication & sessions
# ---------------------------------------------------------------------------
# JWT signing secret (required). Minimum 32 characters.
# Generate with: openssl rand -hex 32. Prefer supplying this via the
# JWT_SECRET environment variable instead of writing it here.
jwt_secret: "your-32-plus-character-secret-here"
# Web session TTL (hours). Default: 1
session_ttl_hours: 1
# Default API token expiry (days). Default: 365
token_expiry_days: 365
# bcrypt cost factor (10-14). Default: 12
bcrypt_cost: 12
# Enable the /signup page and public signup endpoint. Default: false
signup_enabled: false
# Trust X-Forwarded-Proto / X-Forwarded-For. Enable only behind a proxy
# that strips client-supplied copies. Default: false
trust_proxy: false
# Extra origins permitted on public, unauthenticated state-changing
# endpoints (login, signup, setup). SERVER_URL is trusted implicitly.
# allowed_origins: "https://www.packages.example.com,https://packages.internal.example.com"
# ---------------------------------------------------------------------------
# Database (Metadata Store)
# ---------------------------------------------------------------------------
# sqlite | postgres. Default: sqlite
db_backend: sqlite
# SQLite file path. Default: /data/db/club.db
sqlite_path: /data/db/club.db
# Required when db_backend is postgres. Prefer the POSTGRES_URL
# environment variable so the password stays out of this file.
# postgres_url: "postgres://club:secret@db.example.com:5432/club"
# ---------------------------------------------------------------------------
# Blob Storage
# ---------------------------------------------------------------------------
# filesystem | s3 | gcs. Default: filesystem
blob_backend: filesystem
# Filesystem root. Default: /data/blobs
# Stores package tarballs, per-version screenshots, and (when
# dartdoc_backend is `blob`) per-package dartdoc indexed blobs.
blob_path: /data/blobs
# S3 configuration (uncomment when blob_backend is s3).
# Works for AWS S3, Cloudflare R2, MinIO, DigitalOcean Spaces, Backblaze B2,
# and GCS via its S3 interop endpoint.
# Prefer supplying s3_access_key / s3_secret_key via the S3_ACCESS_KEY and
# S3_SECRET_KEY environment variables instead of writing them here.
# s3_endpoint: "https://s3.amazonaws.com" # optional for AWS, required otherwise
# s3_bucket: "club-packages"
# s3_region: "us-east-1" # optional; defaults to us-east-1
# s3_access_key: "AKIA..."
# s3_secret_key: "wJalrXUtnFEMI..."
# GCS (native) configuration (uncomment when blob_backend is gcs).
# Auth priority: gcs_credentials_file > gcs_credentials_json > Application
# Default Credentials. Under ADC there's no private key in process,
# so downloads proxy through the server instead of redirecting.
# gcs_bucket: "my-project.appspot.com"
# gcs_credentials_file: "/secrets/sa.json"
# gcs_credentials_json: '{"type":"service_account",...}'
# ---------------------------------------------------------------------------
# Search
# ---------------------------------------------------------------------------
# sqlite (FTS5) | meilisearch. Default: sqlite
search_backend: sqlite
# Meilisearch configuration (uncomment when search_backend is meilisearch).
# Prefer the MEILISEARCH_KEY environment variable for the API key.
# meilisearch_url: "http://meilisearch:7700"
# meilisearch_key: "your-meilisearch-api-key"
# ---------------------------------------------------------------------------
# Upload
# ---------------------------------------------------------------------------
# Temp dir for upload processing. Default: /data/tmp/uploads
temp_dir: /data/tmp/uploads
# Max tarball size in bytes. Default: 104857600 (100 MiB)
max_upload_bytes: 104857600
# ---------------------------------------------------------------------------
# Dartdoc
# ---------------------------------------------------------------------------
# Serve backend for rendered dartdoc HTML.
# - filesystem (default): local tree at dartdoc_path, served by shelf_static.
# Requires a persistent volume at that path.
# - blob: indexed blob persisted via the configured Blob
# Store (works with filesystem/s3/gcs — orthogonal
# to blob.backend). Required for multi-replica or
# ephemeral container deployments.
# See https://docs.club.birju.dev/reference/dartdoc-serving/ for the full spec.
dartdoc_backend: filesystem
# Local filesystem root used when dartdoc_backend is filesystem.
# Ignored in blob mode. Default: /data/cache/dartdoc
dartdoc_path: /data/cache/dartdoc
# In-process LRU cap for the blob serve path, in MiB. Only used when
# dartdoc_backend is blob. Default: 64
dartdoc_cache_max_memory_mb: 64

YAML Key to Environment Variable Mapping

Each flat top-level YAML key maps to its corresponding environment variable:

YAML KeyEnvironment Variable
server_urlSERVER_URL
hostHOST
portLISTEN_PORT
log_levelLOG_LEVEL
jwt_secretJWT_SECRET
session_ttl_hoursSESSION_TTL_HOURS
token_expiry_daysTOKEN_EXPIRY_DAYS
bcrypt_costBCRYPT_COST
signup_enabledSIGNUP_ENABLED
trust_proxyTRUST_PROXY
allowed_originsALLOWED_ORIGINS
db_backendDB_BACKEND
sqlite_pathSQLITE_PATH
postgres_urlPOSTGRES_URL
blob_backendBLOB_BACKEND
blob_pathBLOB_PATH
s3_endpointS3_ENDPOINT
s3_bucketS3_BUCKET
s3_regionS3_REGION
s3_access_keyS3_ACCESS_KEY
s3_secret_keyS3_SECRET_KEY
gcs_bucketGCS_BUCKET
gcs_credentials_fileGCS_CREDENTIALS_FILE
gcs_credentials_jsonGCS_CREDENTIALS_JSON
search_backendSEARCH_BACKEND
meilisearch_urlMEILISEARCH_URL
meilisearch_keyMEILISEARCH_KEY
temp_dirTEMP_DIR
max_upload_bytesMAX_UPLOAD_BYTES
dartdoc_backendDARTDOC_BACKEND
dartdoc_pathDARTDOC_PATH
dartdoc_cache_max_memory_mbDARTDOC_CACHE_MAX_MEMORY_MB
static_files_pathSTATIC_FILES_PATH

Example: PostgreSQL + S3 Config

A production config using PostgreSQL for metadata and S3 for blob storage. Secrets (JWT_SECRET, POSTGRES_URL, S3_ACCESS_KEY, S3_SECRET_KEY) are supplied as environment variables and omitted from the file:

server_url: "https://packages.example.com"
log_level: info
trust_proxy: true
token_expiry_days: 90
db_backend: postgres
blob_backend: s3
s3_bucket: "club-packages"
s3_region: "us-east-1"
search_backend: sqlite

Example: MinIO Config

Using MinIO as an S3-compatible blob store. Secrets (JWT_SECRET, POSTGRES_URL, S3_ACCESS_KEY, S3_SECRET_KEY, MEILISEARCH_KEY) are supplied as environment variables:

server_url: "https://packages.internal.example.com"
trust_proxy: true
db_backend: postgres
blob_backend: s3
s3_endpoint: "http://minio:9000"
s3_bucket: "club-packages"
s3_region: "us-east-1"
search_backend: meilisearch
meilisearch_url: "http://meilisearch:7700"

Example: GCS (native) on GKE

Using the native GCS backend with Application Default Credentials from a workload-identity service account:

server_url: "https://packages.example.com"
trust_proxy: true
db_backend: postgres
blob_backend: gcs
gcs_bucket: "my-project.appspot.com"
# gcs_credentials_file / gcs_credentials_json both unset — use ADC.
# Downloads proxy through the server (no V4 signing without a private key).

JWT_SECRET and POSTGRES_URL are supplied as environment variables.