M-Bus Gateway
← Tilbage til blog
· Docker· Docker Compose· produktion· SaaS· deployment· health checks· GitHub Actions· Hetzner· CI/CD

Docker Compose til produktion — SaaS deployment med health checks og netværk

Docker Compose produktionsopsætning til SaaS: health checks, interne netværk, secrets management, ressourcegrænser, log-rotation og zero-downtime deploy via GitHub Actions.

Af M-Bus Gateway

M-Bus Gateway platformen kører på Docker Compose på Hetzner. Her er produktionskonfigurationen med health checks, netværksisolation og zero-downtime deploy.


docker-compose.yml: Komplet produktionskonfiguration

# docker-compose.yml
services:
  # ─── Database ──────────────────────────────────────────────────────
  db:
    image: timescale/timescaledb:latest-pg16
    restart: unless-stopped
    environment:
      POSTGRES_DB: mbus
      POSTGRES_USER: mbus
      POSTGRES_PASSWORD_FILE: /run/secrets/db_password
    secrets:
      - db_password
    volumes:
      - postgres-data:/var/lib/postgresql/data
    networks:
      - internal
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U mbus -d mbus"]
      interval: 10s
      timeout: 5s
      retries: 5
      start_period: 30s
    deploy:
      resources:
        limits:
          memory: 2G
          cpus: "2.0"

  # ─── Redis ─────────────────────────────────────────────────────────
  redis:
    image: redis:7-alpine
    restart: unless-stopped
    command: redis-server --requirepass-file /run/secrets/redis_password --maxmemory 256mb --maxmemory-policy allkeys-lru
    secrets:
      - redis_password
    volumes:
      - redis-data:/data
    networks:
      - internal
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 10s
      timeout: 5s
      retries: 3

  # ─── MQTT Broker ────────────────────────────────────────────────────
  mosquitto:
    image: eclipse-mosquitto:2.0
    restart: unless-stopped
    ports:
      - "8883:8883"
    volumes:
      - ./mosquitto/config:/mosquitto/config:ro
      - ./mosquitto/certs:/mosquitto/certs:ro
      - mosquitto-data:/mosquitto/data
    networks:
      - internal
      - external  # Port 8883 eksponeret til gateways

  # ─── API Server ─────────────────────────────────────────────────────
  server:
    image: ghcr.io/kirken20/mbus-gateway/server:${IMAGE_TAG:-latest}
    restart: unless-stopped
    depends_on:
      db:
        condition: service_healthy
      redis:
        condition: service_healthy
    environment:
      DATABASE_URL: postgresql+asyncpg://mbus:${DB_PASSWORD}@db/mbus
      REDIS_URL: redis://:${REDIS_PASSWORD}@redis:6379/0
    env_file: .env.prod
    networks:
      - internal
      - external
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 60s
    deploy:
      resources:
        limits:
          memory: 1G
          cpus: "1.5"

  # ─── Celery Workers ─────────────────────────────────────────────────
  celery-worker:
    image: ghcr.io/kirken20/mbus-gateway/server:${IMAGE_TAG:-latest}
    restart: unless-stopped
    command: celery -A server.src.workers.celery_app worker --loglevel=info --concurrency=4 -Q critical,default,low
    depends_on:
      db:
        condition: service_healthy
      redis:
        condition: service_healthy
    env_file: .env.prod
    networks:
      - internal

  celery-beat:
    image: ghcr.io/kirken20/mbus-gateway/server:${IMAGE_TAG:-latest}
    restart: unless-stopped
    command: celery -A server.src.workers.celery_app beat --loglevel=info --scheduler redisbeat.RedisScheduler
    depends_on:
      - redis
    env_file: .env.prod
    networks:
      - internal

  # ─── Nginx reverse proxy ─────────────────────────────────────────────
  nginx:
    image: nginx:alpine
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
      - ./nginx/conf.d:/etc/nginx/conf.d:ro
      - certbot-www:/var/www/certbot:ro
      - certbot-conf:/etc/letsencrypt:ro
    depends_on:
      server:
        condition: service_healthy
    networks:
      - external

volumes:
  postgres-data:
  redis-data:
  mosquitto-data:
  certbot-www:
  certbot-conf:

networks:
  internal:
    internal: true   # Ingen adgang fra host/internet
  external:

secrets:
  db_password:
    file: ./secrets/db_password.txt
  redis_password:
    file: ./secrets/redis_password.txt

Zero-downtime deploy: GitHub Actions

# .github/workflows/deploy.yml (udsnit)
- name: Deploy til Hetzner
  run: |
    ssh -i ${{ secrets.HETZNER_SSH_KEY }} deploy@178.105.90.8 << 'ENDSSH'
      cd /opt/mbus-gateway

      # Pull seneste image (bygget i CI):
      docker pull ghcr.io/kirken20/mbus-gateway/server:${{ github.sha }}

      # Update IMAGE_TAG og deploy med rolling restart:
      export IMAGE_TAG=${{ github.sha }}
      docker compose up -d --no-deps --wait server celery-worker celery-beat

      # Vent på health check inden success:
      docker compose ps server | grep "healthy" || exit 1

      echo "Deploy succesfuld: ${{ github.sha }}"
    ENDSSH

Log-rotation: Undgå disk-overflow

# Docker log-rotation via daemon.json:
# /etc/docker/daemon.json på Hetzner:
{
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "50m",
    "max-file": "3"
  }
}

# Per-service override i docker-compose:
services:
  server:
    logging:
      driver: "json-file"
      options:
        max-size: "100m"
        max-file: "5"

Ressourcemonitorering

# Tjek ressourceforbrug live:
docker stats --no-stream

# CONTAINER      CPU%   MEM USAGE/LIMIT   MEM%   NET I/O
# server         2.1%   312MiB / 1GiB     30.5%  1.2GB / 890MB
# db             8.3%   1.1GiB / 2GiB     55.0%  4.5GB / 2.1GB
# celery-worker  0.5%   185MiB / 1GiB     18.1%  120MB / 45MB
# mosquitto      0.1%   12MiB / 256MiB    4.7%   8.2GB / 7.1GB

# Health status:
docker compose ps

# Logs fra service:
docker compose logs --tail=100 -f server

Konklusion

Docker Compose med depends_on + health checks sikrer korrekt opstartsrækkefølge. Interne netværk isolerer services fra internet. Docker secrets håndterer adgangskoder sikkert (aldrig i environment variables). Zero-downtime deploy via --no-deps --wait starter kun de opdaterede services og venter på health check. Log-rotation forhindrer disk-overflow.

Se Hetzner Object Storage guide eller GitHub Actions CI guide.