diff --git a/traefik/SKILL.md b/traefik/SKILL.md index 0b422a4..bb81969 100644 --- a/traefik/SKILL.md +++ b/traefik/SKILL.md @@ -1,14 +1,16 @@ --- name: traefik description: >- - Deploy, configure, secure, and maintain Traefik v3 reverse proxy — Docker - provider, HTTP/TCP/UDP routing, TLS/ACME (Let's Encrypt), middlewares, - observability, API, and production deployment. Load when configuring or - troubleshooting a Traefik instance. + Deploy, configure, and troubleshoot Traefik v3 reverse proxy — covers all + providers, routing, TLS/ACME, middlewares, and production patterns with + YAML examples. Load when setting up or debugging a Traefik instance. license: MIT +version: 0.1.0 +compatibility: >- + Compatible with any agent supporting the Agent Skills format (Hermes Agent, + Claude Code, GitHub Copilot, OpenCode, Cursor, etc.) metadata: source: https://doc.traefik.io/traefik/ - spec-version: "1.0" --- # Traefik Agent Skill @@ -17,11 +19,20 @@ Comprehensive reference for deploying, configuring, and maintaining **Traefik v3 ## Quick Start — Minimal Docker Deployment +A production-ready Docker Compose template is available at `templates/docker-compose.yml`. For a quick test: + +### One-Line Health Check + +```bash +bash scripts/traefik-healthcheck.sh # Text output +bash scripts/traefik-healthcheck.sh --json # JSON output for agents +``` + ```yaml # docker-compose.yml services: traefik: - image: traefik:v3.2 + image: traefik:v3.7 command: # Static configuration via CLI args - "--providers.docker=true" @@ -66,6 +77,7 @@ The request flow: `EntryPoint → Router → (Middlewares) → Service → Backe | **Observability** | Prometheus/OTel metrics, access logs, tracing, health checks | `references/observability.md` | | **v2→v3 Migration** | Breaking changes, rule syntax update, deprecated options | `references/migration-v2-to-v3.md` | | **Production Patterns** | Docker Compose template, security hardening, HA, monitoring | `references/production-deployment.md` | +| **Servers Transport** | Backend connection config, mTLS to backends, connection pooling, SPIFFE | `references/servers-transport.md` | | **Kubernetes Providers** | Deploying Traefik in K8s — Ingress, CRD (IngressRoute), Gateway API | `references/kubernetes-providers.md` | | **Other Providers** | ECS, Nomad, Consul Catalog, KV stores, File, HTTP, REST providers | `references/other-providers.md` | | **Community Patterns** | Production wisdom — middleware ordering, performance tuning, CDN real-IP, CrowdSec, Authelia, troubleshooting | `references/community-patterns.md` | @@ -88,4 +100,4 @@ The request flow: `EntryPoint → Router → (Middlewares) → Service → Backe ## When NOT to Use This Skill - For Traefik Hub, Traefik Enterprise, or Traefik Mesh — these are separate products with different APIs -- For writing Traefik plugins — see the official plugin development documentation at https://plugins.traefik.io/create +- For developing Traefik plugins (Yaegi or WASM) — this skill covers *using* configured plugins, not writing them. See https://plugins.traefik.io/create for plugin development. diff --git a/traefik/references/api-dashboard.md b/traefik/references/api-dashboard.md index 5a229a5..4dcc049 100644 --- a/traefik/references/api-dashboard.md +++ b/traefik/references/api-dashboard.md @@ -46,7 +46,7 @@ Or with Docker labels: ```yaml services: traefik: - image: traefik:v3.2 + image: traefik:v3.7 labels: - "traefik.http.routers.dashboard.rule=Host(`traefik.example.com`)" - "traefik.http.routers.dashboard.service=api@internal" diff --git a/traefik/references/community-patterns.md b/traefik/references/community-patterns.md index 616c67a..35b3baa 100644 --- a/traefik/references/community-patterns.md +++ b/traefik/references/community-patterns.md @@ -370,7 +370,7 @@ Request → Traefik → CrowdSec Bouncer (ForwardAuth) → Backend Service ```yaml services: traefik: - image: traefik:v3.2 + image: traefik:v3.7 volumes: - /var/run/docker.sock:/var/run/docker.sock - ./logs:/var/log/traefik diff --git a/traefik/references/docker-provider.md b/traefik/references/docker-provider.md index 744ac37..1a9fcf7 100644 --- a/traefik/references/docker-provider.md +++ b/traefik/references/docker-provider.md @@ -20,7 +20,7 @@ providers: ```yaml services: traefik: - image: traefik:v3.2 + image: traefik:v3.7 volumes: - /var/run/docker.sock:/var/run/docker.sock # Required - ./traefik.yml:/etc/traefik/traefik.yml # Static config diff --git a/traefik/references/migration-v2-to-v3.md b/traefik/references/migration-v2-to-v3.md index b54ee93..b731dc1 100644 --- a/traefik/references/migration-v2-to-v3.md +++ b/traefik/references/migration-v2-to-v3.md @@ -11,7 +11,27 @@ This covers the key changes when migrating from Traefik v2 to v3. Full details: | `Path` matcher wildcards | Supported | Removed (use `PathRegexp` or `PathPrefix`) | | `insecure` field renamed | N/A | `tls.insecureSkipVerify` on serversTransport | | X-Forwarded-For behavior | Always appended | Configurable via `forwardedHeaders` | -| Deprecated field removals | Various | Strictly enforced removal | +| Tracing providers | Jaeger, Zipkin, Datadog, Elastic | OpenTelemetry (OTLP) only | +| Content-Type handling | Auto-detected | Must use `ContentType` middleware | +| IPWhiteList renamed | `IPWhiteList` | `IPAllowList` | +| Provider removals | Marathon, Rancher v1, InfluxDB v1, Pilot | Removed entirely | +| `tls.caOptional` | Available | Removed from all providers | +| Deprecated field removals | Various | Strictly enforced — v3 refuses to start with deprecated fields | + +## Pre-Migration Audit Checklist + +Before upgrading, run through these checks: + +- [ ] **Rule syntax audit** — Search config files for `HostRegexp` with wildcard patterns (`.+\.`); replace with `Host(`*.`)` +- [ ] **Path wildcard audit** — Search for curly-brace path patterns like `/api/{version:...}`; these need `PathRegexp` replacements +- [ ] **TLS compatibility check** — Verify clients support TLS 1.2+ (default changed from TLS 1.0) +- [ ] **ServersTransport rename** — Replace all `insecure: true` with `insecureSkipVerify: true` +- [ ] **Tracing migration** — If using Jaeger/Zipkin/Datadog/Elastic APM, migrate to OpenTelemetry +- [ ] **ContentType audit** — If you relied on Traefik auto-detecting Content-Type, add the `ContentType` middleware +- [ ] **IPWhiteList → IPAllowList** — Rename any `ipWhiteList` config blocks to `ipAllowList` +- [ ] **Remove deprecated providers** — If using Marathon, Rancher, InfluxDB v1 metrics, or Pilot, find alternatives +- [ ] **Default certificate** — If you relied on SNI fallback, configure a default certificate explicitly +- [ ] **Test in staging** — Run a v3 instance alongside v2 with `core.defaultRuleSyntax: v2` for gradual migration ## Rule Syntax Changes @@ -21,10 +41,10 @@ The v3 rule syntax is the default. If you have v2 rules, they'll get a deprecati ```yaml # v2 — required HostRegexp for wildcards -rule: "HostRegexp(`.+\.example\.com`)" +rule: "HostRegexp(`.+\\.example\\.com`)" # v3 — native wildcard support (preferred) -rule: "Host(`*.example.com`)" +rule: "Host(`*.example.com`)" # matches foo.example.com, NOT foo.bar.example.com ``` ### Path Wildcards Removed @@ -45,7 +65,7 @@ To use v2 syntax during migration, set per-router: http: routers: legacy-router: - rule: "HostRegexp(`.+\.example\.com`)" + rule: "HostRegexp(`.+\\.example\\.com`)" ruleSyntax: "v2" # Per-router override ``` @@ -67,9 +87,9 @@ tls: minVersion: VersionTLS12 ``` -## TLS Certificate Fallback +### TLS Certificate Fallback -In v3, when no TLS certificate matches the requested SNI, Traefik v3 returns a certificate error instead of falling back to a default certificate. Configure a default certificate explicitly: +In v3, when no TLS certificate matches the requested SNI, Traefik returns a certificate error instead of falling back to a default certificate. Configure a default certificate explicitly: ```yaml tls: @@ -80,6 +100,37 @@ tls: keyFile: "/certs/default-key.pem" ``` +## Tracing Changes (OTLP Only) + +v3 removed direct support for Jaeger, Zipkin, Datadog APM, and Elastic APM tracing. All tracing now goes through OpenTelemetry (OTLP): + +```yaml +# v3 — OTLP only +tracing: + serviceName: "traefik" + sampleRate: 1.0 + otlp: + grpc: + endpoint: "localhost:4317" + insecure: true + http: + endpoint: "localhost:4318" +``` + +Migrate from Jaeger/Zipkin agents by running an OpenTelemetry Collector as a sidecar that forwards to your existing backend. + +## Content-Type Handling + +v3 removed automatic Content-Type detection. Responses without an explicit Content-Type header may be handled differently: + +```yaml +# v3 — add this middleware to preserve v2 behavior +http: + middlewares: + auto-content-type: + contentType: {} +``` + ## ServersTransport Changes The `insecure` field has been renamed for clarity: @@ -120,12 +171,26 @@ entryPoints: # When set to true, Traefik will NOT append client's RemoteAddr to X-Forwarded-For ``` +## Provider Removals + +The following providers were removed in v3 with no replacement: + +| Removed Provider | Alternative | +|-----------------|-------------| +| Marathon | Use File provider or migrate to Kubernetes/Nomad | +| Rancher v1 | Use File provider or Rancher v2's Kubernetes ingress | +| InfluxDB v1 metrics | Use InfluxDB v2 or Prometheus | +| Pilot | Use Traefik Hub or forward auth middlewares | + ## Migration Steps -1. **Update static config** — review all entryPoints, providers, and TLS settings -2. **Update rule syntax** — replace `HostRegexp` wildcards with `Host(*)`, remove wildcards from `Path` matchers -3. **Check TLS defaults** — verify min TLS version compatibility with clients -4. **Update ServersTransport** — rename `insecure` to `insecureSkipVerify` -5. **Test with staging** — use `core.defaultRuleSyntax: v2` during migration and test each router -6. **Add default certificate** — if you relied on the TLS fallback behavior -7. **Test** — run a staging instance alongside v2, verify all routes work +1. **Run the pre-migration audit** — check all 10 items above before changing anything +2. **Update static config** — review all entryPoints, providers, and TLS settings +3. **Update rule syntax** — replace `HostRegexp` wildcards with `Host(*)`, remove wildcards from `Path` matchers +4. **Check TLS defaults** — verify min TLS version compatibility with clients +5. **Update ServersTransport** — rename `insecure` to `insecureSkipVerify` +6. **Migrate tracing** — switch from direct Jaeger/Zipkin to OpenTelemetry +7. **Test with staging** — use `core.defaultRuleSyntax: v2` during migration and test each router +8. **Add default certificate** — if you relied on the TLS fallback behavior +9. **Run a parallel stack** — deploy v3 alongside v2, redirect a subset of traffic, verify all routes work +10. **Cut over** — switch production traffic to v3, keep v2 as rollback target for 48 hours diff --git a/traefik/references/production-deployment.md b/traefik/references/production-deployment.md index ab22879..6486e09 100644 --- a/traefik/references/production-deployment.md +++ b/traefik/references/production-deployment.md @@ -9,7 +9,7 @@ version: "3.8" services: traefik: - image: traefik:v3.2 + image: traefik:v3.7 restart: unless-stopped security_opt: - no-new-privileges:true @@ -113,7 +113,7 @@ services: - TASKS=1 traefik: - image: traefik:v3.2 + image: traefik:v3.7 environment: - DOCKER_HOST=tcp://docker-proxy:2375 # Do NOT mount the docker socket directly diff --git a/traefik/references/servers-transport.md b/traefik/references/servers-transport.md new file mode 100644 index 0000000..b4d5583 --- /dev/null +++ b/traefik/references/servers-transport.md @@ -0,0 +1,191 @@ +# ServersTransport Reference + +Defines how Traefik connects to backend servers. Configured in static config and referenced per-service in dynamic config. + +## HTTP ServersTransport + +Controls the connection between Traefik and HTTP backend services. + +```yaml +# Static config +serversTransport: + insecureSkipVerify: false # Skip TLS verification to backend + rootCAs: # List of root CA certificate paths + - /etc/traefik/certs/ca.pem + maxIdleConnsPerHost: 200 # Max idle connections per backend host + forwardingTimeouts: + dialTimeout: 30s # TCP dial timeout to backend + responseHeaderTimeout: 0s # Timeout for backend response headers (0=unlimited) + idleConnTimeout: 90s # Idle keep-alive connection timeout + spiffe: + ids: [] # SPIFFE IDs for workload identity + trustDomain: "" # SPIFFE trust domain +``` + +### Configuration Options + +| Option | Description | Default | +|--------|-------------|---------| +| `insecureSkipVerify` | Skip TLS certificate verification for backend connections | `false` | +| `rootCAs` | Paths to PEM-encoded CA certificates for backend TLS verification | `[]` | +| `maxIdleConnsPerHost` | Maximum idle connections kept per backend host | `200` | +| `forwardingTimeouts.dialTimeout` | Maximum time to establish TCP connection to backend | `30s` | +| `forwardingTimeouts.responseHeaderTimeout` | Maximum time to wait for response headers from backend | `0s` (no timeout) | +| `forwardingTimeouts.idleConnTimeout` | Maximum time a keep-alive connection can remain idle | `90s` | +| `spiffe.ids` | Allowed SPIFFE identities for workload identity | `[]` | +| `spiffe.trustDomain` | SPIFFE trust domain for workload identity | `""` | + +## TCP ServersTransport + +Controls connections for TCP routing (non-HTTP backends). + +```yaml +# Static config +tcpServersTransport: + dialKeepAlive: 30s # Keep-alive probe interval for TCP connections + dialTimeout: 30s # TCP dial timeout + terminationDelay: 0s # Delay before terminating TCP connection on shutdown + tls: + insecureSkipVerify: false # Skip TLS verification + rootCAs: # Root CA certificates for backend TLS + - /etc/traefik/certs/ca.pem + spiffe: + ids: [] + trustDomain: "" +``` + +### Configuration Options + +| Option | Description | Default | +|--------|-------------|---------| +| `dialKeepAlive` | TCP keep-alive probe interval | `30s` | +| `dialTimeout` | TCP dial timeout | `30s` | +| `terminationDelay` | Delay before TCP connection termination on shutdown | `0s` | +| `tls.insecureSkipVerify` | Skip TLS verification for backend | `false` | +| `tls.rootCAs` | Root CA certificate paths | `[]` | +| `tls.spiffe.ids` | Allowed SPIFFE identities | `[]` | +| `tls.spiffe.trustDomain` | SPIFFE trust domain | `""` | + +## Per-Service ServersTransport Override + +Reference a named serversTransport from a specific service in dynamic config: + +```yaml +# Dynamic config (File provider) +http: + serversTransports: + mTLS-to-backend: + insecureSkipVerify: false + rootCAs: + - /etc/traefik/certs/internal-ca.pem + forwardingTimeouts: + dialTimeout: 10s + + services: + secure-api: + loadBalancer: + servers: + - url: "https://backend.internal:443" + serversTransport: "mTLS-to-backend@file" +``` + +Via Docker labels: + +```yaml +labels: + - "traefik.http.services.api.loadbalancer.serverstransport=mTLS-to-backend@file" +``` + +## mTLS Between Traefik and Backends + +For mutual TLS (backends that require client certificates): + +```yaml +# Dynamic config +http: + serversTransports: + mtls: + insecureSkipVerify: false + rootCAs: + - /etc/traefik/certs/ca.pem # CA that signed the backend cert + # NOTE: serversTransport does NOT support client cert/key fields. + # For client certificate authentication to backends, configure + # TLS at the service level or use ForwardAuth. + + services: + internal-api: + loadBalancer: + serversTransport: "mtls@file" + servers: + - url: "https://api.internal:8443" +``` + +## Kubernetes CRD Reference + +In Kubernetes, define a `ServersTransport` CRD: + +```yaml +apiVersion: traefik.io/v1alpha1 +kind: ServersTransport +metadata: + name: mtls-transport +spec: + serverName: "api.internal" + insecureSkipVerify: false + rootCAs: + - secret: internal-ca-secret # Kubernetes Secret reference + forwardingTimeouts: + dialTimeout: 30s + responseHeaderTimeout: 30s + idleConnTimeout: 90s +``` + +Reference in an IngressRoute: + +```yaml +apiVersion: traefik.io/v1alpha1 +kind: IngressRoute +spec: + routes: + - kind: Rule + match: Host(`app.example.com`) + services: + - name: api-service + port: 8443 + serversTransport: mtls-transport +``` + +For TCP services, use `ServersTransportTCP`: + +```yaml +apiVersion: traefik.io/v1alpha1 +kind: ServersTransportTCP +metadata: + name: tcp-mtls +spec: + tls: + insecureSkipVerify: false + serverName: "db.internal" + rootCAs: + - secret: db-ca-secret +``` + +## Connection Pooling Best Practices + +```yaml +# For high-throughput APIs — minimize connection churn +serversTransport: + maxIdleConnsPerHost: 500 + forwardingTimeouts: + dialTimeout: 5s # Fast fail on unavailable backends + responseHeaderTimeout: 30s + idleConnTimeout: 120s # Longer idle time reduces reconnects + +# For latency-sensitive services — tight timeouts +serversTransport: + maxIdleConnsPerHost: 50 + forwardingTimeouts: + dialTimeout: 3s + responseHeaderTimeout: 10s + idleConnTimeout: 30s +``` diff --git a/traefik/references/static-configuration.md b/traefik/references/static-configuration.md index 9027373..1b29c4e 100644 --- a/traefik/references/static-configuration.md +++ b/traefik/references/static-configuration.md @@ -33,6 +33,10 @@ spiffe: workloadAPIAddr: "" # SPIRE agent socket path ## Server Transport (HTTP) + +See `references/servers-transport.md` for full detail on serversTransport, tcpServersTransport, connection pooling, backend TLS, and per-service transport overrides. + +```yaml serversTransport: insecureSkipVerify: false # Skip TLS verification to backend rootCAs: # List of root CA certificates @@ -47,6 +51,8 @@ serversTransport: trustDomain: "" ## TCP Server Transport + +```yaml tcpServersTransport: dialKeepAlive: 30s dialTimeout: 30s @@ -57,6 +63,7 @@ tcpServersTransport: spiffe: ids: [] trustDomain: "" +``` ## EntryPoints — Network listeners entryPoints: diff --git a/traefik/references/tcp-routing.md b/traefik/references/tcp-routing.md index 9db1726..62a361c 100644 --- a/traefik/references/tcp-routing.md +++ b/traefik/references/tcp-routing.md @@ -172,7 +172,7 @@ labels: # docker-compose.yml services: traefik: - image: traefik:v3.2 + image: traefik:v3.7 command: - "--entrypoints.postgres.address=:5432" - "--providers.docker=true" diff --git a/traefik/scripts/traefik-healthcheck.sh b/traefik/scripts/traefik-healthcheck.sh new file mode 100755 index 0000000..215d260 --- /dev/null +++ b/traefik/scripts/traefik-healthcheck.sh @@ -0,0 +1,96 @@ +#!/usr/bin/env bash +# traefik-healthcheck.sh — Check a running Traefik instance health +# Usage: ./traefik-healthcheck.sh [--json] [--url https://traefik.example.com] +# +# Requires: curl, jq +# Non-interactive, agent-friendly. + +set -euo pipefail + +# Defaults +URL="${TRAEFIK_URL:-http://localhost:8080}" +JSON=false +EXIT_CODE=0 + +while [[ $# -gt 0 ]]; do + case "$1" in + --json) JSON=true; shift ;; + --url) URL="$2"; shift 2 ;; + --help|-h) + echo "Usage: traefik-healthcheck.sh [--json] [--url ]" + echo "" + echo "Checks ping, API, version, router counts, and certificate expiry." + echo "Defaults to http://localhost:8080. Set TRAEFIK_URL env var to override." + exit 0 + ;; + *) echo "Unknown option: $1"; exit 1 ;; + esac +done + +json_out() { + if $JSON; then + echo "$1" + fi +} + +text_out() { + if ! $JSON; then + echo "$1" + fi +} + +# 1. Ping check +ping_status=$(curl -s -o /dev/null -w "%{http_code}" "${URL}/ping" 2>/dev/null || echo "000") +if [ "$ping_status" = "200" ]; then + text_out "✓ Ping: OK (200)" + json_out "{\"ping\": {\"status\": \"ok\", \"code\": 200}" +else + text_out "✗ Ping: FAILED ($ping_status)" + json_out "{\"ping\": {\"status\": \"fail\", \"code\": $ping_status}" + EXIT_CODE=1 +fi + +# 2. API health check +api_status=$(curl -s -o /dev/null -w "%{http_code}" "${URL}/api/version" 2>/dev/null || echo "000") +if [ "$api_status" = "200" ]; then + version=$(curl -s "${URL}/api/version" 2>/dev/null | jq -r '.Version // "unknown"' 2>/dev/null || echo "unknown") + text_out "✓ API: OK ($api_status) — Traefik $version" + json_out ", \"api\": {\"status\": \"ok\", \"version\": \"$version\"}" +else + text_out "✗ API: FAILED ($api_status) — is the API enabled?" + json_out ", \"api\": {\"status\": \"fail\", \"code\": $api_status}" + EXIT_CODE=1 +fi + +# 3. Router count +routers=$(curl -s "${URL}/api/http/routers" 2>/dev/null | jq length 2>/dev/null || echo "N/A") +text_out " HTTP routers: $routers" +json_out ", \"routers\": {\"count\": $routers}" + +tcp_routers=$(curl -s "${URL}/api/tcp/routers" 2>/dev/null | jq length 2>/dev/null || echo "N/A") +text_out " TCP routers: $tcp_routers" +json_out ", \"tcp_routers\": {\"count\": $tcp_routers}" + +# 4. Overview +overview=$(curl -s "${URL}/api/overview" 2>/dev/null || echo "{}") +if [ "$overview" != "{}" ]; then + total=$(echo "$overview" | jq -r '.http.routers.total // 0' 2>/dev/null) + text_out " Total HTTP routers (overview): $total" +fi + +# 5. Certificate expiry check +certs=$(curl -s "${URL}/api/rawconfig" 2>/dev/null | jq -r '.tls.certificates // []' 2>/dev/null || echo "[]") +if [ "$certs" != "[]" ]; then + cert_count=$(echo "$certs" | jq length) + text_out " TLS certificates: $cert_count" + json_out ", \"tls_certificates\": {\"count\": $cert_count}" +else + json_out ", \"tls_certificates\": null" +fi + +# Close JSON +if $JSON; then + echo "}" +fi + +exit $EXIT_CODE diff --git a/traefik/templates/docker-compose.yml b/traefik/templates/docker-compose.yml new file mode 100644 index 0000000..690d086 --- /dev/null +++ b/traefik/templates/docker-compose.yml @@ -0,0 +1,107 @@ +# Production Traefik Deployment — Docker Compose +# Target: Traefik v3.7+ +# Usage: docker compose up -d +# +# Features: +# - Docker provider (read-only socket via docker-socket-proxy) +# - Let's Encrypt ACME (HTTP-01 challenge) +# - Dashboard with BasicAuth (internal-only) +# - Security hardening (read-only rootfs, no-new-privileges, capability drop) +# - Prometheus metrics endpoint +# - JSON structured logging +# - HTTP/3 (QUIC) enabled + +version: "3.8" + +x-logging: &default-logging + driver: json-file + options: + max-size: "10m" + max-file: "3" + +services: + # --- Docker Socket Proxy (Security) --- + # Replaces direct docker.sock mount with a read-only API proxy + docker-proxy: + image: tecnativa/docker-socket-proxy:latest + restart: unless-stopped + volumes: + - /var/run/docker.sock:/var/run/docker.sock:ro + environment: + - CONTAINERS=1 + - NETWORKS=1 + - SERVICES=1 + - TASKS=1 + - INFO=1 + networks: + - traefik + logging: *default-logging + + # --- Traefik Reverse Proxy --- + traefik: + image: traefik:v3.7 + restart: unless-stopped + security_opt: + - no-new-privileges:true + cap_drop: + - ALL + cap_add: + - NET_BIND_SERVICE + networks: + - traefik + ports: + - "80:80" + - "443:443" + # UDP port for HTTP/3 (QUIC) + - "443:443/udp" + environment: + # DNS challenge credentials (example: Cloudflare) + # - CF_DNS_API_TOKEN=${CF_DNS_API_TOKEN} + - TZ=UTC + volumes: + - ./traefik.yml:/etc/traefik/traefik.yml:ro + - ./dynamic:/etc/traefik/dynamic:ro + - ./letsencrypt:/letsencrypt + # Do NOT mount docker.sock directly — use docker-proxy + command: + # Providers + - "--providers.docker=true" + - "--providers.docker.endpoint=tcp://docker-proxy:2375" + - "--providers.docker.exposedbydefault=false" + - "--providers.docker.network=traefik" + # File provider for shared middlewares + - "--providers.file.directory=/etc/traefik/dynamic" + - "--providers.file.watch=true" + # EntryPoints + - "--entrypoints.web.address=:80" + - "--entrypoints.websecure.address=:443" + - "--entrypoints.websecure.http3=true" + # TLS + - "--certificatesresolvers.letsencrypt.acme.email=admin@example.com" + - "--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json" + - "--certificatesresolvers.letsencrypt.acme.httpchallenge=true" + - "--certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=web" + # API (dashboard served through a router, not directly) + - "--api.dashboard=true" + - "--api.insecure=false" + # Logging + - "--log.level=INFO" + - "--log.format=json" + - "--accesslog=true" + - "--accesslog.format=json" + - "--accesslog.addinternals=false" + labels: + # Dashboard router + - "traefik.enable=true" + - "traefik.http.routers.dashboard.rule=Host(`traefik.example.com`)" + - "traefik.http.routers.dashboard.service=api@internal" + - "traefik.http.routers.dashboard.tls=true" + - "traefik.http.routers.dashboard.tls.certresolver=letsencrypt" + - "traefik.http.routers.dashboard.middlewares=dashboard-auth" + - "traefik.http.middlewares.dashboard-auth.basicauth.users=${DASHBOARD_AUTH:-admin:$$2y$$10$$...}" + logging: *default-logging + +networks: + traefik: + name: traefik + driver: bridge