Skip to content

Configuration Reference (epd.yml)

Complete annotated schema for epd.yml.


Full Annotated Schema

yaml
# ==============================================================================
# Global Application Settings
# ==============================================================================
name: myapp                    # App name (alphanumeric, dashes, underscores)
mode: docker                   # Runtime mode: "docker" | "process"

# ==============================================================================
# Docker Mode Settings (when mode: docker)
# ==============================================================================
image: ghcr.io/org/myapp       # Docker image repository
registry:                      # Optional: Container registry credentials
  server: ghcr.io
  username: deployer
  password: ${GITHUB_TOKEN}    # Resolved from local environment variable

build:
  dockerfile: Dockerfile       # Relative path to Dockerfile (default: Dockerfile)
  context: .                   # Build context directory (default: .)
  platform: linux/amd64        # Target architecture
  args:                        # Build arguments passed to docker build
    COMMIT_SHA: ${GIT_SHA:-dev}
  secrets:                     # BuildKit build secrets
    npm_token: NPM_TOKEN

# ==============================================================================
# Process Mode Settings (when mode: process)
# ==============================================================================
process:
  install: bun install         # Dependency install command on server
  build: bun run build         # Build command on server
  start: bun run start         # Start command (epd automatically injects $PORT)
  exclude:                     # Files excluded from rsync archive
    - ".git"
    - "node_modules"
    - ".env"
    - "dist"
  source: rsync                # Source transport: "rsync" | "git"

# ==============================================================================
# SSH Connection Details
# ==============================================================================
ssh:
  user: root                   # SSH username (default: root)
  port: 22                     # SSH port (default: 22)
  key: ~/.ssh/id_ed25519       # Path to explicit private key
  proxy_jump: bastion.net      # Optional jump host / bastion
  options:                     # Extra OpenSSH options
    - ServerAliveInterval=30

# ==============================================================================
# Server Groups & Service Definitions
# ==============================================================================
servers:
  web:
    hosts:                     # Target servers (IPs or Hetzner selectors)
      - 203.0.113.10
      - hetzner:prod-web-1     # Dynamic Hetzner query by server name
      - hetzner:label:app=web  # Dynamic Hetzner query by label selector
    replicas: 2                # Number of container replicas per host
    port: 3000                 # Internal port the app listens on
    domains: [myapp.com]       # Public domains (automatic SSL)
    cpus: "2.0"                # Docker CPU limit
    memory: "1g"               # Docker RAM limit
    drain: 10                  # Graceful drain period (seconds) before old slot stops
    stop_timeout: 15           # Grace period before SIGKILL
    volumes:
      - /var/lib/epd/volumes/uploads:/app/uploads
    env:
      clear:
        APP_ENV: production
      secret:
        - APP_SECRET

  worker:                      # Background worker service (no exposed routes)
    hosts: [203.0.113.10]
    replicas: 1
    command: bun run worker.ts # Custom entrypoint command

# ==============================================================================
# Shared Reverse Proxy (Traefik)
# ==============================================================================
proxy:
  enabled: true                # Start & manage shared Traefik proxy (default: true)
  ssl: true                    # Auto-provision Let's Encrypt SSL (default: true)
  email: admin@myapp.com       # ACME registration email
  challenge: http              # SSL challenge type: "http" | "dns" | "tlsalpn"
  entrypoints:
    web: 80
    websecure: 443
  cross_host: false            # Route requests across all cluster nodes
  private_ips:                 # VPC IPs for cross-host load balancing
    203.0.113.10: 10.0.0.10

# ==============================================================================
# Automated DNS Synchronization (Cloudflare / Hetzner)
# ==============================================================================
dns:
  provider: cloudflare         # "cloudflare" | "hetzner"
  api_token: ${DNS_API_TOKEN}  # Provider API token
  proxied: true                # Cloudflare CDN orange-cloud (default: true)
  ttl: 300                     # DNS TTL in seconds
  auto_sync: true              # Sync on epd deploy and epd setup (default: true)

# ==============================================================================
# Hetzner Cloud Integration
# ==============================================================================
hetzner:
  api_token: ${HETZNER_API_TOKEN}

# ==============================================================================
# Health Check Probes
# ==============================================================================
healthcheck:
  path: /health                # HTTP endpoint to probe (default: "/")
  status: 200-399              # Acceptable HTTP status codes
  timeout: 60                  # Timeout in seconds before rolling back
  interval: 2                  # Seconds between probe attempts
  delay: 1                     # Initial seconds before first probe

# ==============================================================================
# Environment Variables & Secrets
# ==============================================================================
env:
  clear:                       # Public variables (safe in git)
    NODE_ENV: production
  secret:                      # Private secrets (read from .env or shell)
    - DATABASE_URL
    - STRIPE_KEY

# ==============================================================================
# Persistent Accessories (Databases, Caches)
# ==============================================================================
accessories:
  db:
    image: postgres:17-alpine
    host: 203.0.113.10
    env:
      clear:
        POSTGRES_DB: myapp
      secret:
        - POSTGRES_PASSWORD
    volumes:
      - /var/lib/epd/volumes/myapp-db:/var/lib/postgresql/data
    ports:
      - "127.0.0.1:5432:5432"

  redis:
    image: redis:7-alpine
    host: 203.0.113.10
    volumes:
      - /var/lib/epd/volumes/myapp-redis:/data

# ==============================================================================
# Lifecycle Hooks
# ==============================================================================
hooks:
  pre_build: ./scripts/lint.sh
  pre_deploy: ./scripts/db-migrate.sh
  post_deploy: ./scripts/notify-slack.sh
  on_failure: ./scripts/alert-ops.sh

# ==============================================================================
# General Strategy & Retention
# ==============================================================================
strategy: rolling              # Rollout strategy: "rolling" | "parallel"
keep_releases: 5               # Number of past images / releases to retain
remote_root: /var/lib/epd      # Root directory on remote hosts

Released under the MIT License.