services: # ────────────────────────── front-end ────────────────────────── caddy: image: caddy:2.10.0-alpine container_name: caddy ports: - "80:80" - "443:443" volumes: - ./docker/caddy/Caddyfile:/etc/caddy/Caddyfile:ro - caddy_data:/data # ACME certs - caddy_config:/config - ./public:/srv # static assets networks: [web] nginx: image: nginx:1.28.3-alpine container_name: nginx depends_on: [caddy, php] expose: - "8080" volumes: - ./docker/nginx/nginx.conf:/etc/nginx/nginx.conf:ro - ./public:/srv:ro networks: [web, app] # ─────────────────────────── PHP / FPM ───────────────────────── php: build: ./docker/php # see Dockerfile below container_name: php volumes: - .:/var/www/html environment: APP_ENV: ${APP_ENV:-local} DB_HOST: db DB_DATABASE: ${DB_DATABASE:-easymonitor} DB_USERNAME: ${DB_USERNAME:-easymonitor} DB_PASSWORD: ${DB_PASSWORD:-secret} REDIS_HOST: redis REDIS_PASSWORD: ${REDIS_PASSWORD:-} networks: [app] depends_on: db: condition: service_started redis: condition: service_healthy # Give Horizon time to finish in-flight jobs and deregister itself # from Redis (supervisord stopwaitsecs=60) before Docker kills it. stop_grace_period: 75s # ────────────────────────── data layer ───────────────────────── db: image: timescale/timescaledb:2.26.0-pg18 # PG18 + Timescale 2.26.0 container_name: db ports: - "5432:5432" environment: POSTGRES_USER: ${DB_USERNAME:-easymonitor} POSTGRES_PASSWORD: ${DB_PASSWORD:-secret} POSTGRES_DB: ${DB_DATABASE:-easymonitor} volumes: # timescale/timescaledb:*-pg18 sets PGDATA=/var/lib/postgresql/18/docker # (the image also declares /var/lib/postgresql as a VOLUME, which would # otherwise swallow the data into an anonymous volume on every restart). - db_data:/var/lib/postgresql/18/docker - ./docker/db/init.sh:/docker-entrypoint-initdb.d/999-init.sh:ro networks: [app] redis: image: redis:7.4-alpine container_name: redis environment: REDIS_PASSWORD: ${REDIS_PASSWORD:-} # Run redis-server with --requirepass only when REDIS_PASSWORD is set. # The $$ lets the shell (inside the container) see $REDIS_PASSWORD # instead of Docker Compose trying to substitute it. command: > sh -c ' if [ -n "$$REDIS_PASSWORD" ]; then exec redis-server --appendonly yes --requirepass "$$REDIS_PASSWORD" else exec redis-server --appendonly yes fi ' volumes: - redis_data:/data networks: [app] # Healthy only once the dataset is loaded: while loading, PING # answers "LOADING ..." instead of PONG. healthcheck: test: - CMD-SHELL - '[ -n "$$REDIS_PASSWORD" ] && export REDISCLI_AUTH="$$REDIS_PASSWORD"; redis-cli ping | grep -q PONG' interval: 5s timeout: 3s retries: 30 start_period: 10s # ──────────────────────────── probe node ─────────────────────────── # The probe node is enabled by default for immediate monitoring capability. # To disable it, use: docker compose up -d --scale probe=0 probe: build: ./probe-node container_name: probe environment: NODE_ID: ${PROBE_NODE_ID:-local-node-1} REDIS_URL: ${PROBE_REDIS_URL:-redis://redis:6379/0} REDIS_PASSWORD: ${REDIS_PASSWORD:-} JWT_TOKEN: ${PROBE_JWT_TOKEN:-} DEFAULT_TIMEOUT: 30s BATCH_SIZE: 10 MAX_CONCURRENCY: 10 HEALTH_CHECK_PORT: 8080 depends_on: redis: condition: service_healthy networks: [app] restart: unless-stopped healthcheck: test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:8080/health"] interval: 30s timeout: 5s retries: 3 start_period: 5s networks: web: app: volumes: db_data: redis_data: caddy_data: caddy_config: