Repository navigation
Configuration
Two separate mechanisms, fully disjoint:
-
Env vars (table below): read directly at startup. They own all deploy/infra knobs (
server,webRtc,limits). None have aconfig.inicounterpart. -
config.ini(next todocker-compose.yaml, mounted read-only): rate limiters and login lockout only. The server reads it and never writes it. Missing keys use the defaults from code, so new limiters need no migration.
No key lives in both. Set ports, debug, WebRTC and the user cap via env; tune the limiters in config.ini.
| Variable | Default | Range / values | What it does |
|---|---|---|---|
CAESAR_IMAGE_TAG |
latest |
latest, x.y.z, edge, or a local tag |
Compose only: which ghcr.io/h8d13/caesar image the caesar service runs. Pin a release with x.y.z, follow master with edge, or run your own build (see Home). |
CAESAR_SITE |
localhost |
single hostname (:port for prod-dev only) |
Public host the instance answers on. Read by both containers: Caddy site address, and the server's WebAuthn RP ID + expected origin. Single host only, the server throws at boot on a space/comma value (see Caddy vs app). |
CAESAR_PORT |
4991 |
positive integer | Internal HTTP port the server listens on (behind Caddy). docker-compose.yaml feeds the same value to Caddy's --to caesar:<port>, so both sides move together. |
CAESAR_MAX_REQUEST_BODY_BYTES |
65536 (64 KiB) |
positive integer (bytes) | Hard cap on buffered request bodies (/login, /login-2fa). Caps memory an unauthenticated peer can make the server hold before auth or rate limiting runs. Uploads have their own path and limits. |
CAESAR_DEBUG |
false in prod, true in dev |
boolean | Verbose server logs. |
CAESAR_MAX_USERS |
0 (unlimited) |
non-negative integer | Cap on active (non-deleted) registered users. Bootstrap (first signup) always bypasses. New invites refused at cap; signup refused at cap. UI shows X / Y users on the Invites screen when set. |
CAESAR_WEBRTC_WORKERS |
3 in docker-compose.yaml (1 bare server) |
non-negative integer | Mediasoup worker processes. Each worker binds CAESAR_WEBRTC_PORT + i. Must fit the published port range in docker-compose.yaml (40000-40002). Primary CPU/concurrency lever. |
CAESAR_WEBRTC_PORT |
40000 |
positive integer | Base port for mediasoup workers. Worker N binds base+N. |
CAESAR_WEBRTC_ANNOUNCED_ADDRESS |
empty (auto) | IP literal | LAN/public IP advertised in ICE candidates. Set only when clients can't reach the host via its default address (e.g. testing from another LAN device against prod-dev). |
CAESAR_WEBRTC_MAX_BITRATE |
30000000 (30 Mbps) |
positive integer (bps) | Per-user transport cap (sum of inbound + outbound across that user's mic/cam/screenshare). Applied via setMaxIncomingBitrate / setMaxOutgoingBitrate. |
CAESAR_WEBRTC_LOG_LEVEL |
warn in prod, debug in dev |
debug, warn, error, none
|
Mediasoup worker log verbosity. |
CAESAR_WEBAUTHN_RPNAME |
Caesar |
string | 2FA hardware keys. Display name shown in browser 2FA prompts. |
CAESAR_TRUSTED_PROXY_HOPS |
1 |
non-negative integer | Reverse proxies in front of the app. Client IP (for rate limiting + audit) is the Nth X-Forwarded-For from the right. 1 matches the bundled Caddy; 0 = exposed directly (trust socket peer only). |
CAESAR_TRUSTED_CLIENT_IP_HEADER |
empty | header name | Single-value real-IP header to trust for CDN setups (e.g. cf-connecting-ip behind Cloudflare). Overrides CAESAR_TRUSTED_PROXY_HOPS. Empty = don't trust such headers (spoofable). |
Avoid touching, unless you know what you doing
| Variable | Set by | Purpose |
|---|---|---|
CAESAR_ENV |
Dockerfile.prod (= production) |
Marks runtime as production. Dev path leaves it unset. |
CAESAR_BUILD_VERSION |
Docker --build-arg: CI image (tag or short hash). Unset: git short hash, or dev without git (local image builds) |
Burned into the bundle at build time. Surfaced in UI. |
XDG_CONFIG_HOME |
Dockerfile.prod (= /home/node/.config) |
Data path parent: everything lives in $XDG_CONFIG_HOME/caesar, the mount point of the data volume. |
The caddy service has no config file and no environment. Compose substitutes
.env / shell values straight into its command line:
command: >-
caddy reverse-proxy
--from ${CAESAR_SITE:-localhost} # site address
--to caesar:${CAESAR_PORT:-4991} # upstreamcompose.prod-dev.yaml adds --internal-certs (self-signed) with fixed
localhost:8443 / caesar:4991. The app only sees what sits in the
caesar service's environment: block. With a
Custom Caddyfile the site address is whatever you
write there, keep it equal to CAESAR_SITE.
CAESAR_SITE reaches both, and means something different on each side:
| side | what it takes from CAESAR_SITE
|
|---|---|
| Caddy | site address: which Host matches, which name ACME requests a cert for, and (if you write host:port) which port to listen on |
| server |
RP_ID = hostname with the port stripped; EXPECTED_ORIGIN = scheme + full value. Scheme is http only for bare localhost, https otherwise |
Two consequences worth knowing before you edit either file:
-
One host per instance. Caddy happily takes
example.com, www.example.comas one site block, and the app would then derive a nonsense RP ID from it: hardware 2FA and passkeys stop verifying while password login keeps working. The server rejects such a value at boot instead. Aliases get their ownredirblock, see Home. -
Ports are Caddy's job. The prod caddy service publishes 80/443 only, so
CAESAR_SITE=example.com:9000just makes Caddy listen where nothing is published.localhost:8443is the one supported ported form (prod-dev).
Nothing else in the server checks request origin: it is same-origin by design
(CSP + Cross-Origin-Opener-Policy / -Resource-Policy: same-origin, no CORS
allowlist). CAESAR_SITE is not a CORS setting.
-
Prod (single instance):
.envnext todocker-compose.yaml, start from .env.example. Every variable in it reaches the server (env_file), and compose also uses it forCAESAR_SITE/CAESAR_PORTon the Caddy side. Apply withdocker compose up -d. -
Prod (multi-instance): per-service
environment:block indocker-compose.override.yaml(see Multi-instances). -
Dev (
compose.dev.yaml):.env.devat the repo root (gitignored), same variables.
No env overrides. The shipped config.ini lists every key commented out at its default. Uncomment and change what you need, then:
docker compose restart caesarSections: [rateLimiters.*] (each limiter has maxRequests + windowMs) and
[loginLockout] (maxFailures, windowMs, baseLockMs, maxLockMs).
Defaults live in apps/server/src/helpers/ini-config.ts; a test keeps the
shipped file in sync with them.
A typo'd key or bad value stops the boot and names it (docker logs caesar):
[caesar] fatal during boot: Error: invalid config.ini:
✖ Unrecognized key: "loginn"
→ at rateLimiters
config.ini must exist when the container is created: a missing bind-mount
source gets created as a directory, and the server refuses to boot saying so.
Remove that directory, download the file, docker compose up -d.
In dev (compose.dev.yaml): same format, optional, at
apps/server/data/config.ini in the checkout.
Made with 🖤 CHANGELOG