Configuration reference
Every environment variable of every Sluby service, with its default and what it does.
This page lists every setting a Sluby server reads: the backend, the web proxy, the Studio build, the production override with Caddy, and the image builds. You set them in the .env file in the folder where you cloned Sluby. Docker Compose reads that file and passes each value to the service that needs it. Two exceptions: STUDIO_API_PROXY_TARGET goes in frontend/.env, and the rows that say Compose sets the value (DATABASE_URL, REDIS_URL, PORT, HOST, UPLOAD_DIR, TRANSCODE_OUTPUT_DIR) ignore .env in the Compose stack.
How settings are read
- Docker Compose reads
.envfrom the project folder and substitutes the values intodocker-compose.yml(anddocker-compose.prod.ymlwhen you use it). - An empty value counts as unset.
SIA_DATA_SHARDS=behaves exactly like leaving the line out, so the backend takes its own default. - On/off settings accept
true,false,1or0. The backend refuses any other value, such asyesorTRUE, and does not start. - The backend reads its settings once, at startup. After you change one, run
docker compose up -d(with the same-ffiles you started with) so Compose recreates the containers whose settings changed. - The
VITE_settings are compiled into the Studio when the web image is built. After you change one, rebuild the web image:docker compose up -d --build web(with the same-ffiles you started with). - When the backend finds an invalid value, it prints
Refusing to start, the configuration is not valid:followed by one line per problem, and exits. See Troubleshooting.
The Default column shows the value the shipped Docker Compose stack runs with. Where the backend's own default differs (it applies when you run the backend from source), the description says so.
Backend: server and logging
Read by the backend service.
| Variable | Default | Description |
|---|---|---|
NODE_ENV | development | development, production or test. production turns on the startup checks (a strong SESSION_SECRET is required, AUTH_DISABLED=true is refused, BOOTSTRAP_API_KEY must be a generated key) and switches the logs to JSON. Outside production the logs are colored text. docker-compose.prod.yml sets it to production. |
LOG_LEVEL | info | Log level of the backend and of the Sia storage SDK it uses: fatal, error, warn, info, debug, trace or silent. |
EXPOSE_ERROR_STACKS | false | true adds the internal error message and stack trace to API error responses. They contain file paths and internals, so use it only for debugging. The backend logs a warning at startup while it is on. |
PUBLIC_URL | http://localhost:8080 | The address people open in the browser, without a trailing slash. The backend builds upload URLs, the GitHub callback URL, invite and reset links, signed playback URLs and watch page links from it. The session cookie is marked Secure only when it starts with https://. docker-compose.prod.yml sets it to https:// plus DOMAIN. Backend default: http://localhost:3000. |
PORT | 3000 | Port the backend listens on. Compose sets it to 3000, the port the web proxy connects to (backend:3000). Backend default: 3000. |
HOST | 0.0.0.0 | Address the backend listens on. Compose sets it to 0.0.0.0. Backend default: 0.0.0.0. |
Backend: database and queue
| Variable | Default | Description |
|---|---|---|
POSTGRES_PASSWORD | sluby | Password of the sluby database user. Compose passes it to the postgres service and puts it, unencoded, into DATABASE_URL, so use letters and digits only: generate one with openssl rand -hex 32. A value with / makes the backend refuse to start with DATABASE_URL: Invalid URL. docker-compose.prod.yml requires it. The postgres image applies it only when it creates the database on an empty volume, so set it before the first start. |
DATABASE_URL | postgresql://sluby:<POSTGRES_PASSWORD>@postgres:5432/sluby | PostgreSQL connection string. Compose sets it for the backend container, so you do not set it in the Compose stack. Backend default: postgresql://sluby:sluby@localhost:5432/sluby. |
REDIS_URL | redis://redis:6379 | Redis connection string, used for the job queues. Compose sets it for the backend container. Backend default: redis://localhost:6379. |
To run the backend from source against the Compose databases, docker-compose.dev.yml publishes PostgreSQL on 127.0.0.1:5433 and Redis on 127.0.0.1:6380. Then set these two lines in .env:
DATABASE_URL=postgresql://sluby:sluby@localhost:5433/sluby
REDIS_URL=redis://localhost:6380Backend: Sia storage
How to get these values: Connect Sia storage.
| Variable | Default | Description |
|---|---|---|
SIA_INDEXER_URL | https://sia.storage | Base URL of the indexer's application API, without a trailing slash. The backend connects to this indexer to store and read media on Sia. |
SIA_NETWORK | mainnet | The Sia network the indexer runs on: mainnet, zen, testnet or anagami. Used for labels in the Studio and to pick the explorer for host and contract links (zen and testnet link to Zen Siascan, every other value to Siascan). |
SIA_APP_ID | empty | The 32-byte app ID (hex) that connect-indexer.mjs prints. Keep it the same across restarts. docker-compose.prod.yml requires it. |
SIA_APP_KEY | empty | The 32-byte app key (hex) that connect-indexer.mjs prints. The backend signs every indexer request with it. It can read and delete everything this app stores, so keep it secret. Without it the backend starts but uploads fail, and it logs a warning. docker-compose.prod.yml requires it. |
SIA_DATA_SHARDS | unset | Data shards per slab. Leave both shard settings unset for the SDK default of 10 data and 20 parity shards (30 hosts per slab). Set both, and only to fit an indexer with a smaller host pool. The SDK and the indexer reject ratios whose recovery probability is under 99.99% or whose redundancy is outside 1.5x to 4x. When only one of the two is set, uploads use the SDK default. |
SIA_PARITY_SHARDS | unset | Parity shards per slab. See SIA_DATA_SHARDS. |
SIA_ADMIN_URL | http://127.0.0.1:9980 | Admin API of a self-hosted indexd, without a trailing slash. Used only when SIA_ADMIN_PASSWORD is set, to add contract IDs and the wallet address to the storage breakdown. |
SIA_ADMIN_PASSWORD | empty | HTTP Basic password of that admin API (the indexd adminAPI.password). Empty turns the admin lookups off. |
Backend: sign-in and sessions
How to use these: Set up sign-in.
| Variable | Default | Description |
|---|---|---|
SESSION_SECRET | unset | Secret that signs session cookies and private playback URLs. The setup code for the first admin is also derived from it. Outside production, when it is unset, the backend generates one on its first start and keeps it in its database, so restarts sign nobody out. In production the backend refuses to start unless it is set, at least 32 characters long, and not the old public development value. Generate one with openssl rand -base64 32. Changing it signs everyone out and makes signed links already handed out stop working. docker-compose.prod.yml requires it. |
AUTH_DISABLED | false | true turns sign-in off: every request acts as one local admin account. The API then sends no CORS headers to origins that are not listed in CORS_ORIGIN. Production refuses to start with it on, and docker-compose.prod.yml forces it to false. |
GITHUB_CLIENT_ID | unset | Client ID of a GitHub OAuth app. GitHub sign-in is offered when both GitHub settings are set. |
GITHUB_CLIENT_SECRET | unset | Client secret of that GitHub OAuth app. |
GITHUB_ALLOWED_USERS | empty | Setting from versions before members and roles. The first start after the upgrade removes the GitHub accounts it refused. After that the backend ignores it and logs a warning at every start while it is set. Remove it after the upgrade. |
OPERATOR_USERS | empty | Setting from versions before members and roles, as comma-separated <provider>:<entry> values such as github:octocat. The first start after the upgrade makes the listed accounts admins. After that the backend ignores it and logs a warning at every start while it is set. Remove it after the upgrade. |
BOOTSTRAP_API_KEY | unset | An API key with every scope that the backend creates before it takes requests. It is created once: after you delete or rotate it, it is never created again, and the backend logs a warning while the old value is still set. In production it must look like a generated key (sluby_ and at least 32 more letters, digits, - or _). Generate one with node -e "console.log('sluby_' + require('crypto').randomBytes(32).toString('base64url'))". |
Backend: API access and limits
| Variable | Default | Description |
|---|---|---|
CORS_ORIGIN | empty | Comma-separated browser origins (scheme://host[:port], no path, no trailing slash) whose pages can call the API with the Studio session cookie. Every other origin can still call the API with an API key, without cookies, unless AUTH_DISABLED is on. docker-compose.prod.yml sets it to https:// plus DOMAIN. |
TRUST_PROXY | loopback, linklocal, uniquelocal | Which proxy hops the backend trusts for the client address in X-Forwarded-For: a hop count, true, false, or comma-separated addresses, CIDR ranges and the names loopback, linklocal and uniquelocal. The default trusts private networks, which covers the web proxy alone and Caddy in front of it. |
API_IP_RATE_LIMIT | 600 | Requests per minute one client address can make to /api/v1 without a valid API key or session (sign-in routes, the API spec, failed authentication). |
API_KEY_RATE_LIMIT_MAX | 10000 | The highest per-minute rate limit an API key can have. Creating a key that asks for more is refused, and older keys are held to this value. |
API_KEYS_PER_ACCOUNT_MAX | 100 | How many API keys can be active at once. Revoked and expired keys do not count. Creating a key beyond this number is refused. |
METRICS_TOKEN | unset | When set, GET /metrics requires Authorization: Bearer <token>. Unset, it answers anyone who reaches the backend port. |
WEBHOOK_ALLOWED_NETWORKS | empty | Comma-separated CIDRs or addresses of private or otherwise special networks that webhook receivers can be on, such as 10.0.5.0/24,fd00:5::/64. Empty: webhooks reach public addresses only. |
Backend: uploads, transcoding and delivery
| Variable | Default | Description |
|---|---|---|
MAX_UPLOAD_SIZE | 10737418240 | Largest upload in bytes (10 GiB). |
TRANSCODE_MAX_RENDITION | 1080p | Tallest rendition the transcoder makes: 2160p, 1440p, 1080p, 720p, 540p or 360p. 1440p and 2160p add renditions above 1080p for sources that tall. A lower value drops renditions. The full ladder is in Upload pipeline. |
CACHE_MAX_SIZE_MB | 256 | Size of the backend's in-memory object cache in megabytes. It holds whole objects of up to 8 MB that are read without a Range header (manifests, playlists, thumbnails) and counts against the backend container's memory. Larger objects are streamed and never held. |
UPLOAD_DIR | /app/uploads | Directory for incoming uploads. Compose sets it and mounts the uploads volume there. Backend default: ./uploads. |
TRANSCODE_OUTPUT_DIR | /app/transcode-output | Directory for transcoder output. Compose sets it and mounts the transcode-output volume there. Backend default: ./transcode-output. |
Backend: background jobs
What these jobs do: Deletion and reconciliation.
| Variable | Default | Description |
|---|---|---|
RECONCILE_ENABLED | true | Runs the scheduled reconciliation, which compares the database with what the indexer stores. false stops the schedule. Runs you start on demand still work. |
RECONCILE_INTERVAL_MS | 3600000 | Time between scheduled reconciliation runs, in milliseconds (one hour). |
RECONCILE_PAGE_SIZE | 500 | Page size when reconciliation reads the indexer's object list. At most 500, because the indexer answers larger pages with an error. |
DELETION_GC_ENABLED | true | Runs the deletion sweep, which finishes deletions that stopped before every object was unpinned. |
DELETION_GC_INTERVAL_MS | 900000 | Time between deletion sweeps, in milliseconds (15 minutes). |
DELETION_STUCK_THRESHOLD_MS | 600000 | How long ago a video must have been deleted before the sweep treats its unfinished deletion as stuck and starts it again, in milliseconds (10 minutes). |
Web proxy
The web service is nginx. It serves the Studio and passes the API and video delivery to the backend. It checks these values when the container starts and exits with a message such as WEB_DELIVERY_RATE=fast is not valid (expected a rate such as 50r/s or 600r/m) when one is malformed.
| Variable | Default | Description |
|---|---|---|
WEB_PORT | 8080 | Port on 127.0.0.1 where the base stack publishes the web proxy. Set PUBLIC_URL to match. docker-compose.prod.yml publishes no web port; Caddy connects to it instead. |
WEB_DELIVERY_RATE | 100r/s | Request rate per client address on delivery paths (/v1/), as <n>r/s or <n>r/m. |
WEB_DELIVERY_BURST | 400 | Requests a client can make on delivery paths above that rate before nginx answers 429. |
WEB_DELIVERY_CONN | 200 | Open connections per client address on delivery paths. |
WEB_AUTH_RATE | 10r/s | Request rate per client address on sign-in paths (/api/v1/auth/), as <n>r/s or <n>r/m. |
WEB_AUTH_BURST | 50 | Requests a client can make on sign-in paths above that rate before nginx answers 429. |
WEB_AUTH_CONN | 20 | Open connections per client address on sign-in paths. |
Studio build
These are compiled into the Studio's JavaScript when the web image is built, so every visitor's browser receives them. Rebuild the web image after changing them: docker compose up -d --build web (with the same -f files you started with).
| Variable | Default | Description |
|---|---|---|
VITE_SLUBY_SIA_INDEXER_URL | unset | Indexer URL of the viewer identity for streaming from Sia in the browser. Use an https:// URL: the Studio's content security policy (connect-src 'self' https:) blocks plain http:// addresses. The Studio streams from Sia only when all three VITE_SLUBY_SIA_ values are set. See Stream from Sia in the browser. |
VITE_SLUBY_SIA_APP_ID | unset | App ID of the viewer identity that provision-viewer.mjs prints. |
VITE_SLUBY_SIA_APP_KEY | unset | App key of the viewer identity that provision-viewer.mjs prints. It is public once built in. Never put SIA_APP_KEY here. |
VITE_SIA_EXPLORER_URL | unset | Sia explorer for host, contract and address links in the Studio. Unset, the Studio uses https://zen.siascan.com when SIA_NETWORK is zen or testnet, and https://siascan.com otherwise. |
STUDIO_API_PROXY_TARGET | http://localhost:3000 | Only for the Studio's development server (npm run dev in frontend/), read from frontend/.env: where it forwards /api, /v1, /health and /watch. |
Production with Caddy
Read by docker-compose.prod.yml and the caddy service. See Run in production.
| Variable | Default | Description |
|---|---|---|
DOMAIN | none, required | The domain the server answers on, such as sluby.example.com. Caddy gets and renews its TLS certificate. The override also sets PUBLIC_URL and CORS_ORIGIN to https:// plus this value. |
ACME_EMAIL | none, required | Email address Caddy gives the certificate authority when it requests the certificate. |
docker-compose.prod.yml also requires POSTGRES_PASSWORD, SESSION_SECRET, SIA_APP_ID and SIA_APP_KEY, and it sets NODE_ENV=production and AUTH_DISABLED=false whatever .env says.
Backend image build
| Variable | Default | Description |
|---|---|---|
SIA_STORAGE_NATIVE | unset | Path, relative to backend/vendor/, of a custom-built Sia storage native module to put in the backend image instead of the standard one, for example sia-storage-linux-x64-gnu/sia-storage.node. The build fails when the file is missing. backend/vendor/README.md explains how to build one. |
Local indexer stack
Read by docker-compose.sia.yml, which runs indexd, its own PostgreSQL, walletd and a CPU miner for the Zen testnet. It is separate from the Sluby stack.
| Variable | Default | Description |
|---|---|---|
INDEXD_POSTGRES_PASSWORD | none | Password of the indexd user in the indexer's own PostgreSQL. |
WALLETD_API_PASSWORD | 1234 | API password of walletd, which the miner uses. |
MINER_ADDRESS | none | Wallet address the miner pays out to: the indexd wallet address. |