Kokil Thapa - Professional Web Developer in Nepal
Freelancer Web Developer in Nepal with 15+ Years of Experience

Kokil Thapa is an experienced full-stack web developer focused on building fast, secure, and scalable web applications. He helps businesses and individuals create SEO-friendly, user-focused digital platforms designed for long-term growth.

Traefik: Modern Reverse Proxy for Docker

By Kokil Thapa | Last reviewed: September 2026

Traefik: Modern Reverse Proxy for Docker sits at the edge of most container stacks I deploy today. You run multiple apps on one VPS, each in its own container, and something must route HTTPS traffic to the right service. Manual Nginx vhost edits work, but they do not scale when containers restart with new IPs. Traefik watches the Docker socket, reads container labels, and publishes routes automatically. This guide walks through a production-ready setup you can copy onto Ubuntu with Docker installed on Ubuntu.

What is Traefik and why use it as a reverse proxy for Docker?

Traefik is an edge router written in Go. It terminates HTTP and HTTPS, applies middleware, and forwards requests to backend services. The Docker provider is what makes it feel modern compared to file-based proxies.

When a container starts with the right labels, Traefik creates a router, attaches middleware, and points it at the container IP. When the container stops, the route disappears. No SIGHUP, no stale upstream blocks, no forgotten vhost files.

On real client projects I often pair Traefik with Laravel apps in Docker, WordPress stacks, or multi-site legal portals. The pattern matches what I do with Apache on bare metal, but container-native. For teams already using Docker Compose for local development, Traefik is a natural production step.

Traefik Edge ArchitectureInternetTraefik Proxy:80 / :443 / DashboardDocker Network (bridge / overlay)Laravel App:8000WordPress:80API Service:3000
Traefik: Modern Reverse Proxy for Docker terminates TLS at the edge and routes to containerized backends on a shared network.

Core concepts map cleanly to Traefik v3 terminology:

  • Entrypoints — listeners such as web on port 80 and websecure on 443.
  • Routers — match Host, Path, or headers and send traffic to a service.
  • Services — define the backend pool and load-balancing strategy.
  • Middleware — auth, rate limits, redirects, compression, headers.
  • Providers — Docker, file, Kubernetes, and others that supply dynamic config.

Traefik also ships a dashboard and a Prometheus metrics endpoint. Both are useful during rollout and when you hand the stack to a client ops team. If you need deeper API gateway patterns, see the companion post on Traefik as an API gateway.

How do you install Traefik with Docker Compose?

Start with a dedicated Compose project for the proxy itself. Keep Traefik separate from application stacks. One Traefik instance can front every stack on the same Docker host as long as they share a network.

Create the project structure

mkdir -p ~/traefik/{dynamic,letsencrypt}
cd ~/traefik
touch docker-compose.yml traefik.yml dynamic/tls.yml

Static configuration (traefik.yml)

Static config boots Traefik. Dynamic config can change at runtime. Keep certificates and provider settings in static files or environment variables.

api:
  dashboard: true
  insecure: false

entryPoints:
  web:
    address: ":80"
    http:
      redirections:
        entryPoint:
          to: websecure
          scheme: https
  websecure:
    address: ":443"

providers:
  docker:
    endpoint: "unix:///var/run/docker.sock"
    exposedByDefault: false
    network: traefik-public
  file:
    directory: /etc/traefik/dynamic
    watch: true

certificatesResolvers:
  letsencrypt:
    acme:
      email: admin@example.com
      storage: /letsencrypt/acme.json
      httpChallenge:
        entryPoint: web

log:
  level: INFO
accessLog: {}

Docker Compose for Traefik

