Skip to content

Migrating from Traefik

Traefik routes traffic by reading labels attached to containers or entries in its dynamic-config files. Dwaar achieves the same routing through two equivalent mechanisms: Docker labels (Dwaar watches the Docker socket and reloads routes live) and a Dwaarfile (a static config file you write once and reload on change). Both produce the same result; the labels path requires zero static files, while the Dwaarfile path is independent of Docker.

The sections below map every common Traefik pattern to its Dwaar equivalent, then walk through a full migration.


Traefik conceptDwaar equivalent
RouterSite block in Dwaarfile: example.com { … }
Service / loadbalancerreverse_proxy directive
MiddlewareDirectives (rate_limit, basic_auth, redir, header) or plugins
Provider (docker, file)Docker label watcher or Dwaarfile
Entrypoint (web, websecure)http_port / https_port in global options block
certificatesResolversAutomatic HTTPS — no configuration needed; Dwaar provisions certs from Let’s Encrypt by default
DashboardAdmin API at 127.0.0.1:6190
Static config (YAML/TOML)Global options block at top of Dwaarfile
Dynamic config (file provider)Site blocks in Dwaarfile
Traefik labels namespacedwaar.* label namespace

Each example shows the Traefik labels on the left and the Dwaar labels (or equivalent Dwaarfile block) on the right. The Docker Compose service structure is the same in both cases — only the labels change.

Traefik

services:
app:
image: myapp:latest
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"

Dwaar

services:
app:
image: myapp:latest
labels:
- "dwaar.domain=app.example.com"
- "dwaar.port=8080"

Dwaar enables HTTPS automatically and redirects HTTP → HTTPS with no extra labels. The dwaar.enable=true label is not required — any container with a dwaar.domain label is discovered automatically.


Traefik

labels:
- "traefik.http.routers.app.tls=true"
- "traefik.http.routers.app.tls.certresolver=letsencrypt"

Dwaar

labels:
- "dwaar.domain=app.example.com"
- "dwaar.port=8080"

No TLS labels are needed. Dwaar provisions a certificate from Let’s Encrypt the first time it sees the domain. To disable automatic HTTPS for a specific container:

labels:
- "dwaar.domain=app.example.com"
- "dwaar.port=8080"
- "dwaar.tls=off"

Traefik

services:
app1:
image: myapp:latest
labels:
- "traefik.enable=true"
- "traefik.http.routers.app.rule=Host(`app.example.com`)"
- "traefik.http.services.app.loadbalancer.server.port=8080"
app2:
image: myapp:latest
labels:
- "traefik.enable=true"
- "traefik.http.routers.app.rule=Host(`app.example.com`)"
- "traefik.http.services.app.loadbalancer.server.port=8080"

Dwaar

services:
app1:
image: myapp:latest
labels:
- "dwaar.domain=app.example.com"
- "dwaar.port=8080"
app2:
image: myapp:latest
labels:
- "dwaar.domain=app.example.com"
- "dwaar.port=8080"

Both containers share the same dwaar.domain. Dwaar adds them both to the upstream pool and distributes traffic round-robin. Unhealthy containers are removed from rotation automatically.


Traefik

labels:
- "traefik.enable=true"
- "traefik.http.routers.api.rule=Host(`example.com`) && PathPrefix(`/api`)"
- "traefik.http.services.api.loadbalancer.server.port=3000"

Dwaar labels do not support per-path routing directly. Use a Dwaarfile block instead:

