Docker Compose
Compose deployments for development and small production: single MongoDB by default, environment-driven secrets, healthchecks, an external/replica-set MongoDB option, and the upgrade path.
Prerequisites
- Docker 24+ with Compose v2 (
docker compose, no hyphen). - 4 GiB RAM allocated to the engine — MongoDB is unhappy with less.
Development: the repo's compose file
The repository ships a ready compose file at docker/docker-compose.yml that builds the backend from source and starts a single MongoDB next to it:
make docker-up # docker compose up (mongo + backend on :8181)
make docker-down
It mounts configs/config-docker.yaml into the container and sets a deterministic development KEK via environment variables — fine for a laptop, never for anything real.
Small production
A reference compose.yaml using the published images. Adapt paths, the tag, and the secrets before use:
services:
backend:
image: ghcr.io/cloudnativeworks/certautopilot:1.5.47
restart: unless-stopped
command: ["serve", "--mode=all", "--config=/app/config.yaml"]
volumes:
- ./config.yaml:/app/config.yaml:ro
environment:
CERTAUTOPILOT_DATABASE_HOST: mongo
CERTAUTOPILOT_JWT_SECRET: "${CAP_JWT_SECRET:?set in .env}"
CERTAUTOPILOT_ENCRYPTION_ENV_KEK_V1: "${CAP_KEK_V1:?set in .env}"
CERTAUTOPILOT_ENCRYPTION_CURRENT_VERSION: "1"
depends_on:
mongo:
condition: service_healthy
healthcheck:
test: ["CMD", "wget", "-qO-", "http://localhost:8181/healthz"]
interval: 10s
timeout: 5s
retries: 3
start_period: 10s
frontend:
image: jhonbrownn/certautopilot-frontend:1.5.47
restart: unless-stopped
ports:
- "80:80"
depends_on:
- backend
mongo:
image: mongo:7-jammy
restart: unless-stopped
volumes:
- mongodata:/data/db
healthcheck:
test: ["CMD", "mongosh", "--eval", "db.adminCommand('ping')"]
interval: 5s
timeout: 5s
retries: 5
volumes:
mongodata:
The frontend container is an nginx that serves the UI and proxies /api/, /healthz, and the ACME HTTP-01 challenge path to backend:8181 — so exposing port 80 (or your TLS-terminating reverse proxy in front of it) is the only ingress you need. Start with a minimal config.yaml (a copy of the repo's configs/config-docker.yaml is a fine base); every field can also be overridden by a CERTAUTOPILOT_* environment variable.
Generate the secrets
Both secrets are hex/text values passed as environment variables — put them in an .env file next to compose.yaml (never in git):
cat > .env <<EOF
CAP_JWT_SECRET=$(openssl rand -hex 32)
CAP_KEK_V1=$(openssl rand -hex 32)
EOF
chmod 0600 .env
CERTAUTOPILOT_JWT_SECRET— at least 32 characters; production validation refuses to start without a strong value.CERTAUTOPILOT_ENCRYPTION_ENV_KEK_V1— the KEK: 32 random bytes, hex-encoded (64 hex chars), version 1 of the KEK keystore.
Without the KEK, the envelope-encrypted private keys and credentials in MongoDB are unrecoverable. Back the value up to a real secret store (Vault, AWS Secrets Manager, a password manager) before the first start writes encrypted data.
First start
docker compose up -d
docker compose logs -f backend
Then open the UI and complete the first-run setup wizard (initial admin account and organization). Setup closes itself once the instance is initialized.
MongoDB: single node by default, replica set optional
The bundled mongo service is a single node — CertAutoPilot does not require a replica set. Distributed locking uses findAndModify, and the one place that benefits from multi-document transactions (the KEK version swap during rotation) automatically falls back to a safe sequential path on standalone MongoDB.
To use an external or highly-available MongoDB (Atlas, a self-hosted replica set), drop the mongo service and point the backend at it with a full URI — it takes precedence over the host/port fields:
environment:
CERTAUTOPILOT_DATABASE_URI: "mongodb://m0,m1,m2/certautopilot?replicaSet=rs0&w=majority"
mongodb+srv:// URIs, credentials, authSource, and tls=true all pass through unchanged. See High availability for the full multi-node picture.
Upgrade
Pin the image tags, bump both to the new release, pull, and recreate — schema changes are applied automatically at startup (indexes are ensured, one-time backfills run):
sed -i 's/1\.5\.47/1.5.48/g' compose.yaml
docker compose pull
docker compose up -d
Environment reference
Every config field maps to an environment variable with the CERTAUTOPILOT_ prefix (dots become underscores). The ones a compose deployment typically sets:
| Variable | Required | Notes |
|---|---|---|
CERTAUTOPILOT_DATABASE_HOST / _PORT | yes* | MongoDB host/port (default localhost:27017). |
CERTAUTOPILOT_DATABASE_URI | yes* | Full MongoDB URI — overrides host/port; use for replica sets / Atlas. |
CERTAUTOPILOT_JWT_SECRET | yes | ≥ 32 chars; enforced in production mode. |
CERTAUTOPILOT_ENCRYPTION_ENV_KEK_V1 | yes | 64-hex-char KEK (env provider). |
CERTAUTOPILOT_ENCRYPTION_CURRENT_VERSION | yes | 1 on a fresh install. Only a seed: once the keystore has versions it is ignored, so rotation leaves it at 1 and you do not need to bump it. |
CERTAUTOPILOT_SERVER_PORT | no | Backend port (default 8181). |
CERTAUTOPILOT_LOGGING_LEVEL | no | debug / info / warn. |
* one of the two: either host/port or the full URI.
The runtime mode (api / worker / scheduler / all) is a CLI flag, not an environment variable — override the container command (e.g. ["serve", "--mode=worker", "--config=/app/config.yaml"]) to split roles across containers.