Infrastructure guide

Traefik Docker Compose with HTTPS: A Production-Safe Starting Point

Deploy Traefik with opt-in service discovery, automatic HTTPS, persistent ACME state, a protected dashboard, and practical 404/502 diagnostics.

Published and reviewed by OpenAlt · September 25, 2026

Detailed image of illuminated server racks showcasing modern technology infrastructure.
Photo by panumas nikhomkhai on Pexels

TL;DR

This traefik docker compose baseline routes only services that explicitly opt in, redirects HTTP to HTTPS, obtains certificates through ACME, and keeps the dashboard behind authentication without publishing port 8080. It uses ports 80 and 443, a shared Docker network, read-only Docker socket access, persistent certificate storage, access logs, and Prometheus metrics.

The important safety defaults are providers.docker.exposedbydefault=false, a pinned Traefik image tag, and no --api.insecure=true. The official Docker quick start labels that insecure API mode as development-only: Traefik Docker quick start. The production-oriented configuration pattern is documented in Traefik’s Docker setup guide.

TOC

Network and DNS preflight

Traefik needs public reachability on TCP ports 80 and 443. Point each routed hostname to the server’s public address with DNS A and, when used, AAAA records. If IPv6 is published, verify that the host and firewall accept IPv6 traffic too; an incorrect AAAA record can make a hostname appear intermittently unavailable.

The HTTP-01 ACME challenge requires port 80 to reach Traefik. Do not block that port simply because normal application traffic will be redirected to HTTPS. The redirect and certificate challenge can coexist when Traefik owns the web entrypoint.

Create one external Docker network named proxy. Traefik and every routed application must join it. The network lets Traefik reach containers by Docker service metadata while keeping unrelated containers outside the routing path.

Secure Compose baseline

Save this as compose.yml. The example uses reserved documentation hostnames; replace them with DNS names that resolve to this server before issuing public certificates.

services:
  traefik:
    image: traefik:v3.4
    restart: unless-stopped
    command:
      - --api.dashboard=true
      - --providers.docker=true
      - --providers.docker.exposedbydefault=false
      - --providers.docker.network=proxy
      - --entrypoints.web.address=:80
      - --entrypoints.websecure.address=:443
      - --entrypoints.web.http.redirections.entrypoint.to=websecure
      - --entrypoints.web.http.redirections.entrypoint.scheme=https
      - --certificatesresolvers.letsencrypt.acme.email=ops@example.net
      - --certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json
      - --certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=web
      - --accesslog=true
      - --metrics.prometheus=true
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - ./letsencrypt:/letsencrypt
      - ./auth:/auth:ro
    networks:
      - proxy

networks:
  proxy:
    name: proxy
    external: true

The Docker socket is mounted read-only, which prevents Traefik from writing through that mount. It does not make Docker discovery risk-free: Traefik can still inspect container metadata, labels, networks, and other information exposed by the socket. Treat the socket as sensitive, limit who can modify Docker workloads, and keep the host protected.

The ACME resolver stores certificates and account data in /letsencrypt/acme.json. Traefik’s official ACME documentation explains certificate resolvers and persistent storage.

Persistent ACME storage permissions

Create the external network and storage directories before starting the stack:

docker network create proxy
mkdir -p letsencrypt auth
touch letsencrypt/acme.json
chmod 600 letsencrypt/acme.json

The file must remain writable by the Traefik process and inaccessible to other users. Its contents include certificate material and ACME account information, so do not commit it to a public repository or expose it through a web server. If the container runs with a non-root user in your environment, confirm that the mounted file and directory are writable by that user.

The email address in the resolver should be monitored. It gives the certificate authority a way to communicate about account or certificate events. Certificate storage must persist across container recreation; otherwise Traefik may lose its account state and request certificates unnecessarily.

An IT professional operates a computer in a server room, managing network systems and connected devices.
Photo by panumas nikhomkhai on Pexels
Detailed view of a server rack with a focus on technology and data storage.
Photo by panumas nikhomkhai on Pexels

First app labels

A service is not routed merely because it runs on the host. With exposedbydefault=false, it must carry an explicit enable label and join the proxy network.

  whoami:
    image: traefik/whoami:v1.10
    restart: unless-stopped
    networks:
      - proxy
    labels:
      - traefik.enable=true
      - traefik.docker.network=proxy
      - traefik.http.routers.whoami.rule=Host(`whoami.example.net`)
      - traefik.http.routers.whoami.entrypoints=websecure
      - traefik.http.routers.whoami.tls.certresolver=letsencrypt
      - traefik.http.services.whoami.loadbalancer.server.port=80