example.com {
handle /api/* {
reverse_proxy api-service:3000
}
handle {
reverse_proxy web-service:8080
}
}

Alternatively, expose the API on a subdomain and use two separate label sets:

services:
api:
labels:
- "dwaar.domain=api.example.com"
- "dwaar.port=3000"
web:
labels:
- "dwaar.domain=example.com"
- "dwaar.port=8080"

Traefik

labels:
- "traefik.http.middlewares.ratelimit.ratelimit.average=100"
- "traefik.http.middlewares.ratelimit.ratelimit.burst=50"
- "traefik.http.routers.app.middlewares=ratelimit"

Dwaar

labels:
- "dwaar.domain=app.example.com"
- "dwaar.port=8080"
- "dwaar.rate_limit=100/s"

Dwaar uses a sliding-window estimator. The 100/s limit applies per IP address per domain. Requests over the limit receive 429 Too Many Requests with a Retry-After: 1 header.


Traefik

labels:
- "traefik.http.middlewares.auth.basicauth.users=alice:$$apr1$$..."
- "traefik.http.routers.admin.middlewares=auth"

Dwaar

Docker labels do not support credential configuration for basic auth — credentials would be visible in docker inspect output and process lists. Use a Dwaarfile block instead:

admin.example.com {
basic_auth "Admin Panel" {
alice $2b$12$W9qnDhPDIYYMMsVN5LRVZ.MCFhJJ0lMjx5Uagb0RTMP1bJG2xjhzS
}
reverse_proxy admin-service:9000
}

Generate bcrypt hashes with htpasswd -nbBC 12 alice 'yourpassword'. See Basic Auth for hash generation and security guidance.


Traefik

labels:
- "traefik.http.middlewares.strip.stripprefix.prefixes=/api"
- "traefik.http.routers.api.middlewares=strip"

Dwaar — use handle_path in a Dwaarfile block. It matches the prefix and strips it before the upstream sees the request:

example.com {
# GET /api/users → upstream sees GET /users
handle_path /api/* {
reverse_proxy api-service:3000
}
}

Traefik

labels:
- "traefik.http.middlewares.headers.headers.customresponseheaders.X-Custom=myvalue"
- "traefik.http.routers.app.middlewares=headers"

Dwaar

labels:
- "dwaar.domain=app.example.com"
- "dwaar.port=8080"

Then add custom headers in a Dwaarfile block:

app.example.com {
reverse_proxy app-service:8080
header {
X-Custom "myvalue"
Cache-Control "public, max-age=3600"
}
}

Or use the label for a single header (if your Dwaar deployment supports extended labels):

labels:
- "dwaar.domain=app.example.com"
- "dwaar.port=8080"
- "dwaar.header.X-Custom=myvalue"

Traefik

labels:
- "traefik.http.middlewares.redirect-to-https.redirectscheme.scheme=https"
- "traefik.http.routers.app-http.middlewares=redirect-to-https"

Dwaar — HTTP → HTTPS redirect is automatic when tls auto is active (the default). No labels or directives required.

For custom path redirects, use the Dwaarfile:

example.com {
redir /old-path /new-path 301
redir /legacy/* /new/{http.request.uri.path.remainder} 308
reverse_proxy app-service:8080
}

If you are moving away from Docker label discovery entirely — for example, to manage config in version control or to proxy non-Docker upstreams — translate each Traefik router to a Dwaarfile site block.

Traefik dynamic config (YAML)

http:
routers:
web:
rule: "Host(`example.com`)"
service: web-service
tls:
certResolver: letsencrypt
middlewares:
- rate-limit
- security-headers
services:
web-service:
loadBalancer:
servers:
- url: "http://localhost:3000"
- url: "http://localhost:3001"
middlewares:
rate-limit:
rateLimit:
average: 200
security-headers:
headers:
stsSeconds: 63072000
stsIncludeSubdomains: true

Dwaarfile equivalent

example.com {
reverse_proxy localhost:3000 localhost:3001
rate_limit 200/s
header {
Strict-Transport-Security "max-age=63072000; includeSubDomains; preload"
X-Content-Type-Options nosniff
X-Frame-Options DENY
}
}

Dwaar adds HSTS, X-Content-Type-Options, and X-Frame-Options by default as part of its automatic security headers. The header block shown above is only needed if you want to customise or override the defaults.


Traefik separates static config (entrypoints, certificate resolvers, providers) from dynamic config (routers, services, middlewares). Dwaar merges all of this into a single Dwaarfile.

Traefik static config (YAML)

entryPoints:
web:
address: ":80"
websecure:
address: ":443"
certificatesResolvers:
letsencrypt:
acme:
email: ops@example.com
storage: /acme.json
httpChallenge:
entryPoint: web
api:
dashboard: true
providers:
docker:
exposedByDefault: false

Dwaarfile equivalent

{
http_port 80
https_port 443
email ops@example.com
}
example.com {
reverse_proxy localhost:3000
}

The global options block replaces Traefik’s static config. The email field is the only required option for automatic HTTPS. Dwaar stores its ACME account and certificates on the filesystem at /etc/dwaar/certs — no acme.json file is needed.

To change the certificate storage path, set DWAAR_CERT_DIR in the environment or pass --cert-dir on the command line.


After migrating, the following Traefik-specific boilerplate is no longer needed:

Traefik itemWhy it is gone
traefik.enable=true labelAll containers with dwaar.domain are discovered automatically
entrypoints labelsDwaar listens on 80 and 443 by default; no per-router declaration
tls.certresolver labelsCertificate provisioning is always on; no resolver to name
Dashboard service and router configUse curl http://127.0.0.1:6190/routes instead
api.dashboard: true in static configAdmin API is always available on loopback
providers.docker.exposedByDefault: falseReplace with not labelling containers you don’t want proxied
Middleware chain declarationsDirectives are inlined in each site block; no named chain wiring
acme.json file and its 600 permission fixDwaar manages certs in a directory, not a single JSON file
traefik/traefik:latest image pullReplace with ghcr.io/permanu/dwaar:latest

  1. Deploy Dwaar alongside Traefik. Bind Dwaar to ports 80 and 443 on a test domain before cutting over production. Use DWAAR_CONFIG to point at a Dwaarfile that covers only the test domain.

  2. Translate one service at a time. For each Traefik router, create the equivalent Dwaar labels or Dwaarfile block. Use dwaar validate to check the config before applying it.

    Terminal window
    dwaar validate --config /etc/dwaar/Dwaarfile
    # Config valid.
  3. Verify TLS. After Dwaar starts, check that it obtained a certificate for each domain:

    Terminal window
    curl -s http://127.0.0.1:6190/routes | jq '.[] | {domain, tls}'
  4. Test each route. Send a request through Dwaar and confirm the upstream receives it correctly. Check that redirects, path stripping, and headers are applied as expected.

  5. Cut over DNS. Once all routes are validated, point DNS at the Dwaar host. If you were running Traefik on the same host, stop it first to free ports 80 and 443.

  6. Stop Traefik. Remove Traefik’s container and labels. Remove acme.json and any Traefik static config files.

  7. Remove Traefik labels from all services. Clean up traefik.* labels from your Docker Compose files. Run docker compose up -d to apply.