services: traefik: image: traefik:2.11 container_name: traefik ports: - "${TRAEFIK_WEB_PORT:-80}:80" - "${TRAEFIK_WEBSECURE_PORT:-443}:443" - "${TRAEFIK_WEBSECURE_STAGING_PORT:-4433}:4433" # Prometheus metrics endpoint (local dev) - "${TRAEFIK_METRICS_PORT:-9100}:9100" volumes: - /var/run/docker.sock:/var/run/docker.sock:ro - ./services/traefik/traefik.yml:/etc/traefik/traefik.yml:ro - ./services/traefik/dynamic.yml:/etc/traefik/dynamic.yml:ro - ./services/traefik/acme.json:/acme.json - ./services/traefik/acme-io.json:/acme-io.json labels: - "traefik.enable=true" # Dashboard over HTTPS (production) - "traefik.http.routers.traefik.rule=Host(`traefik.truckwash.dk`)" - "traefik.http.routers.traefik.entrypoints=websecure" - "traefik.http.routers.traefik.tls=true" - "traefik.http.routers.traefik.tls.certresolver=le" - "traefik.http.routers.traefik.service=api@internal" - "traefik.http.routers.traefik.middlewares=dashboard-allow-local@file,dashboard-auth@file" # Dashboard over HTTP (dev) -> redirect to HTTPS - "traefik.http.routers.traefik-http.rule=Host(`traefik.truckwash.dk`)" - "traefik.http.routers.traefik-http.entrypoints=web" - "traefik.http.routers.traefik-http.middlewares=redirect-to-https@file" - "traefik.http.routers.traefik-http.service=api@internal" # Local dashboard on traefik.localhost (HTTP only for dev) - "traefik.http.routers.traefik-local.rule=Host(`traefik.localhost`)" - "traefik.http.routers.traefik-local.entrypoints=web" - "traefik.http.routers.traefik-local.service=api@internal" - "traefik.http.routers.traefik-local.middlewares=dashboard-allow-local@file,dashboard-auth@file" # Broker API (handled in edge-broker service) - "traefik.http.routers.edge-broker.rule=Host(`api.truckwash.dk`) && PathPrefix(`/edge-broker`)" - "traefik.http.routers.edge-broker.entrypoints=websecure" - "traefik.http.routers.edge-broker.tls=true" - "traefik.http.routers.edge-broker.tls.certresolver=le" - "traefik.http.routers.edge-broker.middlewares=secure-headers@file,edge-broker-strip" - "traefik.http.routers.edge-broker.priority=200" - "traefik.http.routers.edge-broker.service=edge-broker" - "traefik.http.routers.edge-broker-io.rule=Host(`api.truckwash.io`) && PathPrefix(`/edge-broker`)" - "traefik.http.routers.edge-broker-io.entrypoints=websecure" - "traefik.http.routers.edge-broker-io.tls=true" - "traefik.http.routers.edge-broker-io.tls.certresolver=le_io" - "traefik.http.routers.edge-broker-io.middlewares=secure-headers@file,edge-broker-strip" - "traefik.http.routers.edge-broker-io.priority=200" - "traefik.http.routers.edge-broker-io.service=edge-broker" - "traefik.http.routers.edge-broker-staging.rule=Host(`api.truckwash.io`) && PathPrefix(`/edge-broker`)" - "traefik.http.routers.edge-broker-staging.entrypoints=websecure-staging" - "traefik.http.routers.edge-broker-staging.tls=true" - "traefik.http.routers.edge-broker-staging.tls.certresolver=le_io" - "traefik.http.routers.edge-broker-staging.middlewares=secure-headers@file,edge-broker-strip" - "traefik.http.routers.edge-broker-staging.priority=200" - "traefik.http.routers.edge-broker-staging.service=edge-broker" - "traefik.http.routers.edge-broker-local.rule=Host(`localhost`) && PathPrefix(`/api/edge-broker`)" - "traefik.http.routers.edge-broker-local.entrypoints=web" - "traefik.http.routers.edge-broker-local.middlewares=secure-headers@file,edge-broker-strip-local" - "traefik.http.routers.edge-broker-local.priority=200" - "traefik.http.routers.edge-broker-local.service=edge-broker" - "traefik.http.routers.edge-broker-local-secure.rule=Host(`localhost`) && PathPrefix(`/api/edge-broker`)" redis: image: redis:7 container_name: redis # ports: # - "6379:6379" volumes: - nnks_redis:/data healthcheck: test: ["CMD", "redis-cli", "ping"] interval: 10s timeout: 5s retries: 5 start_period: 10s redis-staging: image: redis:7 container_name: redis-staging # ports: # - "6380:6379" volumes: - nnks_redis_staging:/data healthcheck: test: ["CMD", "redis-cli", "ping"] interval: 10s timeout: 5s retries: 5 start_period: 10s mysql-debug: image: mysql:8.4 container_name: mysql-debug environment: MYSQL_ROOT_PASSWORD: ${CONFIG_DB_DEBUG_PASSWORD:-debug_root_password} MYSQL_DATABASE: ${CONFIG_DB_DEBUG_DATABASE:-nnks_db_debug} ports: - "3307:3306" volumes: - db_debug_data:/var/lib/mysql healthcheck: test: ["CMD-SHELL", "MYSQL_PWD=$$MYSQL_ROOT_PASSWORD mysqladmin -u root ping --silent"] interval: 10s timeout: 5s retries: 10 start_period: 20s edge-broker: build: context: . dockerfile: services/edge-broker/Dockerfile container_name: edge-broker environment: EDGE_AUTH_MODE: ${EDGE_AUTH_MODE:-manager} EDGE_MANAGER_URL: ${EDGE_MANAGER_URL:-http://caddy} EDGE_BROKER_SHARED_SECRET: ${EDGE_BROKER_SHARED_SECRET:-truckwash-edge-dev} labels: - "traefik.enable=true" - "traefik.http.routers.edge-broker-api.rule=Host(`api.truckwash.dk`) && PathPrefix(`/edge-broker`)" - "traefik.http.routers.edge-broker-api.entrypoints=websecure" - "traefik.http.routers.edge-broker-api.tls=true" - "traefik.http.routers.edge-broker-api.tls.certresolver=le" - "traefik.http.routers.edge-broker-api.middlewares=secure-headers@file,edge-broker-strip" - "traefik.http.routers.edge-broker-api.priority=200" - "traefik.http.routers.edge-broker-api.service=edge-broker" - "traefik.http.routers.edge-broker-api-io.rule=Host(`api.truckwash.io`) && PathPrefix(`/edge-broker`)" - "traefik.http.routers.edge-broker-api-io.entrypoints=websecure" - "traefik.http.routers.edge-broker-api-io.tls=true" - "traefik.http.routers.edge-broker-api-io.tls.certresolver=le_io" - "traefik.http.routers.edge-broker-api-io.middlewares=secure-headers@file,edge-broker-strip" - "traefik.http.routers.edge-broker-api-io.priority=200" - "traefik.http.routers.edge-broker-api-io.service=edge-broker" - "traefik.http.routers.edge-broker-api-staging.rule=Host(`api.truckwash.io`) && PathPrefix(`/edge-broker`)" - "traefik.http.routers.edge-broker-api-staging.entrypoints=websecure-staging" - "traefik.http.routers.edge-broker-api-staging.tls=true" - "traefik.http.routers.edge-broker-api-staging.tls.certresolver=le_io" - "traefik.http.routers.edge-broker-api-staging.middlewares=secure-headers@file,edge-broker-strip" - "traefik.http.routers.edge-broker-api-staging.priority=200" - "traefik.http.routers.edge-broker-api-staging.service=edge-broker" - "traefik.http.routers.edge-broker-local.rule=Host(`localhost`) && PathPrefix(`/api/edge-broker`)" - "traefik.http.routers.edge-broker-local.entrypoints=web" - "traefik.http.routers.edge-broker-local.middlewares=secure-headers@file,edge-broker-strip-local" - "traefik.http.routers.edge-broker-local.priority=200" - "traefik.http.routers.edge-broker-local.service=edge-broker" - "traefik.http.routers.edge-broker-local-secure.rule=Host(`localhost`) && PathPrefix(`/api/edge-broker`)" - "traefik.http.routers.edge-broker-local-secure.entrypoints=websecure" - "traefik.http.routers.edge-broker-local-secure.tls=true" - "traefik.http.routers.edge-broker-local-secure.middlewares=secure-headers@file,edge-broker-strip-local" - "traefik.http.routers.edge-broker-local-secure.priority=200" - "traefik.http.routers.edge-broker-local-secure.service=edge-broker" - "traefik.http.routers.edge-broker-local-staging.rule=Host(`localhost`) && PathPrefix(`/api/edge-broker`)" - "traefik.http.routers.edge-broker-local-staging.entrypoints=websecure-staging" - "traefik.http.routers.edge-broker-local-staging.tls=true" - "traefik.http.routers.edge-broker-local-staging.middlewares=secure-headers@file,edge-broker-strip-local" - "traefik.http.routers.edge-broker-local-staging.priority=200" - "traefik.http.routers.edge-broker-local-staging.service=edge-broker" - "traefik.http.middlewares.edge-broker-strip.stripPrefix.prefixes=/edge-broker" - "traefik.http.middlewares.edge-broker-strip-local.stripPrefix.prefixes=/api/edge-broker" - "traefik.http.services.edge-broker.loadbalancer.server.port=4300" caddy: image: caddy:2.7.6-alpine container_name: caddy depends_on: - php1 - php2 - php3 - php4 - php5 volumes: - ./services/nginx/app:/var/www/html - ./services/caddy:/etc/caddy:ro - ./services/caddy/logs:/var/log/caddy labels: - "traefik.enable=true" # Public API (HTTPS via Traefik + LE) - "traefik.http.routers.api.rule=Host(`api.truckwash.dk`)" - "traefik.http.routers.api.entrypoints=websecure" - "traefik.http.routers.api.tls=true" - "traefik.http.routers.api.tls.domains[0].main=api.truckwash.dk" - "traefik.http.routers.api.tls.certresolver=le" - "traefik.http.routers.api.service=caddy" - "traefik.http.routers.api.middlewares=secure-headers@file,api-ratelimit@file" # Public API (.io version) - "traefik.http.routers.api-io.rule=Host(`api.truckwash.io`)" - "traefik.http.routers.api-io.entrypoints=websecure" - "traefik.http.routers.api-io.tls=true" - "traefik.http.routers.api-io.tls.domains[0].main=api.truckwash.io" - "traefik.http.routers.api-io.tls.certresolver=le_io" - "traefik.http.routers.api-io.service=caddy" - "traefik.http.routers.api-io.middlewares=secure-headers@file,api-ratelimit@file" # HTTP to HTTPS redirect for both API domains - "traefik.http.routers.api-http.rule=Host(`api.truckwash.dk`) || Host(`api.truckwash.io`)" - "traefik.http.routers.api-http.entrypoints=web" - "traefik.http.routers.api-http.middlewares=redirect-to-https@file" - "traefik.http.routers.api-http.service=caddy" # Local development (HTTP only) - "traefik.http.routers.local.rule=Host(`localhost`)" - "traefik.http.routers.local.entrypoints=web" - "traefik.http.routers.local.service=caddy" - "traefik.http.routers.local.middlewares=secure-headers@file" # Local development over HTTPS (self-signed/default Traefik cert) - "traefik.http.routers.local-secure.rule=Host(`localhost`)" - "traefik.http.routers.local-secure.entrypoints=websecure" - "traefik.http.routers.local-secure.tls=true" - "traefik.http.routers.local-secure.service=caddy" - "traefik.http.routers.local-secure.middlewares=secure-headers@file" # Local alias: http://localhost/api -> Caddy (strip /api prefix) - "traefik.http.routers.local-api.rule=Host(`localhost`) && PathPrefix(`/api`)" - "traefik.http.routers.local-api.entrypoints=web" - "traefik.http.routers.local-api.middlewares=strip-api-prefix@file,secure-headers@file" - "traefik.http.routers.local-api.service=caddy" - "traefik.http.routers.local-api.priority=100" # Local alias over HTTPS as well: https://localhost/api -> Caddy (strip /api prefix) - "traefik.http.routers.local-api-secure.rule=Host(`localhost`) && PathPrefix(`/api`)" - "traefik.http.routers.local-api-secure.entrypoints=websecure" - "traefik.http.routers.local-api-secure.tls=true" - "traefik.http.routers.local-api-secure.middlewares=strip-api-prefix@file,secure-headers@file" - "traefik.http.routers.local-api-secure.service=caddy" - "traefik.http.routers.local-api-secure.priority=100" # Tell Traefik which port Caddy listens on - "traefik.http.services.caddy.loadbalancer.server.port=80" caddy-staging: image: caddy:2.7.6-alpine container_name: caddy-staging depends_on: - php-staging command: ["caddy", "run", "--config", "/etc/caddy/Caddyfile-staging", "--adapter", "caddyfile"] volumes: - ./services/nginx/staging:/var/www/html - ./services/caddy:/etc/caddy:ro - ./services/caddy/logs-staging:/var/log/caddy labels: - "traefik.enable=true" # Staging API (.io version on port 4433) - "traefik.http.routers.api-staging.rule=Host(`api.truckwash.io`)" - "traefik.http.routers.api-staging.entrypoints=websecure-staging" - "traefik.http.routers.api-staging.tls=true" - "traefik.http.routers.api-staging.tls.domains[0].main=api.truckwash.io" - "traefik.http.routers.api-staging.tls.certresolver=le_io" - "traefik.http.routers.api-staging.service=caddy-staging" - "traefik.http.routers.api-staging.middlewares=secure-headers@file,api-ratelimit@file" # Local staging development (HTTP on port 4433) - "traefik.http.routers.local-staging.rule=Host(`localhost`)" - "traefik.http.routers.local-staging.entrypoints=websecure-staging" - "traefik.http.routers.local-staging.service=caddy-staging" - "traefik.http.routers.local-staging.middlewares=secure-headers@file" # Local staging development (HTTPS on port 4433) - "traefik.http.routers.local-staging-secure.rule=Host(`localhost`)" - "traefik.http.routers.local-staging-secure.entrypoints=websecure-staging" - "traefik.http.routers.local-staging-secure.tls=true" - "traefik.http.routers.local-staging-secure.service=caddy-staging" - "traefik.http.routers.local-staging-secure.middlewares=secure-headers@file" # Local staging alias: http://localhost:4433/api -> Caddy staging (strip /api prefix) - "traefik.http.routers.local-staging-api.rule=Host(`localhost`) && PathPrefix(`/api`)" - "traefik.http.routers.local-staging-api.entrypoints=websecure-staging" - "traefik.http.routers.local-staging-api.middlewares=strip-api-prefix@file,secure-headers@file" - "traefik.http.routers.local-staging-api.service=caddy-staging" - "traefik.http.routers.local-staging-api.priority=100" # Local staging alias over HTTPS: https://localhost:4433/api -> Caddy staging (strip /api prefix) - "traefik.http.routers.local-staging-api-secure.rule=Host(`localhost`) && PathPrefix(`/api`)" - "traefik.http.routers.local-staging-api-secure.entrypoints=websecure-staging" - "traefik.http.routers.local-staging-api-secure.tls=true" - "traefik.http.routers.local-staging-api-secure.middlewares=strip-api-prefix@file,secure-headers@file" - "traefik.http.routers.local-staging-api-secure.service=caddy-staging" - "traefik.http.routers.local-staging-api-secure.priority=100" # Tell Traefik which port Caddy listens on - "traefik.http.services.caddy-staging.loadbalancer.server.port=80" php1: build: context: . dockerfile: services/php/Dockerfile container_name: php1 depends_on: - redis - edge-broker command: ["php-fpm"] env_file: - .env environment: AUTO_COMPOSER_INSTALL: "true" EDGE_BROKER_URL: ${EDGE_BROKER_URL:-http://edge-broker:4300} EDGE_BROKER_SHARED_SECRET: ${EDGE_BROKER_SHARED_SECRET:-truckwash-edge-dev} volumes: - ./services/nginx/app:/var/www/html - ./services/php/php.ini:/usr/local/etc/php/conf.d/zz-custom.ini:ro - ./services/php/logs:/var/log/php php2: build: context: . dockerfile: services/php/Dockerfile container_name: php2 depends_on: - redis - edge-broker command: ["php-fpm"] env_file: - .env environment: AUTO_COMPOSER_INSTALL: "false" EDGE_BROKER_URL: ${EDGE_BROKER_URL:-http://edge-broker:4300} EDGE_BROKER_SHARED_SECRET: ${EDGE_BROKER_SHARED_SECRET:-truckwash-edge-dev} volumes: - ./services/nginx/app:/var/www/html - ./services/php/php.ini:/usr/local/etc/php/conf.d/zz-custom.ini:ro - ./services/php/logs:/var/log/php php3: build: context: . dockerfile: services/php/Dockerfile container_name: php3 depends_on: - redis - edge-broker command: ["php-fpm"] env_file: - .env environment: AUTO_COMPOSER_INSTALL: "false" EDGE_BROKER_URL: ${EDGE_BROKER_URL:-http://edge-broker:4300} EDGE_BROKER_SHARED_SECRET: ${EDGE_BROKER_SHARED_SECRET:-truckwash-edge-dev} volumes: - ./services/nginx/app:/var/www/html - ./services/php/php.ini:/usr/local/etc/php/conf.d/zz-custom.ini:ro - ./services/php/logs:/var/log/php php4: build: context: . dockerfile: services/php/Dockerfile container_name: php4 depends_on: - redis - edge-broker command: ["php-fpm"] env_file: - .env environment: AUTO_COMPOSER_INSTALL: "false" EDGE_BROKER_URL: ${EDGE_BROKER_URL:-http://edge-broker:4300} EDGE_BROKER_SHARED_SECRET: ${EDGE_BROKER_SHARED_SECRET:-truckwash-edge-dev} volumes: - ./services/nginx/app:/var/www/html - ./services/php/php.ini:/usr/local/etc/php/conf.d/zz-custom.ini:ro - ./services/php/logs:/var/log/php php5: build: context: . dockerfile: services/php/Dockerfile container_name: php5 depends_on: - redis - edge-broker command: ["php-fpm"] env_file: - .env environment: AUTO_COMPOSER_INSTALL: "false" EDGE_BROKER_URL: ${EDGE_BROKER_URL:-http://edge-broker:4300} EDGE_BROKER_SHARED_SECRET: ${EDGE_BROKER_SHARED_SECRET:-truckwash-edge-dev} volumes: - ./services/nginx/app:/var/www/html - ./services/php/php.ini:/usr/local/etc/php/conf.d/zz-custom.ini:ro - ./services/php/logs:/var/log/php php-staging: build: context: . dockerfile: services/php/Dockerfile container_name: php-staging depends_on: - redis-staging - edge-broker command: ["php-fpm"] env_file: - .env.staging environment: AUTO_COMPOSER_INSTALL: "false" EDGE_BROKER_URL: ${EDGE_BROKER_URL:-http://edge-broker:4300} EDGE_BROKER_SHARED_SECRET: ${EDGE_BROKER_SHARED_SECRET:-truckwash-edge-dev} volumes: - ./services/nginx/staging:/var/www/html - ./services/php/php.ini:/usr/local/etc/php/conf.d/zz-custom.ini:ro - ./services/php/logs-staging:/var/log/php php-cron: build: context: . dockerfile: services/php/Dockerfile container_name: php-cron depends_on: - redis - edge-broker command: ["sh", "-c", "while true; do php index.php run cron; sleep 60; done"] env_file: - .env environment: AUTO_COMPOSER_INSTALL: "false" EDGE_BROKER_URL: ${EDGE_BROKER_URL:-http://edge-broker:4300} EDGE_BROKER_SHARED_SECRET: ${EDGE_BROKER_SHARED_SECRET:-truckwash-edge-dev} volumes: - ./services/nginx/app:/var/www/html - ./services/php/php.ini:/usr/local/etc/php/conf.d/zz-custom.ini:ro - ./services/php/logs:/var/log/php portainer: image: portainer/portainer-ce:2.21.4 container_name: portainer profiles: - dev ports: - "9443:9443" - "9000:9000" volumes: - /var/run/docker.sock:/var/run/docker.sock - portainer_data:/data # Jaeger all-in-one for local tracing (Traefik → Jaeger) jaeger: image: jaegertracing/all-in-one:1.53 container_name: jaeger profiles: - dev environment: - COLLECTOR_ZIPKIN_HTTP_PORT=9411 ports: - "16686:16686" # Jaeger UI # No volumes needed for dev; data is ephemeral n8n: image: n8nio/n8n:latest container_name: n8n restart: always environment: - N8N_HOST=n8n.truckwash.io - N8N_PORT=5678 - N8N_PROTOCOL=https - NODE_ENV=production - WEBHOOK_URL=https://n8n.truckwash.io/ - GENERIC_TIMEZONE=${CONFIG_TIMEZONE:-Europe/Copenhagen} volumes: - n8n_data:/home/node/.n8n labels: - "traefik.enable=true" # n8n over HTTPS (le_io cert resolver) - "traefik.http.routers.n8n.rule=Host(`n8n.truckwash.io`)" - "traefik.http.routers.n8n.entrypoints=websecure" - "traefik.http.routers.n8n.tls=true" - "traefik.http.routers.n8n.tls.certresolver=le_io" - "traefik.http.routers.n8n.service=n8n" # n8n HTTP to HTTPS redirect - "traefik.http.routers.n8n-http.rule=Host(`n8n.truckwash.io`)" - "traefik.http.routers.n8n-http.entrypoints=web" - "traefik.http.routers.n8n-http.middlewares=redirect-to-https@file" - "traefik.http.routers.n8n-http.service=n8n" # n8n service port - "traefik.http.services.n8n.loadbalancer.server.port=5678" volumes: db_data: db_debug_data: nnks_redis: nnks_redis_staging: es_data: portainer_data: fleet-server-data: elastic-agent-data: n8n_data: