Traefik or nginx-proxy for Local Docker Stacks
AI generated
Docker · Traefik · nginx-proxy · Reverse Proxy
Traefik or nginx-proxy
for local Docker stacks

Local Docker development environments running several projects need a reverse proxy that routes domains to containers and manages HTTPS certificates. Traefik and nginx-proxy solve the same problem with fundamentally different approaches. This article shows which proxy is the better choice in which scenario.

13 min read Traefik · nginx-proxy · mkcert · service discovery Docker Compose · local HTTPS · multi-project

1. The problem: multiple projects, one local stack

Local development quickly creates situations that call for a reverse proxy: several projects need to run simultaneously and be reachable through meaningful domains like shop.local and api.local instead of localhost:8080 and localhost:8081. HTTPS needs to work because certain browser features, such as service workers and cookies with SameSite=Strict, require HTTPS. And when a new project starts up, it should automatically be reachable under its own domain, without manually adjusting the proxy configuration.

Two tools have become the standard for this scenario: Traefik and nginx-proxy (the nginxproxy/nginx-proxy image). Both listen on the Docker socket, discover new containers and configure routing automatically. The difference lies in the approach: nginx-proxy generates an nginx configuration file from container environment variables and reloads nginx on container events. Traefik reads labels directly from container definitions, keeps a dynamic routing configuration and ships a web dashboard.

The choice between Traefik and nginx-proxy is not universally right or wrong. Anyone looking for a simple setup with minimal configuration overhead and no complex routing rules is well served by nginx-proxy. Anyone who needs dynamic routing, middleware configuration (rate limiting, header manipulation, auth) and a visual dashboard should pick Traefik. Both proxies can manage production HTTPS certificates in a live environment, but this article focuses on the local development setup.

2. nginx-proxy: simple, stable, low configuration

nginx-proxy is the older and simpler of the two tools. It listens on the Docker socket and automatically generates an nginx upstream and server block configuration from the VIRTUAL_HOST environment variable of each container. When a container starts with VIRTUAL_HOST=shop.local, a new server block appears in the generated nginx configuration that forwards requests for shop.local to that container. That is the entire configuration effort on the container side.

The advantage of nginx-proxy is transparency: the generated nginx configuration is visible and easy to understand. When something does not work, you can run docker exec nginx-proxy cat /etc/nginx/conf.d/default.conf and read the generated configuration directly. There is no abstraction layer. For teams familiar with nginx who do not want to learn a label driven configuration scheme, that is a significant advantage. nginx-proxy is also more stable with respect to breaking changes: it does exactly one thing and does it reliably.

The limitations of nginx-proxy are a direct consequence of its simplicity: complex routing rules, such as forwarding specific URL paths to different backend containers, are difficult or require custom nginx configuration fragments. Middleware features like automatic HTTP to HTTPS redirects or rate limiting need manual configuration. For simple domain to container forwarding nginx-proxy is optimal; for more complex scenarios Traefik becomes the superior tool.

3. Traefik: dynamic, label driven, with dashboard

Traefik follows a fundamentally different approach: instead of generating configuration, Traefik reads labels directly from container definitions and configures routing dynamically without a reload. A container with the label traefik.http.routers.shop.rule=Host('shop.local') is reachable under shop.local immediately after startup. Adding or removing containers updates the Traefik configuration without interruption: no nginx reload, no brief downtime.

The Traefik dashboard at http://localhost:8080 shows all configured routers, services and middlewares in real time. That is especially valuable when debugging routing issues: you immediately see whether a container was detected, whether the labels were parsed correctly and whether the service is considered healthy. nginx-proxy offers no comparable diagnostic interface.

Traefik middlewares are one of its most powerful features: a single label can apply a rate limiter, basic auth, a header redirect or an IP whitelist to a router. In local development the automatic HTTP to HTTPS redirect is the most common middleware: one label activates it globally for all routes. That replaces manual nginx configuration for every project individually with a central middleware definition that all projects reuse by reference.


# docker-compose.yml - Traefik as local reverse proxy with HTTPS
services:
  traefik:
    image: traefik:v3.0
    restart: unless-stopped
    command:
      # Enable Docker provider - watch Docker socket for container events
      - "--providers.docker=true"
      - "--providers.docker.exposedByDefault=false"
      - "--providers.docker.network=traefik-proxy"
      # HTTP entrypoint on port 80
      - "--entrypoints.web.address=:80"
      # HTTPS entrypoint on port 443
      - "--entrypoints.websecure.address=:443"
      # Enable dashboard (insecure mode - local only)
      - "--api.insecure=true"
      # TLS certificates from file provider (mkcert)
      - "--providers.file.directory=/certs"
      - "--providers.file.watch=true"
    ports:
      - "80:80"
      - "443:443"
      - "8080:8080"    # Traefik dashboard
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - ./certs:/certs:ro    # mkcert-generated certificates
    networks:
      - traefik-proxy

networks:
  traefik-proxy:
    external: true
    name: traefik-proxy

4. HTTPS locally: mkcert, certificates and browser trust

Local HTTPS with genuine browser trust can be set up with mkcert in a few minutes. mkcert creates a local certificate authority (CA) and installs it in the system browser trust store. Certificates generated afterward are treated as trustworthy by the browser: no SSL warnings, no clicking through security dialogs. That is considerably cleaner than self signed certificates, which have to be manually added as exceptions.

The mkcert certificate can be issued as a wildcard certificate for *.local or specifically for the domains in use. With Traefik the certificate is registered through a file provider: a YAML file in the certificates directory defines which certificate is used for which domain. Traefik watches this directory and reloads certificates automatically when they change. That makes it possible to rotate certificates without restarting Traefik.

nginx-proxy supports HTTPS through a separate companion container system: acme-companion manages Let's Encrypt certificates for production environments, and nginx-proxy-certs allows manual certificates for local development. The certificate is placed as a file in a volume shared with the proxy container. Both approaches work well; the Traefik file provider is somewhat more flexible with automatic reloading.


# Setup: mkcert for local HTTPS - run once per developer machine
# Install mkcert (Linux)
curl -JLO "https://dl.filippo.io/mkcert/latest?for=linux/amd64"
chmod +x mkcert-v*-linux-amd64
sudo mv mkcert-v*-linux-amd64 /usr/local/bin/mkcert

# Install local CA into system/browser trust stores
mkcert -install

# Generate wildcard certificate for local domains
mkdir -p ~/docker-certs
cd ~/docker-certs
mkcert "*.local" "*.dev.local" "localhost" "127.0.0.1"
# Creates: _wildcard.local+2.pem and _wildcard.local+2-key.pem

# Traefik dynamic TLS config (certs/tls.yml - watched by Traefik)
cat > ~/docker-certs/tls.yml << 'EOF'
tls:
  certificates:
    - certFile: /certs/_wildcard.local+2.pem
      keyFile: /certs/_wildcard.local+2-key.pem
  stores:
    default:
      defaultCertificate:
        certFile: /certs/_wildcard.local+2.pem
        keyFile: /certs/_wildcard.local+2-key.pem
EOF

5. Multi-project setup: shared network and DNS

The core of a working multi-project setup with a local reverse proxy is a shared external Docker network. The proxy container is a member of this network, and every project's compose file connects its main services to it as well. That lets the proxy see every project's containers and forward requests, without requiring all projects to be defined in the same compose file.

The external network is created once with docker network create traefik-proxy. In each project's compose file it is referenced as an external network: networks: traefik-proxy: external: true. The project's relevant service connects both to the project-internal network (for communication with database and cache) and to the external proxy network (for reachability through the proxy).

There are two approaches for domain resolution: a static /etc/hosts file with entries for every local domain in use, or a local DNS tool such as dnsmasq that resolves all .local requests to 127.0.0.1. The latter is more flexible: new projects with new subdomains work automatically without touching /etc/hosts. On macOS, resolver support for .local domains via /etc/resolver/local is the recommended solution.

6. Traefik labels in detail: routing and middleware

Traefik labels in Docker Compose follow a consistent scheme: traefik.http.routers.NAME.rule defines the routing rule, traefik.http.routers.NAME.service references a service, and traefik.http.services.NAME.loadbalancer.server.port defines the backend port. For simple setups the routing rule with Host('domain.local') is enough. For HTTPS, traefik.http.routers.NAME.tls=true and a reference to an entrypoint are added.

Middlewares in Traefik are reusable configuration building blocks. A global HTTP to HTTPS redirect middleware is defined once in the Traefik configuration or as a label on a separate container and then activated by name reference in every router. That saves considerable configuration effort compared with nginx-proxy, where the redirect has to be configured separately for every container.


# docker-compose.yml - Project service with Traefik labels for HTTPS routing
services:
  app:
    image: ghcr.io/myorg/myapp:latest
    networks:
      - internal
      - traefik-proxy
    labels:
      # Enable Traefik for this container
      - "traefik.enable=true"
      # HTTP router: redirect to HTTPS
      - "traefik.http.routers.shop-http.rule=Host(`shop.local`)"
      - "traefik.http.routers.shop-http.entrypoints=web"
      - "traefik.http.routers.shop-http.middlewares=redirect-https"
      # HTTPS router
      - "traefik.http.routers.shop-https.rule=Host(`shop.local`)"
      - "traefik.http.routers.shop-https.entrypoints=websecure"
      - "traefik.http.routers.shop-https.tls=true"
      # Backend service port
      - "traefik.http.services.shop.loadbalancer.server.port=8080"
      # Network to use for upstream connection
      - "traefik.docker.network=traefik-proxy"

  db:
    image: mysql:8.4
    networks:
      - internal    # DB stays in internal network only - not exposed to proxy

# Shared redirect middleware (define once, reference everywhere)
# Can also be defined in traefik static config:
# traefik.http.middlewares.redirect-https.redirectscheme.scheme=https

networks:
  internal:
  traefik-proxy:
    external: true
    name: traefik-proxy

7. nginx-proxy with customized nginx configuration

Despite its simplicity, nginx-proxy offers extension points for project specific nginx configuration. In the volume /etc/nginx/vhost.d/, files can be placed that extend the generated server block for a specific domain. A file named shop.local in this directory is automatically included in the server block for shop.local. That enables project specific buffer sizes, timeout values, client_max_body_size or custom headers.

For HTTPS configuration with nginx-proxy and mkcert, certificates are placed as files in the volume /etc/nginx/certs/. The naming scheme matters: shop.local.crt and shop.local.key are automatically assigned to the server block for shop.local. nginx-proxy detects the certificates and activates TLS for the corresponding server block automatically. That is less flexible than the Traefik file provider, but sufficient for simple setups.

A common problem with nginx-proxy occurs when a container is configured for HTTPS but the certificate is missing or not yet ready. nginx-proxy then does not generate an HTTPS server block, and the container is only reachable over HTTP, with no obvious error message. Debugging requires a look at the generated nginx configuration. With Traefik, the dashboard would immediately show that no certificate is configured for the route.

8. Direct comparison: Traefik vs. nginx-proxy

The comparison between Traefik and nginx-proxy can be drawn along several dimensions relevant to local development.

Dimension nginx-proxy Traefik Advantage
Entry barrier Very low (VIRTUAL_HOST) Learn the label scheme nginx-proxy for getting started
Dynamic routing nginx reload on changes No reload needed Traefik for dynamic setups
Debugging Read generated conf Dashboard + logs Traefik for better visibility
Middleware Manual nginx configuration Labels, reusable Traefik for complex rules
Stability Very stable, few breaking changes Breaking changes between versions nginx-proxy for low maintenance

Both tools have their place. nginx-proxy is the better choice for developers who want to set up a proxy quickly and never think about it again. Traefik is the better choice for teams who actively configure their local stack, run several projects at once and value the dashboard as a debugging tool. For production environments the balance shifts: Traefik has significant advantages there with automatic Let's Encrypt integration and Kubernetes CRD support.

9. Recommendation: which proxy for which setup

