Docker Deployment

Quick Start

The fastest way to run Ferro:

docker compose up -d

This starts Ferro with in-memory storage on port 8080.

Base Configuration

The Docker Compose configs use a layered overlay pattern. Start with the base and add overlays as needed:

FileAdds
docker-compose.ymlFerro only
docker-compose.pg.ymlPostgreSQL
docker-compose.redis.ymlRedis

Single Node (Minimal)

cd deploy
docker compose up -d

docker-compose.yml

services:
  ferro:
    image: ghcr.io/wyattau/ferro:latest
    container_name: ferro
    restart: unless-stopped
    ports:
      - "${FERRO_PORT:-8080}:8080"
    volumes:
      - ferro-data:/data
    environment:
      FERRO_DATA_DIR: /data
      FERRO_HOST: 0.0.0.0
      FERRO_PORT: 8080
      FERRO_LOG_FORMAT: json
      FERRO_LOG_LEVEL: info
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8080/healthz"]
      interval: 30s
      timeout: 5s
      retries: 3
      start_period: 10s
    deploy:
      resources:
        limits:
          memory: 512M

volumes:
  ferro-data:
    driver: local

With PostgreSQL

POSTGRES_PASSWORD=your-password \
  docker compose -f docker-compose.yml -f docker-compose.pg.yml up -d

With PostgreSQL and Redis

POSTGRES_PASSWORD=your-password \
  docker compose -f docker-compose.yml -f docker-compose.pg.yml -f docker-compose.redis.yml up -d

Environment Variables

VariableDefaultDescription
FERRO_PORT8080Host port mapping
POSTGRES_PASSWORDferroPostgreSQL password
FERRO_DATA_DIR/dataPersistent data directory in container
FERRO_LOG_FORMATjsonLog format
FERRO_LOG_LEVELinfoLog level

Health Check

The container includes a health check that hits /healthz:

docker inspect --format='{{.State.Health.Status}}' ferro

With Reverse Proxy (Caddy)

For production, use Caddy for automatic HTTPS:

services:
  ferro:
    build: .
    expose:
      - "8080"
    volumes:
      - ferro-data:/data
    environment:
      - FERRO_DATA_DIR=/data
      - FERRO_STATIC_DIR=/app/ui
      - FERRO_ADMIN_USER=admin
      - FERRO_ADMIN_PASSWORD=changeme
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "curl", "-sf", "http://localhost:8080/.well-known/ferro"]
      interval: 30s
      timeout: 5s
      retries: 3
      start_period: 10s

  caddy:
    image: caddy:2-alpine
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile:ro
      - caddy-data:/data
      - caddy-config:/config
    environment:
      - DOMAIN=localhost
    depends_on:
      - ferro
    restart: unless-stopped

volumes:
  ferro-data:
  caddy-data:
  caddy-config:

Tips

  • Use named volumes for persistent data (ferro-data)
  • Set FERRO_LOG_FORMAT=json for structured logging in production
  • Set memory limits via deploy.resources.limits
  • Use docker compose logs -f ferro to follow logs
  • The health check uses /healthz for liveness monitoring