Docker Deployment
This guide covers deploying Voidkey using Docker containers for production use.
Quick Start
Section titled “Quick Start”Deploy Voidkey with Docker Compose:
# Clone the repositorygit clone https://github.com/voidkey-oss/voidkey.gitcd voidkey
# Copy and customize configurationcp docker/docker-compose.prod.yml docker-compose.ymlcp docker/config.example.yaml config.yaml
# Edit configuration for your environmentvim config.yaml
# Start servicesdocker-compose up -dContainer Images
Section titled “Container Images”Official Images
Section titled “Official Images”Voidkey provides official Docker images:
# Broker serverdocker pull voidkey/broker:latestdocker pull voidkey/broker:0.8.0
# CLI (multi-arch)docker pull voidkey/cli:latestdocker pull voidkey/cli:0.8.0Image Variants
Section titled “Image Variants”| Tag | Description | Size |
|---|---|---|
latest | Latest stable release | ~100MB |
0.8.0 | Specific version | ~100MB |
alpine | Alpine-based (smaller) | ~80MB |
debug | With debugging tools | ~150MB |
Docker Compose Setup
Section titled “Docker Compose Setup”Production Configuration
Section titled “Production Configuration”version: '3.8'
services: voidkey-broker: image: voidkey/broker:latest ports: - "3000:3000" volumes: - ./config.yaml:/app/config/config.yaml:ro - ./logs:/app/logs environment: - NODE_ENV=production - LOG_LEVEL=info - CONFIG_PATH=/app/config/config.yaml restart: unless-stopped healthcheck: test: ["CMD", "curl", "-f", "http://localhost:3000/health"] interval: 30s timeout: 10s retries: 3 start_period: 30s
# Security settings user: "1001:1001" read_only: true tmpfs: - /tmp cap_drop: - ALL cap_add: - NET_BIND_SERVICE
# Resource limits deploy: resources: limits: memory: 512M cpus: '0.5' reservations: memory: 256M cpus: '0.25'
# Optional: Reverse proxy nginx: image: nginx:alpine ports: - "80:80" - "443:443" volumes: - ./nginx.conf:/etc/nginx/nginx.conf:ro - ./ssl:/etc/ssl/certs:ro depends_on: - voidkey-broker restart: unless-stopped
volumes: logs: driver: localDevelopment Configuration
Section titled “Development Configuration”version: '3.8'
services: voidkey-broker: build: context: . dockerfile: docker/Dockerfile.dev ports: - "3000:3000" - "9229:9229" # Debug port volumes: - .:/app - /app/node_modules - ./config/dev-config.yaml:/app/config/config.yaml environment: - NODE_ENV=development - LOG_LEVEL=debug command: npm run start:debug
# Development dependencies keycloak: image: quay.io/keycloak/keycloak:latest environment: KEYCLOAK_ADMIN: admin KEYCLOAK_ADMIN_PASSWORD: admin ports: - "8080:8080" command: start-dev
minio: image: minio/minio:latest environment: MINIO_ROOT_USER: minioadmin MINIO_ROOT_PASSWORD: minioadmin ports: - "9000:9000" - "9001:9001" command: server /data --console-address ":9001"Building Custom Images
Section titled “Building Custom Images”Dockerfile
Section titled “Dockerfile”# docker/DockerfileFROM node:18-alpine AS builder
WORKDIR /app
# Copy package filesCOPY broker-core/package*.json ./broker-core/COPY broker-server/package*.json ./broker-server/
# Install dependenciesRUN cd broker-core && npm ci --only=productionRUN cd broker-server && npm ci --only=production
# Copy sourceCOPY broker-core/ ./broker-core/COPY broker-server/ ./broker-server/
# Build applicationsRUN cd broker-core && npm run buildRUN cd broker-server && npm run build
# Production imageFROM node:18-alpine AS production
# Create non-root userRUN addgroup -g 1001 -S voidkey && \ adduser -S voidkey -u 1001 -G voidkey
WORKDIR /app
# Copy built applicationsCOPY --from=builder --chown=voidkey:voidkey /app/broker-core/dist ./broker-core/distCOPY --from=builder --chown=voidkey:voidkey /app/broker-core/package*.json ./broker-core/COPY --from=builder --chown=voidkey:voidkey /app/broker-server/dist ./broker-server/distCOPY --from=builder --chown=voidkey:voidkey /app/broker-server/package*.json ./broker-server/
# Install production dependenciesRUN cd broker-core && npm ci --only=production && npm cache clean --forceRUN cd broker-server && npm ci --only=production && npm cache clean --force
# Health checkHEALTHCHECK --interval=30s --timeout=10s --start-period=30s --retries=3 \ CMD node -e "require('http').get('http://localhost:3000/health', (res) => process.exit(res.statusCode === 200 ? 0 : 1))"
# Security settingsUSER voidkeyEXPOSE 3000
# Start commandCMD ["node", "broker-server/dist/main.js"]Multi-stage Build
Section titled “Multi-stage Build”# Optimized multi-stage buildFROM node:18-alpine AS depsWORKDIR /appCOPY package*.json ./RUN npm ci --only=production && npm cache clean --force
FROM node:18-alpine AS builderWORKDIR /appCOPY . .RUN npm ci && npm run build
FROM node:18-alpine AS runnerWORKDIR /appRUN addgroup -g 1001 -S nodejs && adduser -S voidkey -u 1001
COPY --from=deps --chown=voidkey:nodejs /app/node_modules ./node_modulesCOPY --from=builder --chown=voidkey:nodejs /app/dist ./distCOPY --from=builder --chown=voidkey:nodejs /app/package.json ./package.json
USER voidkeyEXPOSE 3000CMD ["node", "dist/main.js"]Configuration Management
Section titled “Configuration Management”Environment Variables
Section titled “Environment Variables”# Core configurationNODE_ENV=productionLOG_LEVEL=infoPORT=3000
# Configuration fileCONFIG_PATH=/app/config/config.yaml
# Secrets (use Docker secrets or external secret management)BROKER_CLIENT_SECRET_FILE=/run/secrets/broker_secretAWS_EXTERNAL_ID_FILE=/run/secrets/aws_external_idDocker Secrets
Section titled “Docker Secrets”# docker-compose.yml with secretsversion: '3.8'
services: voidkey-broker: image: voidkey/broker:latest secrets: - broker_secret - aws_external_id environment: - BROKER_CLIENT_SECRET_FILE=/run/secrets/broker_secret - AWS_EXTERNAL_ID_FILE=/run/secrets/aws_external_id
secrets: broker_secret: file: ./secrets/broker_secret.txt aws_external_id: file: ./secrets/aws_external_id.txtConfiguration Volume
Section titled “Configuration Volume”services: voidkey-broker: volumes: - type: bind source: ./config target: /app/config read_only: true - type: volume source: logs target: /app/logsReverse Proxy Setup
Section titled “Reverse Proxy Setup”Nginx Configuration
Section titled “Nginx Configuration”events { worker_connections 1024;}
http { upstream voidkey { server voidkey-broker:3000; }
server { listen 80; server_name voidkey.example.com; return 301 https://$server_name$request_uri; }
server { listen 443 ssl http2; server_name voidkey.example.com;
ssl_certificate /etc/ssl/certs/voidkey.crt; ssl_certificate_key /etc/ssl/certs/voidkey.key; ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers ECDHE-RSA-AES256-GCM-SHA512:DHE-RSA-AES256-GCM-SHA512; ssl_prefer_server_ciphers off;
# Security headers add_header Strict-Transport-Security "max-age=63072000" always; add_header X-Frame-Options DENY; add_header X-Content-Type-Options nosniff;
location / { proxy_pass http://voidkey; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme;
# Timeouts proxy_connect_timeout 5s; proxy_send_timeout 60s; proxy_read_timeout 60s; }
location /health { proxy_pass http://voidkey; access_log off; } }}Traefik Configuration
Section titled “Traefik Configuration”# docker-compose.yml with Traefikversion: '3.8'
services: traefik: image: traefik:v2.9 command: - "--api.insecure=true" - "--providers.docker=true" - "--entrypoints.web.address=:80" - "--entrypoints.websecure.address=:443" - "--certificatesresolvers.letsencrypt.acme.email=admin@example.com" - "--certificatesresolvers.letsencrypt.acme.storage=/acme.json" - "--certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=web" ports: - "80:80" - "443:443" volumes: - /var/run/docker.sock:/var/run/docker.sock - ./acme.json:/acme.json
voidkey-broker: image: voidkey/broker:latest labels: - "traefik.enable=true" - "traefik.http.routers.voidkey.rule=Host(`voidkey.example.com`)" - "traefik.http.routers.voidkey.entrypoints=websecure" - "traefik.http.routers.voidkey.tls.certresolver=letsencrypt" - "traefik.http.services.voidkey.loadbalancer.server.port=3000"High Availability Setup
Section titled “High Availability Setup”Multiple Instances
Section titled “Multiple Instances”version: '3.8'
services: voidkey-broker-1: image: voidkey/broker:latest volumes: - ./config.yaml:/app/config/config.yaml:ro environment: - INSTANCE_ID=broker-1
voidkey-broker-2: image: voidkey/broker:latest volumes: - ./config.yaml:/app/config/config.yaml:ro environment: - INSTANCE_ID=broker-2
voidkey-broker-3: image: voidkey/broker:latest volumes: - ./config.yaml:/app/config/config.yaml:ro environment: - INSTANCE_ID=broker-3
load-balancer: image: nginx:alpine ports: - "3000:80" volumes: - ./nginx-lb.conf:/etc/nginx/nginx.conf:ro depends_on: - voidkey-broker-1 - voidkey-broker-2 - voidkey-broker-3Load Balancer Configuration
Section titled “Load Balancer Configuration”upstream voidkey_cluster { least_conn; server voidkey-broker-1:3000 max_fails=3 fail_timeout=30s; server voidkey-broker-2:3000 max_fails=3 fail_timeout=30s; server voidkey-broker-3:3000 max_fails=3 fail_timeout=30s;}
server { listen 80;
location / { proxy_pass http://voidkey_cluster; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_next_upstream error timeout invalid_header http_500 http_502 http_503; }
location /health { proxy_pass http://voidkey_cluster; proxy_next_upstream error timeout invalid_header http_500 http_502 http_503; }}Monitoring and Logging
Section titled “Monitoring and Logging”Logging Configuration
Section titled “Logging Configuration”services: voidkey-broker: logging: driver: "json-file" options: max-size: "10m" max-file: "3"
# Centralized logging fluentd: image: fluent/fluentd:v1.12-debian volumes: - ./fluentd.conf:/fluentd/etc/fluent.conf - /var/log:/var/log ports: - "24224:24224"Prometheus Metrics
Section titled “Prometheus Metrics”services: prometheus: image: prom/prometheus ports: - "9090:9090" volumes: - ./prometheus.yml:/etc/prometheus/prometheus.yml
grafana: image: grafana/grafana ports: - "3001:3000" environment: - GF_SECURITY_ADMIN_PASSWORD=admin volumes: - grafana-data:/var/lib/grafana
volumes: grafana-data:Security Hardening
Section titled “Security Hardening”Container Security
Section titled “Container Security”services: voidkey-broker: # Run as non-root user user: "1001:1001"
# Read-only filesystem read_only: true tmpfs: - /tmp:size=100M - /app/logs:size=500M
# Drop capabilities cap_drop: - ALL cap_add: - NET_BIND_SERVICE
# Security options security_opt: - no-new-privileges:true - apparmor:docker-default
# Resource limits mem_limit: 512m mem_reservation: 256m cpus: 0.5Network Security
Section titled “Network Security”networks: frontend: driver: bridge backend: driver: bridge internal: true
services: nginx: networks: - frontend
voidkey-broker: networks: - frontend - backend
database: networks: - backendBackup and Recovery
Section titled “Backup and Recovery”Configuration Backup
Section titled “Configuration Backup”#!/bin/bashDATE=$(date +%Y%m%d_%H%M%S)BACKUP_DIR="/backups/voidkey/$DATE"
mkdir -p "$BACKUP_DIR"
# Backup configurationcp config.yaml "$BACKUP_DIR/"cp docker-compose.yml "$BACKUP_DIR/"
# Backup secretscp -r secrets/ "$BACKUP_DIR/"
# Backup logstar -czf "$BACKUP_DIR/logs.tar.gz" logs/
echo "Backup completed: $BACKUP_DIR"Disaster Recovery
Section titled “Disaster Recovery”#!/bin/bashBACKUP_DIR=$1
if [ -z "$BACKUP_DIR" ]; then echo "Usage: $0 <backup_directory>" exit 1fi
# Stop servicesdocker-compose down
# Restore configurationcp "$BACKUP_DIR/config.yaml" ./cp "$BACKUP_DIR/docker-compose.yml" ./
# Restore secretscp -r "$BACKUP_DIR/secrets/" ./
# Start servicesdocker-compose up -d
echo "Restore completed from: $BACKUP_DIR"Troubleshooting
Section titled “Troubleshooting”Common Issues
Section titled “Common Issues”Container won’t start:
# Check logsdocker-compose logs voidkey-broker
# Check configurationdocker-compose config
# Validate config filedocker run --rm -v $(pwd)/config.yaml:/config.yaml voidkey/broker:latest node -e " const yaml = require('yaml'); const fs = require('fs'); try { yaml.parse(fs.readFileSync('/config.yaml', 'utf8')); console.log('Config is valid'); } catch (e) { console.error('Config error:', e.message); process.exit(1); }"Health check failing:
# Test health endpointcurl -f http://localhost:3000/health
# Check container healthdocker inspect --format='{{.State.Health.Status}}' voidkey_voidkey-broker_1Permission issues:
# Fix ownershipsudo chown -R 1001:1001 config/ logs/
# Check SELinux contextsls -Z config/Next Steps
Section titled “Next Steps”- Kubernetes Deployment - Container orchestration
- Production Deployment - Production best practices
- Monitoring Guide - Set up monitoring
- Security Guide - Security considerations