version: '3.8' services: # PostgreSQL database for certctl postgres: image: postgres:16-alpine container_name: certctl-postgres-stepca-haproxy environment: POSTGRES_DB: certctl POSTGRES_USER: certctl POSTGRES_PASSWORD: ${DB_PASSWORD:-certctl-dev-password} volumes: - postgres_data:/var/lib/postgresql/data healthcheck: test: ['CMD-SHELL', 'pg_isready -U certctl -d certctl'] interval: 5s timeout: 5s retries: 5 networks: - certctl-network restart: unless-stopped # Smallstep step-ca (internal private CA) # Initialized with default admin token and provisioner configuration step-ca: image: smallstep/step-ca:latest container_name: step-ca-stepca-haproxy environment: # step-ca root password (for key encryption) STEPPATH: /home/step/step-ca # Provisioner password will be set up below volumes: # Persist step-ca configuration and keys - step_ca_data:/home/step/step-ca - ./step-ca-init.sh:/opt/step-ca-init.sh:ro entrypoint: /bin/sh command: - -c - | # Initialize step-ca if not already done if [ ! -f /home/step/step-ca/config/ca.json ]; then echo "Initializing step-ca..." step ca init \ --name="certctl-demo-ca" \ --dns=step-ca \ --address=0.0.0.0:9000 \ --provisioner=admin \ --provisioner-password-file=<(echo "${STEP_CA_PASSWORD:-stepca-demo-password}") \ --password-file=<(echo "${STEP_CA_PASSWORD:-stepca-demo-password}") \ --deployment-type=standalone \ --acme 2>&1 || true fi # Add a JWK provisioner for certctl if not present if ! step ca provisioner list 2>/dev/null | grep -q "certctl"; then echo "Adding certctl JWK provisioner..." step ca provisioner add certctl \ --type=JWK \ --password-file=<(echo "${STEP_CA_PROVISIONER_PASSWORD:-certctl-provisioner-demo}") \ 2>&1 || true fi # Start step-ca echo "Starting step-ca..." step-ca /home/step/step-ca/config/ca.json \ --password-file=<(echo "${STEP_CA_PASSWORD:-stepca-demo-password}") ports: - '9000:9000' healthcheck: test: ['CMD-SHELL', 'step ca health --insecure || exit 1'] interval: 10s timeout: 5s retries: 3 networks: - certctl-network restart: unless-stopped # certctl server (control plane) certctl-server: image: ghcr.io/shankar0123/certctl-server:latest container_name: certctl-server-stepca-haproxy environment: # Database CERTCTL_DATABASE_URL: postgres://certctl:${DB_PASSWORD:-certctl-dev-password}@postgres:5432/certctl?sslmode=disable # Server settings CERTCTL_SERVER_PORT: 8443 CERTCTL_SERVER_HOST: 0.0.0.0 # Auth (disabled for demo; production should use API keys) CERTCTL_AUTH_TYPE: none # CORS (allow agent communication) CERTCTL_CORS_ORIGINS: '*' # Key generation mode (agent-side in production, server-side for demo) CERTCTL_KEYGEN_MODE: agent # step-ca issuer configuration # step-ca runs on step-ca:9000 in this compose network CERTCTL_STEPCA_URL: https://step-ca:9000 CERTCTL_STEPCA_ROOT_CERT_PATH: /etc/certctl/step-ca-root.crt CERTCTL_STEPCA_PROVISIONER: certctl CERTCTL_STEPCA_KEY_PATH: /etc/certctl/step-ca-provisioner.json CERTCTL_STEPCA_PASSWORD: ${STEP_CA_PROVISIONER_PASSWORD:-certctl-provisioner-demo} # Logging CERTCTL_LOG_LEVEL: info volumes: # Mount step-ca certs for TLS verification (auto-generated by step-ca init) - step_ca_data:/home/step/step-ca/config:ro ports: - '${SERVER_PORT:-8443}:8443' depends_on: postgres: condition: service_healthy step-ca: condition: service_healthy networks: - certctl-network healthcheck: test: ['CMD-SHELL', 'curl -sf http://localhost:8443/health || exit 1'] interval: 10s timeout: 5s retries: 3 restart: unless-stopped # certctl agent (runs on the target machine with HAProxy) certctl-agent: image: ghcr.io/shankar0123/certctl-agent:latest container_name: certctl-agent-stepca-haproxy environment: # Control plane connection CERTCTL_SERVER_URL: http://certctl-server:8443 CERTCTL_API_KEY: ${AGENT_API_KEY:-agent-demo-key} # Key generation (agent-side keys, never sent to server) CERTCTL_KEYGEN_MODE: agent CERTCTL_KEY_DIR: /var/lib/certctl/keys # Discovery (scan existing certs so operator knows what's already deployed) CERTCTL_DISCOVERY_DIRS: /etc/haproxy/ssl # Heartbeat interval CERTCTL_HEARTBEAT_INTERVAL: 30s # Agent metadata (self-reported) CERTCTL_AGENT_NAME: haproxy-agent-01 # Logging CERTCTL_LOG_LEVEL: info volumes: # Mount HAProxy config and cert directories # In production, these would be the actual HAProxy paths - haproxy_certs:/etc/haproxy/ssl - haproxy_conf:/etc/haproxy # Agent key storage (persisted across restarts) - agent_keys:/var/lib/certctl/keys depends_on: certctl-server: condition: service_healthy networks: - certctl-network restart: unless-stopped # HAProxy reverse proxy / load balancer # This is where certificates will be deployed haproxy: image: haproxy:2.9-alpine container_name: certctl-haproxy-stepca-haproxy ports: - '80:80' - '443:443' volumes: - haproxy_conf:/etc/haproxy - haproxy_certs:/etc/haproxy/ssl # Default HAProxy config - ./haproxy.cfg:/etc/haproxy/haproxy.cfg:ro depends_on: - certctl-agent networks: - certctl-network healthcheck: test: ['CMD-SHELL', 'wget --quiet --tries=1 --spider http://localhost:8080/stats || exit 1'] interval: 10s timeout: 5s retries: 3 restart: unless-stopped networks: certctl-network: driver: bridge volumes: postgres_data: driver: local step_ca_data: driver: local haproxy_certs: driver: local haproxy_conf: driver: local agent_keys: driver: local