Skip to content

Latest commit

 

History

History
456 lines (349 loc) · 13.2 KB

File metadata and controls

456 lines (349 loc) · 13.2 KB

Docker Build & Deployment

This guide covers building, deploying, and running CertMate in Docker — including multi-platform support for ARM and AMD64.


Quick Start

Pull and Run

# Docker automatically selects the right architecture
docker run -d --name certmate \
  --env-file .env \
  -p 8000:8000 \
  -v certmate_data:/app/data \
  -v certmate_certificates:/app/certificates \
  fabriziosalmi/certmate:latest

Build and Run Locally

docker build -t certmate:latest .
docker run -d --name certmate \
  --env-file .env \
  -p 8000:8000 \
  -v certmate_certificates:/app/certificates \
  -v certmate_data:/app/data \
  -v certmate_logs:/app/logs \
  certmate:latest

Security

The build process ensures no secrets are included in the image:

  • .dockerignore excludes all .env files and sensitive data
  • Environment variables are provided at runtime, not build time
  • Only essential application files are included
  • Images can be safely pushed to public registries

Verify No Secrets in Image

docker history certmate:latest
docker inspect certmate:latest | grep -i env
docker run --rm certmate:latest find / -name "*.env" 2>/dev/null

Runtime Configuration

Option 1: Environment File

Create a .env file on your host (not in the Docker image):

SECRET_KEY=your-super-secret-key-here
# SECRET_KEY_FILE=/run/secrets/secret_key  # Alternative: takes precedence over SECRET_KEY
# Set API_BEARER_TOKEN before exposing a not-yet-onboarded instance to a
# network: until the first admin exists, an instance with no token serves the
# setup bypass to anyone who can reach it. When set, paste it once on the
# first-run screen to create the admin.
API_BEARER_TOKEN=your-api-bearer-token-here
# API_BEARER_TOKEN_FILE=/run/secrets/api_bearer_token  # Alternative: takes precedence over API_BEARER_TOKEN
CLOUDFLARE_API_TOKEN=your-cloudflare-api-token
LOG_LEVEL=INFO
docker run -d --name certmate \
  --env-file .env \
  -p 8000:8000 \
  -v certmate_certificates:/app/certificates \
  -v certmate_data:/app/data \
  -v certmate_logs:/app/logs \
  certmate:latest

Option 2: Direct Environment Variables

docker run -d --name certmate \
  -e SECRET_KEY="your-secret-key" \
  # -e SECRET_KEY_FILE="/run/secrets/secret_key" \  # Alternative: takes precedence over SECRET_KEY
  -e API_BEARER_TOKEN="your-api-bearer-token" \
  # -e API_BEARER_TOKEN_FILE="/run/secrets/api_bearer_token" \  # Alternative: takes precedence over API_BEARER_TOKEN
  -e CLOUDFLARE_API_TOKEN="your-api-token" \
  -p 8000:8000 \
  -v certmate_certificates:/app/certificates \
  -v certmate_data:/app/data \
  certmate:latest

Environment Variables Reference

Variable Required Description
SECRET_KEY No Flask secret key for sessions (auto-generated if unset)
SECRET_KEY_FILE No Path to a file containing the Flask secret key (takes precedence over SECRET_KEY)
API_BEARER_TOKEN No (auto-generated) API auth token. Auto-generated if unset, but set it before exposing a not-yet-onboarded instance to a network; when set, paste it once on the first-run screen to create the admin
API_BEARER_TOKEN_FILE No Path to a file containing the API bearer token (takes precedence over API_BEARER_TOKEN)
LOG_LEVEL No INFO (default), DEBUG, WARNING, ERROR
CERTMATE_BACKUP_PASSPHRASE No When set, unified backups are encrypted at rest (.zip.enc, PBKDF2-SHA256 + Fernet). The same passphrase is required to restore them. Unset = legacy cleartext .zip backups
CLOUDFLARE_API_TOKEN No Cloudflare DNS provider token
AWS_ACCESS_KEY_ID No AWS Route53 access key
AWS_SECRET_ACCESS_KEY No AWS Route53 secret key

See the Installation Guide for the complete list.


Docker Compose

Basic Setup

version: '3.8'

