Skip to main content

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.
Lose the KEK = lose every stored secret

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:

VariableRequiredNotes
CERTAUTOPILOT_DATABASE_HOST / _PORTyes*MongoDB host/port (default localhost:27017).
CERTAUTOPILOT_DATABASE_URIyes*Full MongoDB URI — overrides host/port; use for replica sets / Atlas.
CERTAUTOPILOT_JWT_SECRETyes≥ 32 chars; enforced in production mode.
CERTAUTOPILOT_ENCRYPTION_ENV_KEK_V1yes64-hex-char KEK (env provider).
CERTAUTOPILOT_ENCRYPTION_CURRENT_VERSIONyes1 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_PORTnoBackend port (default 8181).
CERTAUTOPILOT_LOGGING_LEVELnodebug / 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.

See also