Guides

Docker Deployment

Run uPKI CA in Docker or Docker Compose with production-ready settings.

Docker Deployment

Single container

docker run -d \
  --name upki-ca \
  -p 5000:5000 \
  -p 5001:5001 \
  -e UPKI_DATA_DIR=/data \
  -e UPKI_CA_SEED="${PKI_SEED}" \
  -e UPKI_CA_HOST=0.0.0.0 \
  -v upki-ca-data:/data \
  ghcr.io/circle-rd/upki-ca:latest

On the very first start, the container runs init automatically (creating the CA key and certificate), then starts both ZMQ listeners.

Docker Compose with uPKI RA

# docker-compose.yml
services:
  upki-ca:
    image: ghcr.io/circle-rd/upki-ca:latest
    restart: unless-stopped
    environment:
      UPKI_DATA_DIR: /data
      UPKI_CA_SEED: ${PKI_SEED}
      UPKI_CA_HOST: 0.0.0.0
    volumes:
      - upki-ca-data:/data
    ports:
      - "5000:5000"
      - "5001:5001"
    healthcheck:
      test:
        - "CMD-SHELL"
        - >
          python -c "import socket; s=socket.socket(); s.settimeout(2);
          s.connect(('127.0.0.1', 5000)); s.close()"
      interval: 10s
      timeout: 5s
      retries: 10
      start_period: 15s

  upki-ra:
    image: ghcr.io/circle-rd/upki-ra:latest
    restart: unless-stopped
    depends_on:
      upki-ca:
        condition: service_healthy
    environment:
      UPKI_DATA_DIR: /data
      UPKI_CA_HOST: upki-ca
      UPKI_CA_SEED: ${PKI_SEED}
      UPKI_RA_TLS: "true"
      UPKI_RA_SANS: "upki-ra"
    volumes:
      - upki-ra-data:/data
    ports:
      - "8000:8000"

volumes:
  upki-ca-data:
  upki-ra-data:

.env file:

PKI_SEED=your-strong-random-seed-here

Healthcheck

The default CA healthcheck uses a TCP socket probe on port 5000 — it does not require the CA to be in a particular operational state, just to be listening.

healthcheck:
  test:
    - "CMD-SHELL"
    - >
      python -c "import socket; s=socket.socket(); s.settimeout(2);
      s.connect(('127.0.0.1', 5000)); s.close()"
  interval: 10s
  timeout: 5s
  retries: 10
  start_period: 15s

Persisting data

Always mount UPKI_DATA_DIR as a named volume or host bind mount. The CA root key, all node certificates, and the TinyDB files live there.

If UPKI_DATA_DIR is not persisted, the CA will regenerate a new root key on every container restart, invalidating all previously issued certificates.

Exposing ZMQ ports

In production:

  • Port 5000 — expose only to RA nodes and admin tools. Do not expose to the internet.
  • Port 5001 — expose only during initial RA registration, then restrict with a firewall rule.

Use Docker network isolation or a firewall to enforce this:

networks:
  pki-internal:
    internal: true # no external internet routing
Copyright © 2026