Skip to content
appsgit

Deploy guide

How to self-host Traefik with Docker Compose

Traefik Docker Compose setup with automatic Let's Encrypt HTTPS, an HTTP to HTTPS redirect, a password-protected dashboard and Docker labels to route your apps.

  • Updated
  • Intermediate
  • About 20 minutes

You will need

  • 1 vCPU / 512 MB RAM
  • Docker + Docker Compose v2
  • A domain with DNS records pointing at the server
  • Ports 80 and 443 reachable from the internet

What is Traefik?

Traefik is a cloud-native reverse proxy and load balancer. It watches Docker for containers, reads routing rules from their labels, and requests and renews Let's Encrypt certificates automatically. Add a few labels to any service, run docker compose up -d, and it is live on its own HTTPS hostname. Traefik is open source under the MIT license.

Requirements

  • A Linux server with Docker Engine and Docker Compose v2. Traefik is light: 1 vCPU and 512 MB of RAM handle a busy homelab.
  • A domain, with an A record for each hostname (or a wildcard *.example.com) pointing at the server's public IP.
  • Ports 80 and 443 open to the internet so Let's Encrypt can validate the domain.

Step 1: Prepare the server

This guide assumes Ubuntu 24.04 with Docker installed from the official Docker Engine guide. Make sure nothing else (Apache, Nginx, Caddy) is already bound to ports 80 or 443.

mkdir -p ~/traefik/letsencrypt && cd ~/traefik

Create a bcrypt password hash for the dashboard. htpasswd comes from the apache2-utils package:

sudo apt install -y apache2-utils
htpasswd -nbB admin 'CHANGE_ME' | sed -e 's/\$/\$\$/g'

Replace CHANGE_ME with a strong password (for example the output of openssl rand -hex 32). The sed step doubles every $, which Docker Compose needs so it does not treat the hash as a variable. Copy the output for the next step.

Step 2: Create the Docker Compose file

This follows the production setup in the official Traefik docs: HTTP redirects to HTTPS, certificates come from Let's Encrypt over the HTTP challenge, and only containers with traefik.enable=true are exposed. Save it as docker-compose.yml, replacing the email, hostnames and the hash:

services:
  traefik:
    image: traefik:v3.7
    container_name: traefik
    restart: unless-stopped
    security_opt:
      - no-new-privileges:true
    networks:
      - proxy
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - ./letsencrypt:/letsencrypt
    command:
      - "--entrypoints.web.address=:80"
      - "--entrypoints.web.http.redirections.entrypoint.to=websecure"
      - "--entrypoints.web.http.redirections.entrypoint.scheme=https"
      - "--entrypoints.websecure.address=:443"
      - "--entrypoints.websecure.http.tls.certresolver=le"
      - "--certificatesresolvers.le.acme.email=you@example.com"
      - "--certificatesresolvers.le.acme.storage=/letsencrypt/acme.json"
      - "--certificatesresolvers.le.acme.httpchallenge.entrypoint=web"
      - "--providers.docker=true"
      - "--providers.docker.exposedbydefault=false"
      - "--providers.docker.network=proxy"
      - "--api.dashboard=true"
      - "--log.level=INFO"
      - "--accesslog=true"
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.dashboard.rule=Host(`traefik.example.com`)"
      - "traefik.http.routers.dashboard.entrypoints=websecure"
      - "traefik.http.routers.dashboard.service=api@internal"
      - "traefik.http.routers.dashboard.middlewares=dashboard-auth"
      - "traefik.http.middlewares.dashboard-auth.basicauth.users=admin:$$2y$$05$$PASTE_YOUR_HASH"

  whoami:
    image: traefik/whoami:v1.12
    container_name: whoami
    restart: unless-stopped
    networks:
      - proxy
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.whoami.rule=Host(`whoami.example.com`)"
      - "traefik.http.routers.whoami.entrypoints=websecure"

networks:
  proxy:
    name: proxy

The v3.7 tag tracks patch releases of Traefik 3.7. The Docker socket is mounted read-only, but read access still reveals every container on the host, so never expose the dashboard without authentication.

Step 3: Start and open the app

docker compose up -d
docker compose logs -f traefik

Watch the logs for certificate errors. After a few seconds, open https://whoami.example.com: the whoami test container prints the request headers it received, which proves routing and HTTPS work. Then open https://traefik.example.com and sign in with the dashboard user to see routers, services and middlewares.

Step 4: Put it behind HTTPS

Traefik is the HTTPS layer, so this step is about routing your other apps through it. In any other Compose project, join the proxy network and add labels:

services:
  app:
    image: your/app
    networks: [proxy]
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.app.rule=Host(`app.example.com`)"
      - "traefik.http.routers.app.entrypoints=websecure"
      - "traefik.http.services.app.loadbalancer.server.port=8080"

networks:
  proxy:
    external: true

Remove the ports: section from apps routed through Traefik. Docker-published ports bypass ufw, so a published app port stays reachable over plain HTTP even with the firewall on.

Backups and upgrades

Back up docker-compose.yml and the letsencrypt folder. acme.json holds your certificates and the ACME account key, and losing it forces fresh certificates for every domain. To upgrade within 3.7:

docker compose pull && docker compose up -d

For a new minor version, change the tag and read the migration notes in the Traefik docs first.

Troubleshooting

  • 404 page not found: the router rule does not match, the container lacks traefik.enable=true, or it is not on the proxy network.
  • Bad Gateway: Traefik picked the wrong port. Set loadbalancer.server.port explicitly.
  • Certificate stays "TRAEFIK DEFAULT CERT": the HTTP challenge failed. Check that DNS points at this server and port 80 is open, then read the ACME error in the logs.
  • Dashboard login always fails: the $ signs in the hash were not doubled in the Compose label.

Next steps

Add middlewares for security headers and rate limiting, switch to the DNS challenge for wildcard certificates or servers behind NAT, and put Authentik in front of private apps with a forward-auth middleware.

Spotted something out of date? Tell us and we will update the guide.

FAQ

Traefik questions

Still curious? Email info@appsgit.com.

What ports does Traefik use?

In this setup Traefik listens on port 80 (HTTP, redirected to HTTPS and used for the Let's Encrypt challenge) and port 443 (HTTPS). The dashboard runs on port 8080 only if you enable the insecure API, which the Traefik docs say not to do in production.

Is Traefik free?

Yes. Traefik Proxy is free and open source under the MIT license. Traefik Labs sells separate commercial products, such as Traefik Hub for API management, but the reverse proxy itself has no paid tier.

Caddy vs Traefik: which should I use?

Caddy is simpler: a short Caddyfile and automatic HTTPS with almost no configuration. Traefik shines when you run many containers, because it discovers them through Docker labels and updates routes automatically as containers start and stop.

Traefik vs Nginx Proxy Manager?

Nginx Proxy Manager is configured by clicking through a web UI, which suits beginners. Traefik is configured in code, through Compose labels, so your routing lives next to each app's Compose file and is easy to version in Git.

How do I protect the Traefik dashboard?

Do not use --api.insecure. Route the dashboard through the HTTPS entrypoint on its own hostname and attach a basicauth middleware with a bcrypt hash, as shown in this guide, or put it behind an authentication provider such as Authentik.

Where does Traefik store Let's Encrypt certificates?

In the acme.json file set by certificatesresolvers.<name>.acme.storage. Keep it on a persistent volume and back it up, otherwise Traefik requests new certificates on every recreate and can hit Let's Encrypt rate limits.