######################## # Networks & Volumes ######################## networks: traefik_proxy: name: traefik_proxy internal: name: internal volumes: traefik_letsencrypt: traefik_logs: portainer_data: uptime_kuma_data: ######################## # Services ######################## services: ## ───────────────────────────────────────────── ## Traefik — edge router + ACME (HTTP-01) ## ───────────────────────────────────────────── traefik: image: traefik:v3.1 container_name: traefik restart: unless-stopped ports: - "80:80" - "443:443" networks: [traefik_proxy] environment: TZ: "${TZ}" command: # Providers - --providers.docker=true - --providers.docker.exposedbydefault=false # Entrypoints - --entrypoints.web.address=:80 - --entrypoints.web.http.redirections.entrypoint.to=websecure - --entrypoints.web.http.redirections.entrypoint.scheme=https - --entrypoints.websecure.address=:443 - --entrypoints.web.forwardedheaders.insecure=true - --entrypoints.websecure.forwardedheaders.insecure=true # Dashboard/API (internal) - --api.dashboard=true # ACME via HTTP-01 (no registrar API needed) - --certificatesresolvers.le.acme.email=${ACME_EMAIL} - --certificatesresolvers.le.acme.storage=/letsencrypt/acme.json - --certificatesresolvers.le.acme.httpchallenge=true - --certificatesresolvers.le.acme.httpchallenge.entrypoint=web # (Alt) Use TLS-ALPN-01 if port 80 is blocked: # - --certificatesresolvers.le.acme.tlschallenge=true # Global timeouts for slow backends - --serversTransport.forwardingTimeouts.dialTimeout=30s - --serversTransport.forwardingTimeouts.responseHeaderTimeout=60s - --serversTransport.forwardingTimeouts.idleConnTimeout=180s # Logs - --accesslog.filepath=/var/log/traefik/access.log - --accesslog.bufferingsize=100 - --log.level=INFO volumes: - /var/run/docker.sock:/var/run/docker.sock:ro - traefik_letsencrypt:/letsencrypt - traefik_logs:/var/log/traefik labels: - traefik.enable=true # Reusable security headers - traefik.http.middlewares.security-headers.headers.stsSeconds=31536000 - traefik.http.middlewares.security-headers.headers.stsIncludeSubdomains=true - traefik.http.middlewares.security-headers.headers.stsPreload=true - traefik.http.middlewares.security-headers.headers.browserXssFilter=true - traefik.http.middlewares.security-headers.headers.contentTypeNosniff=true - traefik.http.middlewares.security-headers.headers.referrerPolicy=no-referrer-when-downgrade - traefik.http.middlewares.security-headers.headers.frameDeny=true # Traefik dashboard (protected) - traefik.http.routers.traefik.rule=Host(`traefik.gate.${DOMAIN}`) - traefik.http.routers.traefik.entrypoints=websecure - traefik.http.routers.traefik.tls.certresolver=le - traefik.http.routers.traefik.service=api@internal - traefik.http.routers.traefik.middlewares=security-headers ## ───────────────────────────────────────────── ## Portainer — Docker control plane ## ───────────────────────────────────────────── portainer: image: portainer/portainer-ce:latest container_name: portainer restart: unless-stopped networks: [traefik_proxy] volumes: - /var/run/docker.sock:/var/run/docker.sock - portainer_data:/data labels: - traefik.enable=true - traefik.http.routers.portainer.rule=Host(`portainer.gate.${DOMAIN}`) - traefik.http.routers.portainer.entrypoints=websecure - traefik.http.routers.portainer.tls.certresolver=le - traefik.http.routers.portainer.middlewares=authelia,security-headers - traefik.http.services.portainer.loadbalancer.server.port=9000 ## ───────────────────────────────────────────── ## Authelia — authentication and authorization ## ───────────────────────────────────────────── authelia: image: authelia/authelia:latest container_name: authelia restart: unless-stopped networks: [traefik_proxy, internal] volumes: - ./authelia:/config environment: TZ: "${TZ}" AUTHELIA_JWT_SECRET: "${AUTHELIA_JWT_SECRET}" AUTHELIA_SESSION_SECRET: "${AUTHELIA_SESSION_SECRET}" AUTHELIA_STORAGE_ENCRYPTION_KEY: "${AUTHELIA_STORAGE_ENCRYPTION_KEY}" DOMAIN: "${DOMAIN}" healthcheck: test: ["CMD-SHELL", "wget --no-verbose --tries=1 --spider http://localhost:9091/api/health || exit 1"] interval: 30s timeout: 10s retries: 5 start_period: 60s labels: - traefik.enable=true - traefik.http.routers.authelia.rule=Host(`auth.gate.${DOMAIN}`) - traefik.http.routers.authelia.entrypoints=websecure - traefik.http.routers.authelia.tls.certresolver=le - traefik.http.routers.authelia.middlewares=security-headers - traefik.http.services.authelia.loadbalancer.server.port=9091 # ForwardAuth middleware for protecting other services - traefik.http.middlewares.authelia.forwardauth.address=http://authelia:9091/api/authz/forward-auth - traefik.http.middlewares.authelia.forwardauth.trustForwardHeader=true - traefik.http.middlewares.authelia.forwardauth.authResponseHeaders=Remote-User,Remote-Groups,Remote-Email,Remote-Name ## ───────────────────────────────────────────── ## Uptime Kuma — status page / checks ## ───────────────────────────────────────────── uptime-kuma: image: louislam/uptime-kuma:1 container_name: uptime-kuma restart: unless-stopped volumes: - uptime_kuma_data:/app/data networks: [traefik_proxy] labels: - traefik.enable=true - traefik.http.routers.kuma.rule=Host(`uptime.gate.${DOMAIN}`) - traefik.http.routers.kuma.entrypoints=websecure - traefik.http.routers.kuma.tls.certresolver=le - traefik.http.routers.kuma.middlewares=authelia,security-headers - traefik.http.services.kuma.loadbalancer.server.port=3001