Deploy the Cronium application stack with Docker Compose. This guide covers the required services, recommended configuration, and the environment variables needed to run Cronium in your own infrastructure.
A production Cronium deployment consists of the Next.js control plane (cronium-app), the scheduling worker (cronium-worker — dispatches due schedules, runs tool-action and HTTP jobs, and recovers stuck work), the secure job orchestrator (cronium-orchestrator), the runtime API used by containerised scripts, and supporting services (PostgreSQL for persistence and Valkey/Redis for caching). Docker Compose offers a simple way to run these services together.
Verify the following before launching the stack.
AUTH_SECRET,ENCRYPTION_KEY, a CRONIUM_ORCHESTRATOR_KEY that the orchestrator presents to the app, and a SOCKET_BROADCAST_KEY for app/worker→socket-server broadcastsThe fastest way to get running is the installer script. It checks your Docker setup, downloads the Compose file, generates every secret into a .env (chmod 600), starts the stack, and waits until Cronium is healthy:
curl -fsSL https://raw.githubusercontent.com/addison-moore/cronium/main/install.sh | bashIt asks one question — the URL where Cronium will be reached (defaults to http://localhost:3000; pass --url https://cronium.example.com for non-interactive installs). Re-running the installer upgrades in place without touching your secrets, and --uninstall stops the stack while keeping your data. Prefer to see every step? The manual path below does exactly what the installer automates.
You only need a few long, random strings and a minimal .env file to get started.
curl -O https://raw.githubusercontent.com/addison-moore/cronium/main/docker-compose.example.ymlopenssl is not installed, use a password manager to generate random strings of the same length:# macOS / Linux / WSL
openssl rand -hex 32 # AUTH_SECRET, ENCRYPTION_KEY, JWT_SECRET
openssl rand -base64 32 # CRONIUM_ORCHESTRATOR_KEY
openssl rand -base64 32 # SOCKET_BROADCAST_KEY
openssl rand -hex 16 # POSTGRES_PASSWORD
openssl rand -hex 24 # VALKEY_PASSWORDENCRYPTION_KEY must be exactly 64 hex characters (openssl rand -hex 32 produces that)..env file next to the Compose file with the secrets you generated and your public URL (use http://localhost:3000 if you are testing locally). The Compose file reads everything from .env — you never need to edit the YAML — and it refuses to start with a clear error if a required value is missing:AUTH_URL=https://cronium.example.com
PUBLIC_APP_URL=https://cronium.example.com
SOCKET_ALLOWED_ORIGINS=https://cronium.example.com
AUTH_SECRET=<paste value>
ENCRYPTION_KEY=<paste value>
CRONIUM_ORCHESTRATOR_KEY=<paste value>
SOCKET_BROADCAST_KEY=<paste value>
JWT_SECRET=<paste value>
POSTGRES_PASSWORD=<paste value>
VALKEY_PASSWORD=<paste value>The Compose example below assumes the following images are available locally or in a registry you can pull from:
cronium-app – Next.js control plane UI & API (the same image also runs the cronium-worker scheduling service with a different entrypoint)cronium-orchestrator – Go daemon that executes jobscronium-runtime – Runtime API for container executions (optional if you only use SSH targets)docker build -t cronium-app:latest -f apps/cronium-app/Dockerfile .
docker build -t cronium-orchestrator:latest -f apps/orchestrator/Dockerfile .
docker build -t cronium-runtime:latest apps/runtime/cronium-runtimeThe stack is defined by docker-compose.example.yml in the repository — the same file the installer and the curl command above download. It runs PostgreSQL, Valkey, the Cronium app, the orchestrator, and the runtime service on a shared network with healthchecks, restart policies, and persistent volumes wired in. The file is commented and is the single source of truth; this page deliberately does not duplicate it. What you need to know:
.env — a standard deployment never edits the YAML. Required secrets use the ${VAR:?error} form, so a missing value stops docker compose up with the exact generation command in the error message:# excerpt — how configuration flows from .env
cronium-app:
image: ghcr.io/addison-moore/cronium-app:${CRONIUM_IMAGE_TAG:-latest}
environment:
AUTH_URL: ${AUTH_URL:-http://localhost:3000}
SOCKET_ALLOWED_ORIGINS: ${SOCKET_ALLOWED_ORIGINS:-}
AUTH_SECRET: ${AUTH_SECRET:?generate with openssl rand -hex 32}
DATABASE_URL: postgres://${POSTGRES_USER:-cronium}:${POSTGRES_PASSWORD:?generate with openssl rand -hex 16}@postgres:5432/${POSTGRES_DB:-cronium}3000 is published for the web app; WebSocket port 5002 is bound only to host loopback for a reverse proxy. The orchestrator and runtime are internal-only and monitored via their container healthchecks (docker compose ps)./api/socketio) through your TLS reverse proxy to 127.0.0.1:5002. A proxy container can use cronium-app:5002 on the Cronium network. Never proxy the service-only /broadcast/* routes; they also requireAuthorization: Bearer $SOCKET_BROADCAST_KEY..env: SMTP_HOST/SMTP_PORT/SMTP_USER/SMTP_PASSWORD/SMTP_FROM_EMAIL for outbound email, CRONIUM_IMAGE_TAG to pin a release, and APP_PORT to move the public port orSOCKET_PORT to move the loopback proxy target. UseSOCKET_ALLOWED_ORIGINS only when trusted browser clients come from more than the canonicalPUBLIC_APP_URL/AUTH_URL origins.Live-log and terminal clients automatically request a short-lived authenticated ticket before connecting. The server accepts each ticket once through shared Valkey, checks that the account is still active, and also requires console permission for terminal access. Authorization changes revoke live connections. Exact origin allowlisting adds a separate browser boundary.
The bundled Valkey service uses noeviction because consumed ticket markers are security state. At its configured memory limit, new security writes fail closed instead of evicting replay protection.
There are no default credentials: your first browser visit shows a one-time setup page where you create the admin account. For headless installs (CI, infrastructure-as-code), set AUTO_SEED_ADMIN=true plus ADMIN_USERNAME, ADMIN_EMAIL, and ADMIN_PASSWORD in .env to seed the admin on first boot instead.
The Cronium app automatically runs database migrations on start. You can disable this behaviour by setting AUTO_MIGRATE=false if you prefer to manage the schema yourself.
Leave the /var/run/docker.sock mount in place if you plan to run container jobs—the orchestrator needs access to the host Docker daemon. Remove it only when you exclusively use an externally isolated SSH execution topology.
The tables below summarise the key variables per service. Values marked as required must be set for a production deployment.
| Variable | Required | Description |
|---|---|---|
PUBLIC_APP_URL | Yes | Public base URL of the Next.js application (used by links, auth callbacks, emails). |
NEXT_PUBLIC_APP_URL | Optional | Mirror of PUBLIC_APP_URL exposed to the browser; set when serving the UI behind a proxy. |
AUTH_URL | Yes | URL that NextAuth should consider as the canonical origin for authentication. |
SOCKET_ALLOWED_ORIGINS | Optional | Comma-separated exact browser origins permitted to open live-log and terminal sockets. Defaults to PUBLIC_APP_URL and AUTH_URL; include the scheme and any non-default port. Wildcards and URL paths are not supported. |
AUTH_SECRET | Yes | Random 32-character string used by NextAuth to sign session cookies (e.g. openssl rand -hex 32). |
DATABASE_URL | Yes | PostgreSQL connection string in the format postgres://user:pass@host:5432/db; see the Compose example for defaults. |
ENCRYPTION_KEY | Yes | 32-byte key (Base64 or hex) used to encrypt stored secrets; generate with openssl rand -hex 32 or a password manager. |
CRONIUM_ORCHESTRATOR_KEY | Yes | Orchestrator service-identity credential — the app verifies it on the orchestrator-facing routes (claim, heartbeat, health/metrics); generate with openssl rand -base64 32. Never expose it to browser clients. |
SOCKET_BROADCAST_KEY | Yes | Authenticates internal broadcasts from the app/worker to the socket server; generate with openssl rand -base64 32. Never expose it to browser clients. |
JWT_SECRET | Yes | Token used for signing internal service-auth tokens and WebSocket payloads; reuse this for the runtime service (e.g. openssl rand -hex 32). |
AUTO_MIGRATE | Optional | Defaults to true. Leave enabled unless you plan to run migrations yourself; set to falseif you need to manage schema updates manually. |
ORCHESTRATOR_URL | Optional | Base URL for the orchestrator health endpoints. Defaults to http://cronium-orchestrator:8080 inside Docker. |
VALKEY_URL | Optional | Connection string for Valkey (use thevalkey:// scheme). Falls back to in-memory caching if omitted. |
SMTP_* | Optional | Configure SMTP credentials when enabling email notifications. |
| Variable | Required | Description |
|---|---|---|
CRONIUM_API_ENDPOINT | Yes | Base URL of the Cronium app (internal service-to-service address). |
CRONIUM_API_TOKEN | Yes | Must match CRONIUM_ORCHESTRATOR_KEY so the orchestrator can authenticate with the app. |
CRONIUM_ORCHESTRATOR_ID | Yes | Unique identifier for this orchestrator instance (used for logging and job claims). |
CRONIUM_CONTAINER_RUNTIME_JWT_SECRET | Yes* | Shared secret between the orchestrator and the runtime API for container job authentication. Required if you enable the container executor. |
CRONIUM_CONTAINER_RUNTIME_BACKEND_URL | Optional | Internal URL the runtime API should use to call back into the Cronium app (defaults to http://cronium-app:3000). |
CRONIUM_CONTAINER_RUNTIME_VALKEY_URL | Optional | Valkey connection string for coordinating container job state (supports the valkey:// scheme). |
CRONIUM_SSH_EXECUTION_ISOLATION_MODE | Optional | Defaults to disabled. Set tooperator-enforced only after a trusted external launcher guarantees a separate UID or isolated container for every mutually untrusted remote job. A normal shared SSH account is not sufficient. |
LOG_LEVEL | Optional | Overrides orchestrator logging verbosity. Defaults toinfo. |
*Required when using container-based execution. An SSH-only environment must first satisfy and explicitly enable the external per-job isolation requirement.
| Variable | Required | Description |
|---|---|---|
RUNTIME_BACKEND_URL | Yes | Internal URL the runtime service should use to reach the Cronium app (typically http://cronium-app:3000). |
| (no backend token) | — | The runtime no longer holds a shared app credential. It authenticates each callback with a per-job capability token extracted from its execution JWT, so there is no RUNTIME_BACKEND_TOKEN to configure. |
RUNTIME_VALKEY_URL | Yes | Valkey connection string used for caching workflow state. |
RUNTIME_JWT_SECRET | Yes | Same value as CRONIUM_CONTAINER_RUNTIME_JWT_SECRET; used to validate execution tokens. |
RUNTIME_PORT | Optional | Port for the runtime API (defaults to 8081). |
RUNTIME_LOG_LEVEL | Optional | Sets runtime logging verbosity. Defaults to info. |
| Service | Variable | Description |
|---|---|---|
| PostgreSQL | POSTGRES_USER,POSTGRES_PASSWORD,POSTGRES_DB | Standard PostgreSQL variables. Ensure they align with theDATABASE_URL provided to the app. |
| Valkey | - | No special variables required. Persistent volumes are recommended for durability. |
.env and the sample Compose file ready to use.cronium-app, cronium-orchestrator, and cronium-runtime images from GHCR, or build them locally.docker compose up -d and wait for all containers to report healthy states.AUTO_MIGRATE=false to control schema changes yourself, apply pending migrations from a clone of the repository with pnpm install followed by DATABASE_URL=... pnpm --filter @cronium/app db:migrate.apps/orchestrator/configs/cronium-orchestrator.yaml from the repo) if you need advanced tuning for metrics, SSH executors, or polling cadence.Confirm each service is reachable before inviting teammates.
https://cronium.example.com (or your configured domain) – you should see the login screen.curl http://localhost:3000/api/health from the host running Docker.docker compose ps and confirm every service reports healthy — both services are internal-only and are probed by their container healthchecks.docker compose logs cronium-app while triggering a job to verify live log streaming through the proxied /api/socketio path.https://cronium.example.com and create the first admin account.Keep your deployment healthy and secure.
Reach out to the Cronium community or open a discussion in the repository for assistance with your self-hosted deployment.