Migrate from Traefik
ReverseDock’s configuration model is a superset of Traefik’s dynamic configuration: entry points, routers, services and middlewares map onto listeners, routes, services and middlewares, and route rules use the Traefik v3 grammar unchanged. This page moves a configuration written for Traefik’s file provider since v0.1.
Traefik Docker labels are read by the Docker provider since v0.2, and rdock migrate traefik turns labels or a dynamic configuration into YAML since v0.2: see Docker labels and rdock migrate traefik below.
The Traefik facts below come from the Traefik v3.7 documentation (checked 2026-10-27) and from the ReverseDock research of Traefik v3.7.16, recorded in ADR-0014.
Concept map
Section titled “Concept map”| Traefik v3 | ReverseDock |
|---|---|
Static entryPoints |
listeners (reference) |
http.routers |
routes (reference) |
Router rule |
routes.<name>.match (same grammar, or a structured match) |
Router priority |
routes.<name>.priority |
Router entryPoints (all entry points when unset) |
routes.<name>.listeners (defaults.listeners when unset, which is [websecure]) |
Router tls and certResolver |
routes.<name>.tls and defaults.tls; automatic certificates planned for v0.3 |
http.services.<name>.loadBalancer.servers[].url |
services.<name>.backends[].address plus services.<name>.scheme |
Server weight |
backends[].weight |
passHostHeader (default true) |
pass_host_header (default true) |
serversTransport (rootCAs, serverName, insecureSkipVerify, forwardingTimeouts) |
services.<name>.tls (ca_file, server_name, verify) and services.<name>.timeouts |
http.middlewares.<name>.<kind> |
middlewares.<name> with a type (reference) |
chain middleware |
the ordered middlewares list of the route |
tls.certificates (a list) |
certificates (a map: each certificate has a name) |
tls.stores.default.defaultCertificate |
defaults.certificate |
entryPoints.<name>.forwardedHeaders.trustedIPs |
security.trusted_proxies (one list for all listeners) |
entryPoints.<name>.http.redirections.entryPoint |
defaults.redirect_http_to_https (on by default) |
entryPoints.<name>.transport.respondingTimeouts |
listeners.<name>.timeouts |
name@file references |
name or name@file |
providers.file.directory |
one rdock.yaml; extra files (providers.file.paths) since v0.2 |
providers.docker and labels |
providers.docker with traefik_labels since v0.2 |
certificatesResolvers (ACME) |
acme planned for v0.3 |
Middleware kinds:
Traefik http.middlewares |
ReverseDock type |
|---|---|
headers (customRequestHeaders, customResponseHeaders) |
headers (request.set, response.set; an empty Traefik value that removes a header becomes remove) |
headers (stsSeconds, stsIncludeSubdomains, stsPreload) |
headers with hsts (max_age, include_subdomains, preload) |
headers (frameDeny, contentTypeNosniff) |
headers with response.set of X-Frame-Options: DENY, X-Content-Type-Options: nosniff |
headers (accessControlAllowOriginList, accessControlAllowMethods …) |
cors (allowed_origins, allowed_methods …) |
redirectScheme |
redirect with scheme |
redirectRegex |
redirect with regex and replacement |
stripPrefix |
strip_prefix |
addPrefix |
add_prefix |
replacePath |
replace_path |
replacePathRegex |
rewrite |
basicAuth |
basic_auth |
forwardAuth (authResponseHeaders, trustForwardHeader) |
forward_auth (copy_headers, trust_forward_header) |
ipAllowList (sourceRange) |
ip_allow (cidrs); ip_deny has no Traefik twin |
rateLimit |
rate_limit |
compress |
compress |
errors (status, service, query) |
errors (status, service, path) |
| (none) | maintenance |
retry |
services.<name>.retries planned for v0.4 |
Five common setups
Section titled “Five common setups”Each pair shows the Traefik configuration and a complete rdock.yaml that passes rdock validate --strict. Traefik’s static configuration is traefik.yml; its dynamic configuration is a file read by the file provider.
1. One HTTPS site with your own certificate
Section titled “1. One HTTPS site with your own certificate”Traefik redirects HTTP to HTTPS on the web entry point and serves the router on websecure with a certificate from the dynamic file.
entryPoints: web: address: ":80" http: redirections: entryPoint: to: websecure scheme: https websecure: address: ":443"providers: file: directory: /etc/traefik/dynamichttp: routers: app: rule: "Host(`app.example.com`)" entryPoints: [websecure] service: app tls: {} services: app: loadBalancer: servers: - url: "http://10.0.0.5:3000"tls: certificates: - certFile: /etc/traefik/certs/example.pem keyFile: /etc/traefik/certs/example.keyReverseDock attaches routes to websecure and redirects plain HTTP to HTTPS by default, so neither needs to be written.
listeners: web: { address: ":80" } websecure: { address: ":443", tls: {} }
certificates: example: cert_file: /etc/rdock/certs/example.pem key_file: /etc/rdock/certs/example.key
routes: app: match: "Host(`app.example.com`)" service: app
services: app: backends: [{ address: "10.0.0.5:3000" }]2. Several sites and a path prefix that is stripped
Section titled “2. Several sites and a path prefix that is stripped”http: routers: www: rule: "Host(`www.example.com`)" entryPoints: [websecure] service: www tls: {} api: rule: "Host(`www.example.com`) && PathPrefix(`/api`)" entryPoints: [websecure] service: api middlewares: [strip-api] tls: {} blog: rule: "Host(`blog.example.com`)" entryPoints: [websecure] service: blog tls: {} middlewares: strip-api: stripPrefix: prefixes: ["/api"] services: www: loadBalancer: servers: [{ url: "http://10.0.0.10:8080" }] api: loadBalancer: servers: [{ url: "http://10.0.0.11:9000" }] blog: loadBalancer: servers: [{ url: "http://10.0.0.12:2368" }]The api route wins over www for /api paths in both proxies, because its rule is longer and the rule length is the default priority.
listeners: web: { address: ":80" } websecure: { address: ":443", tls: {} }
certificates: example: cert_file: /etc/rdock/certs/example.pem key_file: /etc/rdock/certs/example.key
routes: www: match: "Host(`www.example.com`)" service: www api: match: "Host(`www.example.com`) && PathPrefix(`/api`)" service: api middlewares: [strip-api] blog: match: "Host(`blog.example.com`)" service: blog
middlewares: strip-api: { type: strip_prefix, prefixes: ["/api"] }
services: www: backends: [{ address: "10.0.0.10:8080" }] api: backends: [{ address: "10.0.0.11:9000" }] blog: backends: [{ address: "10.0.0.12:2368" }]Both proxies add X-Forwarded-Prefix: /api when the prefix is stripped.
3. Basic authentication and security headers
Section titled “3. Basic authentication and security headers”http: routers: admin: rule: "Host(`admin.example.com`)" entryPoints: [websecure] service: admin middlewares: [admin-auth, secure-headers] tls: {} middlewares: admin-auth: basicAuth: usersFile: /etc/traefik/admins.htpasswd secure-headers: headers: stsSeconds: 31536000 stsIncludeSubdomains: true frameDeny: true contentTypeNosniff: true services: admin: loadBalancer: servers: [{ url: "http://10.0.0.20:8000" }]listeners: web: { address: ":80" } websecure: { address: ":443", tls: {} }
certificates: example: cert_file: /etc/rdock/certs/example.pem key_file: /etc/rdock/certs/example.key
routes: admin: match: "Host(`admin.example.com`)" service: admin middlewares: [admin-auth, secure-headers]
middlewares: admin-auth: type: basic_auth users_file: /etc/rdock/admins.htpasswd realm: Admin secure-headers: type: headers hsts: { max_age: 365d, include_subdomains: true } response: set: X-Frame-Options: DENY X-Content-Type-Options: nosniff
services: admin: backends: [{ address: "10.0.0.20:8000" }]Traefik’s htpasswd files usually hold Apache MD5 ($apr1$) hashes. ReverseDock accepts them with a warning, so rdock validate --strict fails on them. Re-create each entry with rdock hash --user NAME, which reads the password from standard input and prints an argon2id user:hash line. See Basic auth.
4. Weighted backends, one of them over HTTPS with a private CA
Section titled “4. Weighted backends, one of them over HTTPS with a private CA”http: routers: shop: rule: "Host(`shop.example.com`)" entryPoints: [websecure] service: shop tls: {} services: shop: loadBalancer: serversTransport: internal-ca servers: - url: "https://10.0.0.30:8443" weight: 2 - url: "https://10.0.0.31:8443" weight: 1 serversTransports: internal-ca: serverName: shop.internal.example.com rootCAs: - /etc/traefik/certs/internal-ca.pemIn ReverseDock the scheme belongs to the service, not to each server, so every backend of a service speaks the same protocol.
listeners: web: { address: ":80" } websecure: { address: ":443", tls: {} }
certificates: example: cert_file: /etc/rdock/certs/example.pem key_file: /etc/rdock/certs/example.key
routes: shop: match: "Host(`shop.example.com`)" service: shop
services: shop: scheme: https backends: - { address: "10.0.0.30:8443", weight: 2 } - { address: "10.0.0.31:8443", weight: 1 } tls: server_name: shop.internal.example.com ca_file: /etc/rdock/certs/internal-ca.pemTraefik’s default strategy is weighted round-robin; ReverseDock’s round_robin is the same, and random is the other policy available in v0.1. Health checks are planned for v0.4, so a backend that is down keeps receiving its share of requests until then.
5. Rate limit and IP allow-list behind a load balancer
Section titled “5. Rate limit and IP allow-list behind a load balancer”Traefik reads the client IP from the TCP peer unless the middleware has an ipStrategy; depth picks an entry of X-Forwarded-For counted from the right.
http: routers: api: rule: "Host(`api.example.com`)" entryPoints: [websecure] service: api middlewares: [office-only, api-limit] tls: {} middlewares: office-only: ipAllowList: sourceRange: ["203.0.113.0/24"] ipStrategy: depth: 1 api-limit: rateLimit: average: 100 burst: 200 period: 1s sourceCriterion: ipStrategy: depth: 1 services: api: loadBalancer: servers: [{ url: "http://10.0.0.40:8080" }]ReverseDock resolves the client IP once, from security.trusted_proxies, and every middleware and the ClientIP matcher use that address. List the load balancer’s network instead of an ipStrategy.
listeners: web: { address: ":80" } websecure: { address: ":443", tls: {} }
certificates: example: cert_file: /etc/rdock/certs/example.pem key_file: /etc/rdock/certs/example.key
security: trusted_proxies: [10.0.0.0/24]
routes: api: match: "Host(`api.example.com`)" service: api middlewares: [office-only, api-limit]
middlewares: office-only: { type: ip_allow, cidrs: [203.0.113.0/24] } api-limit: { type: rate_limit, average: 100, burst: 200, period: 1s }
services: api: backends: [{ address: "10.0.0.40:8080" }]See Behind a proxy for how the client IP is resolved.
Docker labels and rdock migrate traefik
Section titled “Docker labels and rdock migrate traefik”There are two ways to move a Docker setup with Traefik labels since v0.2:
-
Keep the labels. Enable the Docker provider;
traefik.*labels are read as they are, within the supported subset. Setproviders.docker.entrypointswhen your entry points are not calledwebandwebsecure. See Docker containers and labels.rdock.yaml listeners:web: { address: ":80" }websecure: { address: ":443", tls: {} }certificates:example:cert_file: /etc/rdock/certs/example.pemkey_file: /etc/rdock/certs/example.keyproviders:docker:network: proxyentrypoints: { http: web, https: websecure } -
Turn them into YAML.
rdock migrate traefikreads a Compose file, or a Traefik dynamic configuration, and prints routes, services and middlewares through the Docker provider’s own translator. Everything it cannot carry over is listed at the top of the output.Terminal window rdock migrate traefik compose.yaml -o conf.d/app.yamlrdock migrate traefik /etc/traefik/dynamic/app.yml > conf.d/app.yamlrdock validate rdock.yamlCompose service names become backend hosts (
api:8080). A service withoutexpose,portsor a port label gets port 80 with a note, because Traefik would use the port the image exposes, which the Compose file does not say. Traefik exposes containers by default; ReverseDock does not, so services withouttraefik.enableare skipped unless you pass--expose-by-default.
The provider differs from Traefik on purpose in a few places (opt-in containers, no guessed port, no default rule): see Differences from Traefik.
Differences and gotchas
Section titled “Differences and gotchas”These differences are deliberate (ADR-0014). Rules written for Traefik route the same way unless a point below says otherwise.
Routing
Section titled “Routing”- Priority is one number, as in Traefik:
prioritywhen set, else the byte length of the rule as written. Explicit and computed priorities compete, sopriority: 1still makes a catch-all. Ties go to the route name; Traefik breaks ties by provider and then leaves the order unspecified. - Matchers behave as in Traefik: names are case-insensitive;
Hostignores case, the port and a trailing dot;PathPrefixis a plain string prefix (/apimatches/apiv2);HostRegexpandPathRegexpare unanchored; one-argumentQuery(`k`)matches only an empty value;Methodcompares in upper case. See Rules. - Paths are normalised and the normalised path is forwarded, as in Traefik 3.7. Encoded slashes (
%2F) are allowed and never act as separators. ClientIP,ip_allow,ip_deny,rate_limitandmaintenance.allowuse the resolved client IP. Withoutsecurity.trusted_proxiesthis is the TCP peer, the same as Traefik. Behind a load balancer it is the address the load balancer reports; Traefik needs anipStrategyper middleware for that.- Route defaults differ. A Traefik router without
entryPointsattaches to every entry point, and one withouttlsserves plain HTTP. A ReverseDock route withoutlistenersattaches todefaults.listeners([websecure]) and asks for HTTPS (defaults.tls: auto). A route meant for plain HTTP needslisteners: [web]andtls: off. - Until automatic certificates arrive planned for v0.3, a route on an HTTPS listener needs a certificate under
certificatesthat covers each of its hosts, ordefaults.certificate.rdock validatewarns, andrdock runrefuses the configuration, when a host is not covered.
Forwarding headers and limits
Section titled “Forwarding headers and limits”- From clients that are not trusted proxies,
X-Forwarded-*,X-Real-IP,ForwardedandCF-Connecting-IPare removed (also when spelled with_) and then written again. From trusted proxies they are kept and the peer is appended toX-Forwarded-For.X-Real-IPcarries the resolved client IP.X-Forwarded-Protoiswsorwsson WebSocket handshakes, as in Traefik. - HTTP/1.1 requests without
Hostget 400,Expectvalues other than100-continueget 417, and bare-LF line endings in request headers close the connection. - Request headers are limited to 64 KiB and 100 headers, and the request target to 8 KiB. Traefik allows far larger headers (Go’s 1 MiB default, per the research behind ADR-0014). Requests over a limit get 431 or 414. There is no body limit unless you set
security.limits.max_body_bytes, as in Traefik. See Timeouts and limits.
Timeouts
Section titled “Timeouts”- Listener timeouts are per read and per write, not totals:
read60 s,write60 s,idle60 s, plus a 10 s deadline (request_header) for receiving the request headers. Traefik’sreadTimeout(60 s) bounds reading the whole request,writeTimeoutis 0 (none) andidleTimeoutis 180 s (Traefik entry points documentation, checked 2026-10-27). - Upstream timeouts are
connect5 s,read60 s andwrite60 s, each per operation as in NGINX, andidle90 s for pooled connections. Traefik’sserversTransportdefaults aredialTimeout30 s andresponseHeaderTimeout0 (none) (checked 2026-10-27). A backend that sends nothing for more than 60 s, such as a long poll, gets a 504 in ReverseDock; raiseservices.<name>.timeouts.readorroutes.<name>.timeouts.readfor it.
Middlewares
Section titled “Middlewares”redirect.permanentdefaults tofalseas in Traefik’sredirectScheme(checked 2026-10-27): 302 for GET and 307 for other methods; withpermanent: true, 301 and 308. A redirect to the current URL does nothing.- The automatic HTTP→HTTPS redirect uses 301 for GET and 308 otherwise, like Traefik’s entry-point redirect, whose
permanentdefaults to true (checked 2026-10-27). It covers only hosts that have a route on an HTTPS listener; other hosts get 404 on the plain listener. basic_authaccepts argon2id and bcrypt, and Apache MD5 ($apr1$,$1$) and SHA-1 ({SHA}) with a warning. Plain-text passwords are refused. The default realm isrdock; Traefik’s istraefik.forward_authalways removes thecopy_headersfrom the client request before copying the auth service’s values, so a client cannot forge them. If the auth service cannot be reached the request fails closed with 502, or 504 on timeout; Traefik answers 500. The defaulttimeoutis 5 s.rate_limitdefaultsbursttoaverage; Traefik’s default burst is 1 (checked 2026-10-27). Requests over the limit get 429 withRetry-Afterat once; Traefik first delays them briefly (Traefik v3.7.16 source, per the research behind ADR-0014).average: 0is a validation error, where Traefik reads it as no limit.ip_allowanswers 403 and has norejectStatusCode.corsis its own middleware. It answers preflights itself with 204;*together withallow_credentials: trueis a validation error, where Traefik echoes the request’sOrigin. List the origins instead.compressuses gzip on an allow-list of text types and never touches server-sent events, gRPC, partial responses or HEAD; it honours q-values. Brotli and zstd are planned for v0.4.errorskeeps the original status and also covers ReverseDock’s own 502, 503 and 504 answers.maintenancehas no Traefik equivalent: it answers with itsstatus(503 by default),Retry-After,Cache-Control: no-storeand your page, except for theallownetworks.
Configuration files
Section titled “Configuration files”- Unknown keys are errors, with a suggestion for the nearest valid key, so a Traefik key such as
stsSecondspasted into a ReverseDock middleware is reported, not ignored. Keys that start withx-are ignored, for YAML anchors. - Settings the model accepts but v0.1 cannot honour are reported:
rdock validatelists them as warnings (“planned for vX.Y”) andrdock runrefuses them. In v0.1 these are ACME and DNS-01, TCP and UDP listeners and routes, the PROXY protocol, traffic splits, load-balancing policies other thanround_robinandrandom, health checks, retries, sticky sessions, client certificates to backends, brotli and zstd, extra configuration files, the Docker, Swarm and Kubernetes providers,cloudflareintrusted_proxies,client_ip_header: cf-connecting-ip, and tracing. See the roadmap.
Cutover
Section titled “Cutover”Run ReverseDock next to Traefik on other ports, compare the answers, then move the ports. Traefik keeps its configuration untouched, so rolling back means starting it again.
-
Write
rdock.yamland validate it with the same files it refers to:Terminal window rdock validate --strict rdock.yaml -
Copy it to
rdock-test.yamland change only the listener addresses to free ports, for example:8081forweband:8444forwebsecure. If another service already uses port 2080, also setadmin.listenerto another loopback port. Start the test instance with its own data directory:Terminal window rdock run --config rdock-test.yaml --data-dir ./rdock-test-data -
Send the same requests to both proxies and compare status lines and headers.
--resolvesends the real host name to the local port:Terminal window curl -sk -o /dev/null -D - --resolve app.example.com:443:127.0.0.1 https://app.example.com/curl -sk -o /dev/null -D - --resolve app.example.com:8444:127.0.0.1 https://app.example.com:8444/Repeat for every route: the paths behind
PathPrefixrules, authenticated pages (with and without-u user:password), a WebSocket or long-running endpoint, and plain HTTP to check the redirect (curl -s -o /dev/null -w '%{http_code} %{redirect_url}\n' http://127.0.0.1:8081/ -H 'Host: app.example.com'should print301 https://app.example.com:8444/). ReverseDock adds anX-Request-IDheader to every answer; it is also in the access log. -
Check the access log of the test instance for unexpected 404, 502 or 504 answers. Each line names the
route,serviceandupstream. -
Switch: stop Traefik, then start ReverseDock with the real
rdock.yamlon ports 80 and 443. With systemd that issystemctl stop traefikfollowed bysystemctl enable --now rdock; with Docker Compose, move theportsfrom the Traefik service to the ReverseDock one and rundocker compose up -d. -
Watch the first minutes:
rdock statusshows the generation and the last reload, andcurl -s http://127.0.0.1:2080/metricsshowsrdock_http_requests_totalby route and status class. -
Roll back if needed: stop ReverseDock and start Traefik again. Nothing in Traefik’s configuration changed.
Checklist
Section titled “Checklist”- Every router has a route, and every route’s
matchis the router’srule. - Routers that served plain HTTP have
listeners: [web]andtls: off. - Every HTTPS host is covered by a certificate under
certificates, or bydefaults.certificate. forwardedHeaders.trustedIPsand everyipStrategyare replaced bysecurity.trusted_proxies.basicAuthusers are re-hashed withrdock hash, or the MD5 and SHA-1 warnings are accepted without--strict.rate_limitsetsburstexplicitly if you relied on Traefik’s burst of 1.- Long polls, streams and slow backends have a
timeouts.readlong enough for them. - Redirects that should be cached by browsers set
permanent: true. corslists origins instead of*when credentials are allowed.- Nothing planned for a later version is in the file:
rdock validate --strict rdock.yamlexits 0. - Responses were compared with
curlon the test ports before the switch, and the rollback steps are written down.
Related: Concepts · Routing and priority · HTTP behaviour · Configuration reference