services:
  traefik:
    image: traefik:v3.3
    container_name: traefik
    restart: unless-stopped
    security_opt:
      - no-new-privileges:true
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - ./traefik.yml:/etc/traefik/traefik.yml:ro
      - ./dynamic:/etc/traefik/dynamic:ro
      - ./letsencrypt:/letsencrypt
    networks:
      - traefik-public
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.dashboard.rule=Host(`traefik.example.com`)"
      - "traefik.http.routers.dashboard.entrypoints=websecure"
      - "traefik.http.routers.dashboard.tls.certresolver=letsencrypt"
      - "traefik.http.routers.dashboard.service=api@internal"
      - "traefik.http.routers.dashboard.middlewares=auth"
      - "traefik.http.middlewares.auth.basicauth.users=admin:$$apr1$$H6uskkkW$$IgXLP6ewTrSuBkTrqE8wj/"

networks:
  traefik-public:
    name: traefik-public
    driver: bridge

Run docker compose up -d after DNS for your domains points at the server. Traefik needs ports 80 and 443 open in UFW or your cloud security group. For server hardening beyond Docker, Linux system administration covers firewall and SSH baseline work I apply on every VPS.

  1. Create the traefik-public network and start Traefik.
  2. Point domain A records at the server IP.
  3. Attach application stacks to traefik-public.
  4. Add Traefik labels on each app service.
  5. Verify HTTPS and check the dashboard behind basic auth.

How does Traefik auto-discover Docker containers?

The Docker provider polls the local socket. It reads container metadata and Docker labels prefixed with traefik.. Only containers with traefik.enable=true are published when exposedByDefault: false.

This design prevents accidental exposure. A database container on the same network stays internal unless you label it. That matters on shared VPS hosts where one mistake can leak MySQL to the internet.

Docker Provider DiscoveryDocker Engine/var/run/docker.sockTraefik ProviderReads traefik.* labelsDynamic ConfigRouters + ServicesExample Container Labelstraefik.enable=truetraefik.http.routers.app.rule=Host(`app.example.com`)traefik.http.routers.app.tls.certresolver=letsencrypttraefik.http.services.app.loadbalancer.server.port=8000
Traefik watches the Docker socket and converts container labels into live routes without manual reloads.

Label a Laravel app behind Traefik

On booking systems like Adventure Third Pole Trek, the app container might expose port 8000 internally. Traefik publishes it on a public hostname.

services:
  app:
    image: myorg/laravel-app:latest
    networks:
      - traefik-public
      - internal
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.laravel.rule=Host(`book.example.com`)"
      - "traefik.http.routers.laravel.entrypoints=websecure"
      - "traefik.http.routers.laravel.tls.certresolver=letsencrypt"
      - "traefik.http.services.laravel.loadbalancer.server.port=8000"
      - "traefik.http.middlewares.laravel-headers.headers.customrequestheaders.X-Forwarded-Proto=https"
      - "traefik.http.routers.laravel.middlewares=laravel-headers@docker"

networks:
  traefik-public:
    external: true
  internal:
    driver: bridge

Always set the load balancer port explicitly. Traefik cannot guess which exposed port you intend when a container publishes several. For local Laravel work with Sail, compare patterns in local Laravel dev with Sail and Docker before promoting the same Compose shape to production.

Network choice matters. The app must attach to the same network Traefik uses for routing. I name that network traefik-public and mark it external in app Compose files. Details on bridge vs overlay behaviour sit in Docker networking and volumes explained.

How do you configure HTTPS with Let's Encrypt in Traefik?

Traefik stores ACME certificates in acme.json. Create the file and restrict permissions before the first certificate request.

touch letsencrypt/acme.json
chmod 600 letsencrypt/acme.json

The HTTP-01 challenge needs port 80 reachable from the public internet. Traefik answers /.well-known/acme-challenge/ on the web entrypoint. Do not put another service on host port 80 alongside Traefik.

DNS challenge for wildcards

HTTP-01 works for single hostnames. Wildcard certs need DNS-01 with a provider plugin or external cert management. On small VPS setups I usually assign one hostname per container and skip wildcards entirely.

Request Flow and MiddlewareClient HTTPSEntrypointRouter MatchTLSMiddleware Chainredirect · compress · rateLimit · headers · authBackend ServiceContainer IP : published port
Each HTTPS request hits an entrypoint, matches a router, passes middleware, then reaches the Docker backend service.

