Zero-downtime HTTPS for Docker apps with Traefik

Guide · Docker · Traefik

Disclosure: this guide links to Hetzner, a VPS provider we actually use. If you sign up through our link, we may earn a commission at no extra cost to you.

If you're running more than one app on a single VPS, the recurring pain is: each app wants its own domain, its own TLS cert, and you don't want to hand-edit nginx config every time you deploy something new. Traefik solves this by watching Docker directly and generating routing + certificates from container labels — no separate config file to maintain.

The setup

One Traefik container sits in front of everything, listening on 80/443. Every app container joins the same Docker network and declares its own routing rule as labels. Traefik picks it up automatically — no restart of Traefik needed when you deploy a new app.

# traefik docker-compose.yml (core bits)
services:
  traefik:
    image: traefik:v3
    command:
      - "--providers.docker=true"
      - "--providers.docker.exposedbydefault=false"
      - "--entrypoints.websecure.address=:443"
      - "--certificatesresolvers.le.acme.httpchallenge=true"
      - "--certificatesresolvers.le.acme.httpchallenge.entrypoint=web"
      - "--certificatesresolvers.le.acme.email=you@example.com"
      - "--certificatesresolvers.le.acme.storage=/letsencrypt/acme.json"
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - "/var/run/docker.sock:/var/run/docker.sock:ro"
      - "./letsencrypt:/letsencrypt"
    networks:
      - proxy

networks:
  proxy:
    external: true

Routing a new app

Any app you deploy just needs labels — no touching the Traefik container at all:

services:
  myapp:
    image: myapp:latest
    networks:
      - proxy
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.myapp.rule=Host(`myapp.example.com`)"
      - "traefik.http.routers.myapp.entrypoints=websecure"
      - "traefik.http.routers.myapp.tls.certresolver=le"
      - "traefik.http.services.myapp.loadbalancer.server.port=8000"

That's it. Push the compose file, run docker compose up -d, and Traefik requests a Let's Encrypt cert for that subdomain on the first request and renews it automatically before it expires. Deploy a second app with a different subdomain the same way, zero downtime for the apps already running, zero shared config to break.

Where this gets you in trouble

Where to run this

This pattern runs fine on the cheapest tier of most providers since Traefik itself is lightweight. We run ours on a Hetzner CX22 — best price-to-performance we've found for a multi-app Docker host. (Affiliate link — see disclosure above.)