Traefik Docker Compose Guide for Self-Hosted Apps
traefikdocker composereverse proxytlsself-hostingdevops

Traefik Docker Compose Guide for Self-Hosted Apps

AAlex Rowan
2026-06-14
10 min read

A practical Traefik Docker Compose workflow for routing self-hosted apps with TLS, reusable labels, and maintainable proxy patterns.

If you run more than one service on a self hosted server, a reverse proxy quickly becomes the piece that makes everything manageable. This Traefik Docker Compose guide shows a practical workflow for exposing self hosted apps behind clean domain names, automating TLS certificates, and keeping routing rules close to each application. Rather than treating Traefik as a one-time setup, the goal here is to give you a repeatable pattern you can use as your stack grows, changes, and occasionally breaks.

Overview

Traefik is a strong fit for self-hosting because it works well with Docker labels. Instead of maintaining a large static proxy file for every service, you can define most routing behavior next to the container it belongs to. In practice, that means each app carries its own host rules, middleware, and backend port declarations. For teams and homelab operators alike, this reduces guesswork when you revisit a stack months later.

In a typical traefik docker compose setup, Traefik listens on ports 80 and 443, discovers containers through the Docker socket, and creates routers and services from labels attached to those containers. You then point your DNS records at the server, let Traefik request certificates, and expose only the apps you actually want reachable from the outside.

This pattern is especially useful when you host a mix of tools such as file sharing, project management, RSS readers, admin panels, or internal developer utilities. If you are still planning the broader layout of your server, it helps to first read How to Run Multiple Self-Hosted Apps on One Server Safely, because Traefik is only one part of the isolation and routing story.

For this guide, assume a Linux host with Docker Engine and the Docker Compose plugin installed, a domain you control, and basic DNS access. The examples are intentionally simple and meant to be adapted. The exact image tags, provider credentials, and app names will vary in your environment.

Step-by-step workflow

Here is the workflow that tends to stay useful even as Traefik versions, label syntax, and certificate options evolve: start with networking, deploy Traefik on its own shared network, confirm plain HTTP reachability, add TLS automation, then attach apps one by one with explicit labels.

1. Create a shared external Docker network

Give Traefik and your public-facing containers a common network. This keeps routing predictable and avoids attaching every service to every stack network.

docker network create proxy

Using an external network is a small decision that pays off later. Separate application stacks can join the same proxy network without being merged into one giant Compose file.

2. Prepare a Traefik directory

A clean directory layout makes maintenance easier. A simple structure might look like this:

traefik/
├── compose.yml
├── data/
│   └── acme.json
└── dynamic/
    └── middlewares.yml

Create the certificate storage file and lock down its permissions:

touch data/acme.json
chmod 600 data/acme.json

Traefik stores certificate data there when using ACME. The restrictive permission is a practical default.

3. Deploy a minimal Traefik Compose stack

Start with a baseline configuration that does three things well: listens on ports 80 and 443, redirects HTTP to HTTPS, and uses Docker as the service discovery provider.

services:
  traefik:
    image: traefik:v3.0
    container_name: traefik
    restart: unless-stopped
    command:
      - --api.dashboard=true
      - --providers.docker=true
      - --providers.docker.exposedbydefault=false
      - --providers.file.directory=/dynamic
      - --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=admin@example.com
      - --certificatesresolvers.letsencrypt.acme.storage=/data/acme.json
      - --certificatesresolvers.letsencrypt.acme.httpchallenge=true
      - --certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=web
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - ./data:/data
      - ./dynamic:/dynamic:ro
    networks:
      - proxy
    labels:
      - traefik.enable=true
      - traefik.http.routers.traefik.rule=Host(`traefik.example.com`)
      - traefik.http.routers.traefik.entrypoints=websecure
      - traefik.http.routers.traefik.tls.certresolver=letsencrypt
      - traefik.http.routers.traefik.service=api@internal

networks:
  proxy:
    external: true

Two lines deserve attention. First, exposedbydefault=false ensures containers are not published unless you opt in with labels. That is one of the most important safety defaults in any docker reverse proxy Traefik setup. Second, the Docker socket is mounted read-only, which is common for discovery, though you should still treat socket access as sensitive.