Common middleware I apply in production:

  • compress — gzip responses for HTML and JSON.
  • ratelimit — basic abuse protection at the edge.
  • headers — HSTS, frame deny, content-type nosniff.
  • redirectscheme — force HTTPS at the router level.

Official ACME behaviour is documented in the Traefik ACME guide. Cross-check Compose syntax against the Docker Compose specification when you upgrade Compose on the host.

Validate generated JSON config with a JSON formatter when you debug dynamic file provider snippets. Small syntax errors silently block route creation.

Traefik vs Nginx: which reverse proxy fits Docker best?

Both proxies are production-grade. The right choice depends on how often your backends change and who maintains the server.

CriteriaTraefikNginx (manual / template)
Dynamic backendsNative via Docker/K8s providersRequires reload scripts or lua/openresty
TLS automationBuilt-in ACME resolverOften Certbot + cron + reload
Learning curveLabel syntax and provider modelFamiliar config files
ObservabilityDashboard, metrics, access logsLogs; dashboard needs extras
Static file performanceGood; not its primary strengthExcellent for high-throughput static
Multi-team ownershipApp teams set labels in ComposeOps owns central vhost files

I still deploy Nginx reverse proxies on bare-metal LAMP stacks and high-traffic static fronts. For Docker-heavy workloads, Traefik wins on operational speed. When clients need API aggregation, rate limits, and JWT validation at the edge, compare options in Kong vs Traefik vs AWS API Gateway.

Proxy Choice Decision TreeDocker workloads?YesNoFrequent deploys?Prefer NginxUse TraefikNginx + scriptsTraefik: Modern Reverse Proxy for Docker — best when containers churn and teams own Compose files
Choose Traefik when containers deploy often; choose Nginx for static-heavy bare-metal or legacy PHP stacks.

Envoy fits service-mesh scenarios with sidecars. For a single VPS running Compose, Envoy is usually heavier than needed. Read Envoy proxy fundamentals if you are evaluating mesh patterns later.

How do you run Traefik safely in production?

Mounting docker.sock gives Traefik broad control over the host. Treat the Traefik container as privileged infrastructure.

Security checklist

  1. Mount the socket read-only (:ro).
  2. Set exposedByDefault: false.
  3. Protect the dashboard with basic auth or IP allowlists.
  4. Do not publish the Docker API port to the internet.
  5. Pin Traefik image tags; avoid bare latest in production.
  6. Back up acme.json with your nightly server backups.

I've encountered production outages when acme.json permissions were wrong after a restore. Traefik refused to write certs and every HTTPS route failed. Fix permissions, restart Traefik, and watch the logs for ACME success.

Logging and debugging

docker logs traefik --tail 100 -f

Enable access logs in static config when tracing 502 errors. A 502 usually means Traefik reached the router but the backend port or network was wrong. Confirm the container is on traefik-public and the label port matches the process listening inside the container.

For multi-container legal portals such as Mijar Law Associates, I isolate databases on internal-only networks. Traefik never sees those containers because they carry no publish labels. That split mirrors how I segment Apache vhosts on traditional servers.

When you outgrow a single node, Traefik runs on Kubernetes and Docker Swarm with the same label concepts. Until then, one well-configured Compose host handles most SME workloads in Nepal and abroad. Hosting, DNS, and TLS rollout are part of domain registration and hosting engagements I deliver end to end.

Ongoing updates, image pin bumps, and certificate renewal monitoring belong in support and maintenance retainers. Traefik reduces manual work, but someone still watches disk space on acme.json and log rotation.

If your stack exposes public APIs, pair edge routing with application-level controls described in API rate limiting and abuse prevention. Traefik rate limits are a first line; they do not replace auth inside your API development layer.

Cache-heavy read paths may still benefit from a dedicated cache proxy. See reverse proxy and caching with Varnish for a two-tier edge design Traefik can sit in front of.

Enterprise clients running mixed VM and container fleets often need a migration plan before standardising on labels. Enterprise application development covers phased rollouts without downtime surprises during Dashain traffic spikes.

Resource limits on app containers prevent one runaway PHP worker from starving Traefik itself. Apply the patterns in limit Docker container resources on the same host.