The internal service port is 80, even though the application has no published host port. That is the desired pattern: Traefik is the public entrypoint, while Docker networking carries traffic internally.

A routed application can be attached to multiple networks. traefik.docker.network=proxy removes ambiguity and tells Traefik which shared network to use. The application does not need to publish ports to the host.

Dashboard authentication

Do not enable --api.insecure=true. That option exposes the dashboard through port 8080 and is explicitly intended for development. The secure pattern enables the dashboard API internally and creates an HTTPS router for api@internal.

Generate a bcrypt password entry and store it in the mounted file:

htpasswd -nB operator > auth/users
chmod 600 auth/users

Add these labels to the Traefik service:

      - traefik.http.routers.dashboard.rule=Host(`traefik.example.net`)
      - 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=dashboard-auth
      - traefik.http.middlewares.dashboard-auth.basicauth.usersfile=/auth/users

Only ports 80 and 443 are published. Port 8080 is not exposed, and the dashboard is protected by basic authentication before it reaches the internal API service. Use a dedicated account and a strong password; basic authentication should still be carried over HTTPS only.

Verify redirect and certificate

After starting the stack, confirm that Docker reports both containers on the proxy network and inspect Traefik logs for configuration or ACME errors.

Test the expected behavior:

curl -I http://whoami.example.net
curl -vk https://whoami.example.net

The HTTP request should return a redirect to HTTPS. The HTTPS request should complete with a certificate matching the hostname and return the application response. Open the dashboard hostname in a browser and confirm that authentication is required.

If the certificate is not ready immediately, allow time for the ACME request and check logs. Avoid repeated restarts while troubleshooting; persistent storage prevents unnecessary account churn.

Backup, upgrade, and rollback

Back up compose.yml, the auth/users file, and letsencrypt/acme.json. Store the ACME file encrypted and restrict access because it contains private certificate data. Also preserve DNS records and any host firewall configuration needed for ports 80 and 443.

Pin an explicit Traefik version instead of using latest. Before an upgrade, make a backup, review the relevant release notes, pull the new image, and recreate the service during a maintenance window. Keep the previous image tag available so a rollback can restore the last known-good configuration. Test routing, certificate renewal, dashboard authentication, and logs after every upgrade.

Troubleshooting

A 404 usually means Traefik received the request but no router rule matched it. Check the requested hostname, DNS result, entrypoint, HTTPS scheme, and spelling of the Host() rule. Also confirm traefik.enable=true; with opt-in discovery, missing that label intentionally leaves the service unrouted.

A 502 usually means the router matched but Traefik could not reach the backend. Confirm that the application and Traefik share the proxy network, that traefik.docker.network=proxy is correct, and that loadbalancer.server.port matches the application’s listening port. Check whether the application is listening on its container interface rather than only on localhost.

A certificate error commonly comes from DNS pointing elsewhere, port 80 being blocked, an incorrect ACME entrypoint, or an unwritable acme.json. Verify the hostname resolves to this server, inspect Traefik’s ACME logs, and confirm that the storage file remains present with restrictive permissions.

FAQ

Why disable exposed-by-default?

It creates an explicit allowlist. A newly started container does not become publicly reachable just because it has a Docker label or happens to share a network. Routing requires deliberate labels, reducing accidental exposure.

Should port 8080 be public?

No. Do not publish port 8080 for a production dashboard. Route api@internal through HTTPS and protect it with authentication, as in the dashboard configuration above.

What must persist?

Persist /letsencrypt/acme.json, the Compose configuration, and the dashboard credential file. The ACME file is essential for retaining certificate and account state across container recreation.

How do services join Traefik’s network?

Attach Traefik and each routed service to the external Docker network named proxy. A service may have other networks, but traefik.docker.network=proxy identifies the network Traefik should use.

Why can a valid route return 502?

A valid router only proves that the hostname and rule matched. The backend can still be unreachable because of a wrong internal port, missing shared network, incorrect network selection, or an application that is not listening on its container interface.

Once the first service works, explore OpenAlt’s Traefik profile, compare self-hosting platforms, or evaluate one-click self-hosting. Readers comparing the coolify traefik path with a directly managed stack can use those options to choose the right operating model.