4. Point DNS before testing certificates

Create DNS records for the hostnames you plan to route, such as traefik.example.com or notes.example.com. Wait for them to resolve to your server before expecting ACME issuance to work. A reverse proxy problem is often a DNS problem first.

If your environment uses tunnels or private access instead of direct public exposure, your design may differ. For remote access patterns, compare that approach with Cloudflare Tunnel vs Tailscale vs WireGuard for Secure Remote Access. Many operators mix Traefik for HTTP routing with a separate remote access path for admin services.

5. Add shared middleware definitions

Some middleware belongs in one reusable file rather than repeated on every container. A basic dynamic file could include secure headers or authentication middleware.

http:
  middlewares:
    secure-headers:
      headers:
        sslRedirect: true
        browserXssFilter: true
        contentTypeNosniff: true
        frameDeny: true
        referrerPolicy: no-referrer
    gzip:
      compress: {}

This is a good place for common patterns you want to apply across many apps. Keep it small. If every special case goes into one file, you lose the clarity that labels provide.

6. Expose your first app with labels

Now attach an application container to the same proxy network and define its routing labels. Here is a generic example for a web app listening on port 3000 inside the container:

services:
  app:
    image: your-app:latest
    restart: unless-stopped
    networks:
      - proxy
      - internal
    labels:
      - traefik.enable=true
      - traefik.http.routers.app.rule=Host(`app.example.com`)
      - traefik.http.routers.app.entrypoints=websecure
      - traefik.http.routers.app.tls.certresolver=letsencrypt
      - traefik.http.routers.app.middlewares=secure-headers@file,gzip@file
      - traefik.http.services.app.loadbalancer.server.port=3000

networks:
  proxy:
    external: true
  internal:

This pattern is the core of a maintainable traefik self hosting setup. The labels answer four questions clearly: should this app be exposed, which hostname should route to it, which entrypoint should accept traffic, and which internal port should Traefik forward to?

7. Add path rules only when you really need them

Traefik supports routing by host, path, headers, and more. In most self hosted environments, host-based routing is the cleanest choice. Prefer app.example.com over example.com/app unless the application explicitly supports a subpath deployment. Many apps behave unpredictably behind path prefixes unless additional base URL settings are configured.

8. Use separate routers for app and redirect behavior when needed

As your stack grows, you may want one hostname to redirect elsewhere, send users through auth middleware, or expose different behavior for internal and external clients. Traefik can do that, but clarity matters more than cleverness. A good rule is to keep one primary router per app and introduce additional routers only for a specific operational reason.

9. Keep secrets out of labels

Labels are visible through Docker inspection and management tools. Do not place credentials, API tokens, or private keys there. Use environment files, Docker secrets where supported, or mounted files with controlled permissions.

10. Back up the proxy config like application data

Your reverse proxy is infrastructure, not just glue. Back up the Compose files, dynamic config files, and certificate storage. If you rebuild a host without the ACME state file, certificate reissuance may still work, but you should not treat that as your recovery plan. Backup discipline matters just as much for routing as for databases. This pairs well with a broader server hardening checklist for self-hosted apps on public VPS infrastructure.

Tools and handoffs

Once the first app works, the next challenge is consistency. A reliable Traefik workflow is really a series of handoffs between systems: DNS sends traffic to your host, Traefik terminates TLS and matches rules, Docker networking connects the request to the container, and the application itself must know its public URL if it generates redirects or callback links.

DNS provider

DNS is the first handoff. You need stable records for each hostname or a wildcard strategy if you prefer broad subdomain coverage. Keep naming boring and descriptive. A stack with notes.example.com, files.example.com, and status.example.com is easier to debug than one built around inconsistent names.

Docker Compose

Compose is the control layer where infrastructure and application settings meet. One practical habit is to keep Traefik in its own dedicated Compose project and every app in its own project. That reduces blast radius and makes updates safer. If you later move to a control panel or platform layer, this separation still maps cleanly to tools discussed in Portainer vs Coolify vs CapRover: Which Self-Hosting Control Panel Fits Best?.

Middleware library

Traefik middleware is powerful, but it is also where many stacks become hard to read. A good pattern is to maintain a small shared library of middlewares in the file provider, such as secure headers, compression, and perhaps basic authentication for selected admin tools. Everything else should stay as close to the app as possible.

Application configuration

Some apps need explicit settings for their external URL, trusted proxies, or forwarded headers. If an app redirects to the wrong scheme, generates broken callback URLs, or logs every request as coming from the proxy IP, the issue may not be Traefik at all. Always check whether the app expects a BASE_URL, APP_URL, or trusted proxy setting.

Observability and maintenance

At minimum, review Traefik logs and access patterns after each new deployment. If you run several public services, connect your setup to uptime checks and alerting. A proxy failure can make every app look down at once, so monitoring the edge layer deserves priority. For tool comparisons, see Best Self-Hosted Uptime Monitoring Tools Compared.

Quality checks

Before you call the setup finished, run through a short validation list. These checks catch most of the issues people blame on Traefik but usually come from DNS, networking, or application assumptions.

  • DNS resolves correctly: each hostname points to the expected public IP or tunnel endpoint.
  • Ports 80 and 443 are reachable: if using direct exposure, confirm the host firewall and provider firewall allow them.
  • Traefik and the app share a network: if they are not on the same Docker network, routing will fail.
  • The backend port label matches reality: the loadbalancer.server.port value must be the container's internal listening port, not the host port.
  • The router rule is exact: a typo in the hostname is enough to produce certificate or routing confusion.
  • Certificates are issued and renewed: check logs and confirm the ACME storage file updates as expected.
  • HTTP redirects to HTTPS: test plain HTTP intentionally, not just the final HTTPS URL.
  • App-generated links are correct: login callbacks, attachments, and websocket endpoints should all use the intended public URL.

One more quality check is architectural rather than technical: ask whether the app should be public at all. Many services are better kept behind a VPN, private network, or authentication gateway. Public exposure should be a deliberate choice, not the default outcome of adding labels.

As your catalog of apps grows, consistency matters more than novelty. Whether you are exposing a file platform, a project management tool, or a media service, the same routing checklist should apply. That is what makes a reverse proxy setup durable instead of fragile.

When to revisit

The best time to revisit your Traefik setup is before it drifts into mystery. Treat the proxy layer as a living part of your platform and schedule small reviews instead of waiting for a public outage.

Revisit this setup when any of the following changes:

  • You add a new public app: confirm your label conventions, middleware choices, and DNS naming still make sense.
  • You change certificate strategy: for example, moving between HTTP challenge, DNS challenge, or a different trust model.
  • You introduce a platform tool: control panels and app deployers may generate labels or networks differently.
  • You change your remote access model: especially if some admin services move behind tunnels or private mesh networking.
  • You update Traefik major versions: review deprecated flags, provider behavior, and any label syntax changes.
  • You harden the host: firewall rules, Docker socket handling, and container isolation may need proxy adjustments.
  • You troubleshoot unexplained redirects or 404s: these usually signal drift between app settings, router rules, and network assumptions.

A practical maintenance routine looks like this:

  1. Keep one canonical Traefik Compose file in version control.
  2. Document your standard labels for public apps.
  3. Keep a small shared middleware file and avoid unnecessary complexity.
  4. Back up ACME state and proxy config with the same seriousness as application data.
  5. Test one app end to end after every significant change: DNS, certificate, login flow, file upload, websocket if applicable.

If you do that, Traefik remains what it should be in a self-hosted stack: a quiet, dependable edge layer that helps you deploy apps on your own cloud without turning every new service into a custom proxy project. That is the real advantage of a good traefik guide workflow. It is not just getting one container online today. It is creating a system you can return to next month when you add the next app, rotate infrastructure, or rebuild the server with confidence.

Related Topics

#traefik#docker compose#reverse proxy#tls#self-hosting#devops
A

Alex Rowan

Senior Editor

Senior editor and content strategist. Writing about technology, design, and the future of digital media. Follow along for deep dives into the industry's moving parts.

2026-06-14T10:44:27.497Z