Key Takeaways

  • Run one Traefik instance per Docker host with a shared external network like traefik-public.
  • Set exposedByDefault: false and explicit traefik.enable=true on every public service.
  • Always declare loadbalancer.server.port in labels; Traefik will not guess correctly.
  • Protect acme.json with mode 600 and include it in backups.
  • Use middleware for HTTPS headers, compression, and rate limits instead of baking rules into apps.
  • Pick Traefik for dynamic Docker fleets; keep Nginx where static files and central ops files win.

People Also Ask

Does Traefik replace Nginx inside containers?

No. Traefik routes traffic to containers; your app container can still run Nginx or PHP-FPM internally. Traefik replaces the host-level reverse proxy that would otherwise point at published ports.

Can Traefik route to non-Docker backends?

Yes. The file provider accepts static URLs for legacy VMs or upstream SaaS APIs. Many teams run Traefik in Docker while pointing some routers at external IP addresses during migration.

What Traefik version should you use in 2026?

Pin a current Traefik v3 release such as v3.3 or later stable tag. Read release notes before upgrading; major versions occasionally rename middleware keys or deprecate legacy label formats.

Is the Traefik dashboard safe to expose?

Only with TLS, authentication, and ideally a restricted hostname. Never expose api.insecure: true on production hosts. The dashboard shows live routes and can leak internal hostnames.

Deploy Traefik on your next Docker stack

You now have a complete pattern for Traefik: Modern Reverse Proxy for Docker — static config, Compose service, labels, TLS, and production guardrails. Start on a staging VPS, attach one app, verify HTTPS, then migrate additional Compose projects onto the shared network. The payoff is fewer midnight Nginx edits and fewer stale upstream entries after container restarts.

Need help migrating Laravel, WordPress, or multi-site portals to a container edge? Contact us for deployment planning, or browse the portfolio for examples of production systems already running on disciplined infrastructure.

Frequently Asked Questions

Traefik is an edge router written in Go that terminates HTTP and HTTPS, applies middleware, and forwards requests to backend services. For Docker, its value is the Docker provider: it watches the socket, reads container labels, and builds routes automatically when containers start or stop. No SIGHUP reloads, no stale upstream blocks, no forgotten vhost files. On production VPS hosts running multiple Compose stacks, that operational speed beats manual Nginx edits every time a container gets a new IP.

Create a dedicated Compose project separate from application stacks, for example mkdir -p ~/traefik/{dynamic,letsencrypt}, then add traefik.yml for static config and docker-compose.yml pointing at traefik:v3.3. Mount the Docker socket read-only, your config files, and a letsencrypt directory for ACME storage. Expose ports 80 and 443, attach Traefik to a bridge network named traefik-public, and run docker compose up -d after DNS A records point at the server. Open ports 80 and 443 in UFW or your cloud security group before requesting certificates.

The Docker provider polls unix:///var/run/docker.sock and reads container metadata plus labels prefixed with traefik.. With exposedByDefault set to false, only containers carrying traefik.enable=true are published. Traefik converts those labels into routers, services, and middleware at runtime. When a container stops, its route disappears automatically. This prevents accidental exposure: a database container on the same host stays internal unless you explicitly label it for publication, which matters on shared VPS hosts.

Define a certificatesResolvers block in traefik.yml pointing at a letsencrypt storage file, then create acme.json with chmod 600 before the first request. Configure HTTP-01 challenge on the web entrypoint so Traefik answers /.well-known/acme-challenge/ on port 80. Reference tls.certresolver=letsencrypt on each router label. Do not run another service on host port 80 alongside Traefik. Back up acme.json with nightly server backups; I've seen HTTPS fail entirely when restored files had wrong permissions.

Both are production-grade. Traefik wins when backends change often because the Docker provider updates routes from labels without reload scripts. Nginx still fits bare-metal LAMP stacks, high-throughput static fronts, and teams where ops owns central vhost files. Traefik ships built-in ACME, a dashboard, and Prometheus metrics. Nginx often needs Certbot plus cron plus reload for TLS. For Docker-heavy Compose workloads on a single VPS, Traefik reduces midnight config edits. Keep Nginx where static performance and familiar config files matter most.

