# Local / single-host deployment. # # Secrets come from .env (gitignored). Copy .env.example to .env and set a # strong POSTGRES_PASSWORD plus a matching DATABASE_URL before `docker compose # up`. For a production database, point DATABASE_URL at a TLS-enabled endpoint, # e.g. postgres://user:pass@db.example.com:5432/ctcloud?sslmode=verify-full # # The compose bridge network is private; sslmode=disable is only acceptable here # because the database is not reachable from outside the stack. services: postgres: image: postgres:17-alpine environment: POSTGRES_USER: ${POSTGRES_USER:-ctcloud} POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD in .env} POSTGRES_DB: ${POSTGRES_DB:-ctcloud} volumes: - pgdata:/var/lib/postgresql/data healthcheck: test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-ctcloud} -d ${POSTGRES_DB:-ctcloud}"] interval: 5s timeout: 5s retries: 12 mem_limit: 1g cpus: 2.0 pids_limit: 256 logging: driver: json-file options: { max-size: "10m", max-file: "3" } restart: unless-stopped ctcloud: build: . depends_on: postgres: condition: service_healthy environment: DATABASE_URL: ${DATABASE_URL:?set DATABASE_URL in .env} METRICS_ADDR: ":9090" LOG_LEVEL: ${LOG_LEVEL:-info} DNS_SERVERS: ${DNS_SERVERS:-1.1.1.1:53,8.8.8.8:53,1.0.0.1:53} DNS_WORKERS: ${DNS_WORKERS:-16} ACTIVE_PROBING: ${ACTIVE_PROBING:-false} # START_AT_END: "true" # skip historical certificates on first run # Metrics/health are bound to loopback only: scrape from the host or a # sidecar, never expose them publicly (there is no auth on this listener). ports: - "127.0.0.1:9090:9090" # No application volume: the container is stateless apart from PostgreSQL. read_only: true tmpfs: - /tmp:size=16m,noexec,nosuid security_opt: - no-new-privileges:true cap_drop: - ALL mem_limit: 1g cpus: 2.0 pids_limit: 512 logging: driver: json-file options: { max-size: "10m", max-file: "3" } restart: unless-stopped volumes: pgdata: