"I have a Traefik setup with a stack of docker labels and a dynamic config. Show me the equivalent
asdmanifests so I can compare side by side and migrate without losing routes."
Most Traefik concepts map one-to-one to fields in net.manifest.yaml.
This page is the translation table.
Traefik configuration lives in two places: the static config
(traefik.yml / startup flags) and per-service docker labels (or
dynamic providers). asd collapses both into one
net.manifest.yaml per service. Most migrations are mechanical —
read the labels off, write the YAML in.
The mapping isn't always 1:1 (Traefik middlewares vs Caddy handlers
have different vocabulary), but the missing pieces always have an
asd answer.
Traefik (docker labels):
labels:
- "traefik.enable=true"
- "traefik.http.routers.my-app.rule=Host(`my-app.example.com`)"
- "traefik.http.services.my-app.loadbalancer.server.port=3000"
asd:
# packages/my-app/net.manifest.yaml
id: my-app
endpoint:
url: "http://127.0.0.1:3000"
caddy:
hostRoute:
host: "my-app.example.com"
tunnel:
public: true
Traefik:
- "traefik.http.routers.my-app.tls=true"
- "traefik.http.routers.my-app.tls.certresolver=letsencrypt"
asd: automatic. With tunnel.public: true, the tunnel server
terminates TLS. For local-only routes (tunnel.public: false),
Caddy's internal CA does it (see cookbook/self-signed-https).
No config needed.
Traefik:
- "traefik.http.routers.my-app.rule=PathPrefix(`/api`)"
- "traefik.http.middlewares.my-strip.stripprefix.prefixes=/api"
- "traefik.http.routers.my-app.middlewares=my-strip"
asd:
caddy:
hostRoute: { host: "..." }
forwardedPrefix: "/api"
Traefik:
- "traefik.http.middlewares.cors.headers.accesscontrolalloworiginlist=*"
- "traefik.http.middlewares.cors.headers.framedeny=true"
- "traefik.http.routers.my-app.middlewares=cors"
asd:
caddy:
responseHeaders:
Access-Control-Allow-Origin: ["*"]
X-Frame-Options: ["DENY"]
Traefik:
- "traefik.http.middlewares.my-auth.basicauth.users=user:$$apr1$$..."
- "traefik.http.routers.my-app.middlewares=my-auth"
asd:
basicAuth:
enabled: true
username: "${{ env.MYAPP_USER }}"
password: "${{ env.MYAPP_PASSWORD }}"
asd does the bcrypt hashing for you — supply plaintext (in .env,
ideally per-mode via learn/04)
and asd hashes at runtime.
Traefik:
- "traefik.http.middlewares.my-oidc.forwardauth.address=https://oauth2.example.com/verify"
- "traefik.http.routers.my-app.middlewares=my-oidc"
asd:
caddy:
authPolicy: api-users # defined in asd.yaml caddy.apps.security
authBypassPaths: ["/oauth2/*", "/healthz"]
See cookbook/put-an-api-behind-sso
for the full OIDC setup including the policy definition.
Traefik:
- "traefik.http.middlewares.ratelimit.ratelimit.average=100"
- "traefik.http.middlewares.ratelimit.ratelimit.burst=200"
asd: drop into caddy.rawRoutes with a Caddy rate_limit handler.
Caddy's rate limiter is a separate module — confirm it's bundled in
your asd distribution before relying on this.
Traefik:
- "traefik.http.routers.my-app.rule=Host(`new.example.com`) || Host(`legacy.example.com`)"
asd:
caddy:
hostRoute:
host: "new.example.com"
routes:
- match:
- host: ["legacy.example.com"]
handle:
- handler: reverse_proxy
upstreams:
- dial: "127.0.0.1:${{ env.MYAPP_PORT }}"
Traefik: works out of the box. asd: also works out of the box,
unless you've dropped into rawRoutes, in which case set
flush_interval: -1 on the reverse_proxy handler.
A typical migration ends up about 30-50% the line count of the
equivalent Traefik label set. No more $$ double-escaping in YAML
strings, no labels-vs-static-config split.
Side-by-side: a Traefik docker-compose.yml with one service +
three middlewares (around 25 labels) compresses to ~15 lines of
net.manifest.yaml.
asd services,asd net, asd urls). No drop-in replacement.asd net apply. For k8s, you probably want anlearn/02-first-service, then add complexity per Levels 3–6.compared/vs-traefik, compared/vs-cloudflare-tunnel.reference/net-manifest — every field.