Choose nginx-proxy when: the setup consists of a few fixed projects that are rarely added or removed. The team is familiar with nginx. Routing and middleware requirements are simple. A fast, low maintenance setup matters more than features.

Choose Traefik when: several projects are started and stopped dynamically. Middleware features such as automatic HTTPS redirects, rate limiting or auth are needed. The dashboard is meant to be used as a debugging tool. The stack is being prepared for production and consistency between local development and production is desired.

For the Mark Shust Magento Docker setup, nginx-proxy is the more proven choice: the setup is built around a single project, configuration is minimal and behavior is predictable. Anyone running several Magento projects at once and switching between them benefits from Traefik's dynamic service discovery. In that case it is worth introducing the Traefik network as a global standard and connecting every project to it.

Mironsoft

Local development environments, reverse proxy and Docker setups

Need a local development environment with HTTPS and multi-project routing?

We set up local Docker stacks with Traefik or nginx-proxy, mkcert certificates and multi-project networks, for productive development workflows without port conflict chaos.

Proxy setup

Set up Traefik or nginx-proxy with HTTPS, mkcert and multi-project routing

Dev environment

Run Magento, Shopware and other projects locally at the same time

Team onboarding

A reproducible development environment that runs identically on every developer's machine

10. Summary

Traefik and nginx-proxy solve the same problem with different philosophies. nginx-proxy is transparent, stable and ready to use with minimal configuration effort, ideal for teams looking for a low maintenance proxy without a learning curve. Traefik is dynamic, feature rich and offers considerably more flexibility for complex multi-project setups through its dashboard and label based configuration.

Local HTTPS with mkcert is set up in a few minutes with either proxy and removes the classic problem of self signed certificates in the browser. The shared external Docker network as the foundation of a multi-project setup is the right architecture regardless of the chosen proxy: the proxy has access to every project's containers, while the projects keep their internal network isolation for database access and other internal services.

Traefik vs. nginx-proxy: the essentials at a glance

Choose nginx-proxy when

A few fixed projects, nginx knowledge available, minimal configuration effort desired, low maintenance setup preferred.

Choose Traefik when

Dynamic number of projects, middleware features desired, dashboard valued for debugging, consistency with production Traefik desired.

HTTPS locally

mkcert -install, then a wildcard certificate for *.local. Traefik: file provider. nginx-proxy: certificate in the /etc/nginx/certs/ volume.

Multi-project network

docker network create traefik-proxy. Reference it as external: true in every compose file. The proxy is a member, services connect to it.

11. FAQ: Traefik and nginx-proxy for local stacks

1Main difference between Traefik and nginx-proxy?
nginx-proxy: generates nginx config from ENV, reloads nginx. Traefik: labels dynamically, no reload, dashboard. Traefik more powerful, nginx-proxy simpler.
2Set up HTTPS locally with Traefik?
mkcert -install, create a wildcard certificate, put it in a volume, configure the Traefik file provider. Traefik loads certificates automatically.
3Multiple Compose projects with Traefik?
docker network create traefik-proxy. external: true in every compose file. Connect the app service to the network and set labels.
4Traefik with Mark Shust Magento?
Yes, with adjustments. Connect the nginx service to the Traefik network, add labels, remove port exposure.
5What is mkcert?
Creates a local CA and installs it in the browser trust store. No SSL warnings for local domains. Done once per developer machine.
6nginx-proxy and Traefik at the same time?
Not recommended. Both listen on the Docker socket, causing routing conflicts. Choose one for the whole stack.
7Debug Traefik routing problems?
Dashboard at localhost:8080 shows routers and services. traefik.enable=true must be set on the container. docker logs traefik for label parsing errors.
8Debug nginx-proxy routing problems?
docker exec nginx-proxy cat /etc/nginx/conf.d/default.conf. VIRTUAL_HOST missing or wrong network. docker logs nginx-proxy for reload errors.
9Which proxy for production?
Traefik for Let's Encrypt automation and Kubernetes support. nginx-proxy for simple production setups with static domains.
10Avoid port conflicts with multiple projects?
Only the proxy exposes port 80/443. No project directly. All services over internal networks. Proxy routing handles domain to container mapping.