Treat the Traefik container as privileged infrastructure because mounting docker.sock grants broad host control. Mount the socket read-only, set exposedByDefault to false, pin image tags like traefik:v3.3 instead of bare latest, and never publish the Docker API port publicly. Protect the dashboard with TLS, basic auth, and a restricted hostname; never set api.insecure to true on production. Back up acme.json, restrict it to mode 600, and isolate databases on internal-only networks with no traefik.enable labels so Traefik never routes to them.

No. Traefik routes traffic at the host edge to containers; your app container can still run Nginx, PHP-FPM, or Apache internally. Traefik replaces the host-level reverse proxy that would otherwise point at published Docker ports.

Yes. The file provider accepts static URLs for legacy VMs, external IP addresses, or upstream SaaS APIs. Many teams run Traefik in Docker while pointing some routers at external targets during phased migration from bare metal to containers.

Pin a current Traefik v3 release such as v3.3 or a later stable tag. Read release notes before upgrading; major versions occasionally rename middleware keys or deprecate legacy label formats.

Only with TLS, authentication, and ideally a restricted hostname such as traefik.example.com behind basic auth middleware. Never expose api.insecure true on production hosts. The dashboard shows live routes, services, and middleware and can leak internal hostnames to anyone who reaches it. In the article's Compose example, dashboard access uses websecure entrypoint, Let's Encrypt, and basicauth middleware. For client handoffs, the dashboard helps ops teams verify routes during rollout, but treat it like admin infrastructure, not a public page.

Traefik cannot reliably guess which exposed port you intend when a container publishes several. A missing or wrong port is the most common cause of 502 errors: Traefik matches the router but cannot reach the backend process. On Laravel apps listening on 8000 internally, the label traefik.http.services.laravel.loadbalancer.server.port=8000 tells Traefik exactly where to forward. Always verify the label port matches the process listening inside the container, not the host-mapped port Traefik ignores when routing over the Docker network.

traefik-public is an external bridge network Traefik and every public-facing app container must share. Define it once in the Traefik Compose file with name traefik-public, then mark it external: true in application Compose files. Traefik routes to container IPs on this network directly. If an app attaches only to an internal network, Traefik never reaches it regardless of labels. I use the same pattern on multi-container legal portals: public apps on traefik-public, databases on internal-only networks Traefik never sees.

The article recommends compress for gzip on HTML and JSON responses, ratelimit for basic edge abuse protection, headers for HSTS, frame deny, and content-type nosniff, and redirectscheme to force HTTPS at the router level. Apply middleware via labels or dynamic file provider rather than baking rules into each app. For Laravel backends, customrequestheaders like X-Forwarded-Proto=https help the app generate correct URLs behind TLS termination. Traefik rate limits are a first line only; they do not replace authentication inside your API layer.

A 502 usually means Traefik matched the router but failed to reach the backend. Confirm the container is attached to traefik-public, traefik.enable is true, and loadbalancer.server.port matches the process listening inside the container. Run docker logs traefik --tail 100 -f and enable accessLog in static config to trace requests. Check that no other service occupies host port 80 blocking ACME or HTTP redirects. Small syntax errors in dynamic file provider JSON can also silently block route creation, so validate snippets carefully when debugging.

For a single VPS running Docker Compose, Envoy is usually heavier than needed; it fits service-mesh scenarios with sidecars better than a simple edge proxy. When clients need API aggregation, JWT validation, and advanced gateway patterns, compare Kong, Traefik, and AWS API Gateway separately. Traefik suits teams already using Compose labels for local dev who want the same model in production. Choose Traefik when containers deploy often and routes must update without reloads. Keep Nginx for static-heavy bare-metal stacks or legacy PHP hosts where central ops owns vhost files.

Share this article

0 Comments

Leave a comment

Your email is not published. Comments appear once they have been read. Sign in to have your details filled in.

Quick Contact Options
Choose how you want to connect me: