Traefik Domain Routing#
Dokploy converts each Domain database record into Traefik routing configuration at deploy time. There are two distinct code paths depending on service type:
| Service type | Routing config destination | Entry point |
|---|---|---|
| Compose apps | Labels injected into docker-compose YAML | createDomainLabels() |
| Regular apps | Per-app static YAML file written to disk | manageDomain() |
Both paths share the same Domain schema and produce equivalent routing rules.
Domain Schema#
The domains table is defined in packages/server/src/db/schema/domain.ts. Key fields that drive routing:
| Field | Type | Default | Purpose |
|---|---|---|---|
host | text | — | Traefik Host() rule |
port | integer | 3000 | Load balancer server port |
path | text | "/" | Optional PathPrefix() rule |
https | boolean | false | Enables websecure router + TLS |
certificateType | enum | "none" | letsencrypt, custom, or none |
customEntrypoint | text | — | Overrides default web/websecure split |
stripPath | boolean | false | Adds stripprefix middleware |
internalPath | text | "/" | Adds addprefix middleware |
middlewares | text[] | [] | Custom Traefik middleware names |
forwardAuthEnabled | boolean | false | SSO forward-auth gate (apps only) |
serviceName | text | — | Target service name in compose file |
domainType | enum | "application" | compose, application, or preview |
uniqueConfigKey | serial | auto | Unique suffix for router/service names |
Compose Path: Label Injection#
Injection flow#
getBuildComposeCommand()callswriteDomainsToCompose()before executing the deploy command.writeDomainsToCompose()callsaddDomainToCompose(), which loads the compose file, optionally applies randomization/isolated-deployment transforms, then iterates each domain.- For each domain,
createDomainLabels()builds the Traefik label list, which is prepended toservices.<name>.labels(docker-compose) orservices.<name>.deploy.labels(Swarm stack) . - The modified compose spec is serialized to YAML, base64-encoded, and written back to disk via a shell command .
Router naming#
Router and service names follow the pattern {appName}-{uniqueConfigKey}-{entrypoint} . This ensures uniqueness across multiple domains on the same app.
Labels generated by createDomainLabels()#
For each domain + entrypoint combination :
traefik.http.routers.<name>.rule=Host(`<host>`) [&& PathPrefix(`<path>`)]
traefik.http.routers.<name>.entrypoints=<entrypoint>
traefik.http.services.<name>.loadbalancer.server.port=<port>
traefik.http.routers.<name>.service=<name>
Conditional labels appended:
- Network:
traefik.docker.network=dokploy-network(docker-compose) ortraefik.swarm.network=dokploy-network(stack), omitted for isolated deployments . - TLS cert:
tls.certresolver=letsencrypt,tls.certresolver=<custom>, ortls=truefornone+https. - Middlewares:
redirect-to-https@file(web redirect router),stripprefix-*,addprefix-*, and any custom middlewares fromdomain.middlewares.
HTTPS dual-router logic#
When https=true and no customEntrypoint is set, addDomainToCompose() generates two routers :
webrouter — carries onlyredirect-to-https@file; all other middlewares are skipped here.websecurerouter — carries TLS config and all real middlewares (strip/add prefix, custom).
If customEntrypoint is set, only one router is created for that single entrypoint.
Application Path: File Config#
Regular (non-compose) applications use manageDomain() in packages/server/src/utils/traefik/domain.ts. Instead of labels, it writes per-app Traefik YAML config files to disk via writeTraefikConfig / writeTraefikConfigRemote.
Router names follow a slightly different pattern: {appName}-router-{uniqueConfigKey} (without the entrypoint suffix) . The HTTPS split still applies: a *-websecure-{uniqueConfigKey} router is added when https=true .
createRouterConfig() additionally handles:
- Punycode conversion for internationalized domain names .
- Redirect rules from the parent app — skipped for
domainType === "preview"so wildcard preview subdomains don't inherit the parent's redirect middlewares . - Security middlewares (basic auth) — the middleware name is derived from the parent app for preview domains .
- Forward-auth (SSO) — when
forwardAuthEnabled=true, error and auth middlewares are prepended .
Network Wiring#
For Traefik to reach compose services, services must share a network with the Traefik container.
- Shared mode (default):
dokploy-network(external) is injected into the service's network list and into the compose root networks block byaddDokployNetworkToService()andaddDokployNetworkToRoot(). Thetraefik.docker.network=dokploy-networklabel tells Traefik which network to use for routing. - Isolated mode (
isolatedDeployment=true): a per-app named network is created at deploy time and the Traefik container is connected to it . Thetraefik.docker.networklabel is omitted; Traefik finds the service on the isolated network automatically.
See the Docker Compose Networking article for more detail on network injection and collision prevention.
Key Source Files#
| File | Purpose |
|---|---|
packages/server/src/utils/docker/domain.ts | Core compose label generation: createDomainLabels, addDomainToCompose, writeDomainsToCompose |
packages/server/src/utils/traefik/domain.ts | App file-config generation: manageDomain, createRouterConfig |
packages/server/src/utils/builders/compose.ts | Orchestrates compose build and domain injection via getBuildComposeCommand |
packages/server/src/db/schema/domain.ts | Domain schema — all routing fields |