services:
  certmate:
    image: fabriziosalmi/certmate:latest
    container_name: certmate
    ports:
      - "8000:8000"
    environment:
      - SECRET_KEY=${SECRET_KEY:-}
      # - SECRET_KEY_FILE=${SECRET_KEY_FILE:-}  # Alternative: path to a file containing the secret key
      - API_BEARER_TOKEN=${API_BEARER_TOKEN:-}
      # - API_BEARER_TOKEN_FILE=${API_BEARER_TOKEN_FILE:-}  # Alternative: path to a file containing the bearer token
      - LOG_LEVEL=${LOG_LEVEL:-INFO}
    volumes:
      - certmate_certificates:/app/certificates
      - certmate_data:/app/data
      - certmate_logs:/app/logs
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 40s

volumes:
  certmate_certificates:
  certmate_data:
  certmate_logs:
# Start with .env file in the same directory
docker-compose up -d

# Or specify a different env file
docker-compose --env-file /path/to/.env up -d

Rootless podman / OpenShift (arbitrary UID)

The image runs as the non-root user 1000 by default, but it also follows the OpenShift arbitrary-UID pattern: the runtime-writable directories (/app/data, /app/certificates, /app/logs, /app/backups) are owned by group 0 (root group) and are group-writable + setgid. A container process running as any UID that belongs to group 0 — which is how rootless podman and OpenShift launch containers — can therefore create and rename files without a manual chown -R 1000:1000 of the volumes. Secret files the app writes (the CA private key, the audit-signing key, DNS credential files, .secret_key) are always created 0600 owner-only, so the group-writable directories never expose a key. (Issue #380.)

Named volumes — works out of the box

A fresh named volume inherits the image's group-0 permissions, so no host preparation is needed regardless of the UID podman assigns:

podman run -d --name certmate \
  -p 8000:8000 \
  -v certmate_data:/app/data \
  -v certmate_certificates:/app/certificates \
  -v certmate_logs:/app/logs \
  -v certmate_backups:/app/backups \
  docker.io/fabriziosalmi/certmate:latest

Bind mounts (host directories)

A bind mount keeps the host directory's ownership, which shadows the image's permissions. Make the host directories writable by group 0 once, then run:

# Create the host dirs group-0 writable (any UID in group 0 can write)
mkdir -p ./{data,certificates,logs,backups}
chgrp -R 0 ./{data,certificates,logs,backups}
chmod -R g+rwX ./{data,certificates,logs,backups}

podman run -d --name certmate \
  -p 8000:8000 \
  -v ./data:/app/data \
  -v ./certificates:/app/certificates \
  -v ./logs:/app/logs \
  -v ./backups:/app/backups \
  docker.io/fabriziosalmi/certmate:latest

Alternatively let podman fix the ownership for you with the :U mount option (recursively chowns the source to match the container UID/GID):

podman run -d --name certmate \
  -p 8000:8000 \
  -v ./data:/app/data:U \
  -v ./certificates:/app/certificates:U \
  -v ./logs:/app/logs:U \
  -v ./backups:/app/backups:U \
  docker.io/fabriziosalmi/certmate:latest

rootless podman-compose

services:
  certmate:
    image: docker.io/fabriziosalmi/certmate:latest
    ports:
      - "8000:8000"
    volumes:
      - certmate_data:/app/data
      - certmate_certificates:/app/certificates
      - certmate_logs:/app/logs
      - certmate_backups:/app/backups
    restart: unless-stopped

volumes:
  certmate_data:
  certmate_certificates:
  certmate_logs:
  certmate_backups:

If startup aborts with "Required directories are not writable by the CertMate process", the mount is not group-0 writable — apply the chgrp 0 … && chmod g+rwX … above, switch to a named volume, or add :U to the bind mounts.

Kubernetes / OpenShift: no changes needed. Set spec.securityContext.fsGroup: 0 (or rely on the default restricted SCC, which already assigns an arbitrary UID in group 0) and the mounted volumes become group-0 writable automatically.


Upgrading

CertMate keeps all persistent state in the mounted volumes — chiefly ./data (settings.json, the admin users, the auto-generated .secret_key, the audit signing key, and the scheduler database) and ./certificates. Because state lives in those volumes and not in the image, upgrading is just pulling a newer image and recreating the container; your configuration, certificates, and login carry forward.

# Recommended: take a backup first (Settings → Backup, or the API)

# Docker Compose
docker compose pull          # fetch the new image
docker compose up -d         # recreate the container (data/ + certificates/ persist)

# Plain docker run — stop/remove and re-run with the SAME volume mounts
docker pull fabriziosalmi/certmate:latest
docker rm -f certmate
docker run -d --name certmate --env-file .env -p 127.0.0.1:8000:8000 \
  -v "$(pwd)/data:/app/data" -v "$(pwd)/certificates:/app/certificates" \
  -v "$(pwd)/logs:/app/logs" -v "$(pwd)/backups:/app/backups" \
  fabriziosalmi/certmate:latest

Settings-format migrations run automatically on boot. For production, pin a version tag (e.g. fabriziosalmi/certmate:2.19) instead of :latest so repulls don't surprise you with an unintended upgrade — the multi-platform build publishes MAJOR, MAJOR.MINOR, and MAJOR.MINOR.PATCH tags.


Multi-Platform Builds

CertMate supports multi-platform Docker images for both ARM and AMD64 architectures.

Supported Architectures

Platform Description Common Use Cases
linux/amd64 Intel/AMD 64-bit Most cloud servers, desktops
linux/arm64 ARM 64-bit Apple Silicon, ARM cloud instances
linux/arm/v7 ARM 32-bit v7 Raspberry Pi 3+ — not published: buildable on request (see below), the release images are amd64 + arm64
linux/arm/v6 ARM 32-bit v6 Raspberry Pi 1, Zero

Build Scripts

# Build for current platform only
./build-docker.sh

# Build for multiple platforms (ARM64 + AMD64)
./build-docker.sh -m

# Build and push to Docker Hub
./build-docker.sh -m -p -r YOUR_DOCKERHUB_USERNAME

# Dedicated multi-platform script
./build-multiplatform.sh -r USERNAME -v v1.0.0 -p

# Build for Raspberry Pi
./build-multiplatform.sh --platforms linux/arm/v7 -r USERNAME -p

Manual Docker Buildx

# Create and use buildx builder
docker buildx create --name certmate-builder --use

# Build for multiple platforms
docker buildx build --platform linux/amd64,linux/arm64 \
  -t USERNAME/certmate:latest .

# Build and push
docker buildx build --platform linux/amd64,linux/arm64 \
  -t USERNAME/certmate:latest --push .

Prerequisites for Multi-Platform

# Verify buildx support
docker buildx version
docker buildx inspect --bootstrap

# Enable QEMU emulation (if needed)
docker run --privileged --rm tonistiigi/binfmt --install all

Force Specific Platform

# Force AMD64 (e.g., on Apple Silicon for testing)
docker run --platform linux/amd64 --rm \
  --env-file .env -p 8000:8000 certmate:latest

# Auto-detect (recommended)
docker run --rm --env-file .env -p 8000:8000 certmate:latest

Pushing to Docker Hub

# Login
docker login

# Tag and push
docker build -t USERNAME/certmate:latest .
docker push USERNAME/certmate:latest

# With version tag
docker build -t USERNAME/certmate:v1.0.0 .
docker push USERNAME/certmate:v1.0.0

CI/CD Integration

GitHub Actions

Required secrets:

  • DOCKERHUB_USERNAME
  • DOCKERHUB_TOKEN
# Manual trigger with custom platforms
gh workflow run docker-multiplatform.yml \
  -f platforms="linux/amd64,linux/arm64,linux/arm/v7" \
  -f push_to_registry=true

Production Tips

  1. Use secrets management: Docker secrets, Kubernetes secrets, or a secrets manager
  2. Enable TLS: Run behind a reverse proxy with TLS termination
  3. Monitor resources: Set CPU and memory limits
  4. Backup volumes: Regularly backup certificate and data volumes
  5. Update regularly: Keep the image updated with security patches
  6. Use layer caching for faster builds:
    docker buildx build --cache-from type=registry,ref=USERNAME/certmate:cache .

Troubleshooting

Container Won't Start

docker logs certmate
docker exec certmate env

Health Check Fails

docker logs certmate
docker exec certmate curl -v http://localhost:8000/health

Permission Issues

docker exec certmate ls -la /app/certificates
docker exec certmate ls -la /app/data

Multi-Platform Build Issues

Error Solution
"multiple platforms not supported for docker driver" docker buildx create --name multiplatform --use
"exec format error" docker run --privileged --rm tonistiigi/binfmt --install all
Slow non-native builds Normal due to emulation; use GitHub Actions for production
Cannot load multi-platform to local Docker Use --load with single platform for local testing

Image Sizes

Typical sizes per architecture:

  • AMD64: ~200-300 MB
  • ARM64: ~200-300 MB
  • ARM v7: ~180-250 MB