Guides

Docker Deployment

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

Docker Deployment

Environment variables

The Docker image ships with these defaults:

VariableImage defaultDescription
UPKI_DATA_DIR/dataData directory path
UPKI_CA_HOSTupki-caCA hostname (ZMQ connection)
UPKI_CA_PORT5000CA ZMQ port
UPKI_RA_HOST0.0.0.0RA bind address
UPKI_RA_PORT8000RA HTTP/HTTPS port
UPKI_RA_TLStrueEnable HTTPS (uses ra.crt/ra.key)
UPKI_RA_SANSupki-raSANs for the RA's own certificate (first boot only)

Single container

docker run -d \
  --name upki-ra \
  -p 8000:8000 \
  -e UPKI_CA_HOST=upki-ca \
  -e UPKI_CA_SEED="${PKI_SEED}" \
  -e UPKI_RA_SANS="upki-ra,ra.example.internal" \
  -v upki-ra-data:/data \
  ghcr.io/circle-rd/upki-ra:latest

Docker Compose (complete stack)

# 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
    networks:
      - pki-net
    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,${DOMAIN}"
    volumes:
      - upki-ra-data:/data
    ports:
      - "8000:8000"
    networks:
      - pki-net
    healthcheck:
      test:
        - "CMD-SHELL"
        - >
          python -c "
          import os, urllib.request, ssl
          tls = os.getenv('UPKI_RA_TLS','true').lower() not in ('false','0','no')
          ctx = ssl.create_default_context() if tls else None
          if ctx: ctx.check_hostname=False; ctx.verify_mode=ssl.CERT_NONE
          urllib.request.urlopen(
            ('https' if tls else 'http')+'://localhost:8000/api/v1/public/health',
            context=ctx, timeout=5)
          "
      interval: 15s
      timeout: 10s
      retries: 5
      start_period: 30s

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

networks:
  pki-net:
    internal: true

.env:

PKI_SEED=your-strong-random-seed-here
DOMAIN=ra.example.internal

UPKI_RA_SANS — important caveat

UPKI_RA_SANS is consumed only on the first boot when the RA registers with the CA. Changing it afterwards has no effect until the RA certificate is re-issued (re-registration).Always set all expected hostnames (Docker service name, FQDN, IP) before the first start.

Example:

UPKI_RA_SANS=upki-ra,ra.example.internal,192.168.1.5

Data persistence

Mount UPKI_DATA_DIR as a named volume. This directory contains:

  • ra.crt / ra.key — RA TLS certificate and key
  • ca.crt — downloaded CA certificate
  • acme.db — SQLite ACME state

If the volume is lost, the RA will re-bootstrap on next start (generating a new certificate).

Copyright © 2026