Repository navigation
Conversation
Dokploy recreates the Traefik container whenever the webserver is reloaded, and the container is created without an address, so it gets a new one from the network every time. Any setup that points at Traefik's address breaks on the next recreate: - split-DNS: a DNS server answering internal hostnames with Traefik's container address silently resolves to an address that no longer exists - anything else that references the address: firewall rules, SNAT targets, monitoring, host entries This adds a TRAEFIK_STATIC_IP setting that pins the container to a fixed address on the dokploy-network, so those setups keep working across recreates. The value is read from the "Traefik Env" setting, which is read from the running container and written back on every recreate, so no schema migration is needed and the address survives a recreate. Callers that don't pass an env (the startup path) inherit it from the running container. The address is validated before the container is created, so a misconfiguration reports its cause instead of Docker's "invalid endpoint settings": - it must be a valid IPv4 address - it must be inside the dokploy-network subnet - it must not be used by another container (the Traefik containers themselves keep the address across a recreate, so they don't count) Swarm assigns an address per task, so there is nothing to pin there. Instead of ignoring the setting silently, swarm mode fails with a clear error. Fixes Dokploy#5793
| // Restores a container left behind by an interrupted recreate before the | ||
| // address is read, so the value can be inherited from it. | ||
| await restoreInterruptedTraefik(serverId); | ||
| const staticIp = await resolveStaticIp(docker, env); |
There was a problem hiding this comment.
When initializeStandaloneTraefik() runs without env, resolveStaticIp reads the saved address, but the replacement container still receives Env: undefined. This pins the address once without saving the setting. If the Dokploy process does not also define TRAEFIK_STATIC_IP, the next recreate loses the pin and can break DNS or firewall rules that point at Traefik. Preserve the inherited setting in the replacement container and test two consecutive recreates.
| const subnet = network.IPAM?.Config?.find( | ||
| (config: { Subnet?: string }) => config.Subnet, | ||
| )?.Subnet; |
There was a problem hiding this comment.
assertStaticIpIsUsable checks only the first subnet in IPAM.Config. If a dual-stack network lists IPv6 first, every IPv4 address is rejected because isAddressInSubnet only accepts IPv4. An address in a later IPv4 subnet is also rejected. This blocks reloads with otherwise valid settings. Check the configured IPv4 subnets rather than whichever subnet appears first.
| if (staticIp) { | ||
| await assertStaticIpIsUsable(docker, staticIp); |
There was a problem hiding this comment.
The new address errors never reach users saving “Traefik Env”. writeTraefikEnv runs writeTraefikSetup in the background and only logs failures. Since these checks leave the old Traefik running, the health check succeeds and the editor reports “Traefik Env Updated” even for an invalid or conflicting address. Run these checks before returning success, or expose the background result to the editor.
…te before saving Address the review feedback on the static address handling: - The replacement container was created with `Env: undefined` when the caller didn't pass an env (the startup path), so the inherited address was pinned once and then dropped on the next recreate. The env is now inherited together with the address, and the container keeps it. - The subnet check only looked at the first entry of `IPAM.Config`, so a dual-stack network with the IPv6 subnet first rejected every IPv4 address, and an address in a second IPv4 subnet was rejected too. All IPv4 subnets of the network are checked now. - The new errors only reached the log of the background recreate, so the editor reported a successful update for an address that was rejected. `validateTraefikStaticIp` checks the address before it is stored, and the editor shows the error.
|
Thanks for the review - all three findings were valid and are fixed in 1. "Saved address disappears" - correct. The startup path called 2. "Valid addresses are rejected" - correct. The check only looked at the first entry of 3. "Rejected settings look saved" - correct. Tests: 14 in |
bf2684a to
1229252
Compare
| const staticIp = hasEnvValue(resolvedEnv, STATIC_IP_ENV_KEY) | ||
| ? readEnvValue(resolvedEnv, STATIC_IP_ENV_KEY) | ||
| : TRAEFIK_STATIC_IP; |
There was a problem hiding this comment.
Local address overrides server settings
If the Dokploy process has TRAEFIK_STATIC_IP set, an explicit env that omits the key now falls back to that address. Previously, the supplied env took precedence. Saving a remote server’s settings can therefore apply Dokploy’s local address and fail the reload if it is outside the remote subnet or already used. Deleting the key in the editor also no longer removes the pin.
Keep the process default limited to callers that omit env.
The process default (TRAEFIK_STATIC_IP in the Dokploy process env) was applied whenever the env didn't set the key. An env is the operator's choice though, so: - saving a remote server's settings could apply the address of the local Dokploy process, which is outside the remote subnet or already taken there - removing the key in the editor no longer removed the pin The process default now only applies to the callers that don't pass an env at all, which is the startup path. The same applies to the swarm mode check. Added tests for both cases.
|
Good catch - fixed in The process default (
The process default now only applies to the callers that don't pass an env at all, which is the startup path: const resolvedEnv = await resolveTraefikEnv(docker, env);
// The process default only applies to callers that don't pass an env at
// all: an env is the operator's choice, and for a remote server the
// address of the local Dokploy process would be the wrong one.
const staticIp = hasEnvValue(resolvedEnv, STATIC_IP_ENV_KEY)
? readEnvValue(resolvedEnv, STATIC_IP_ENV_KEY)
: env === undefined
? TRAEFIK_STATIC_IP
: "";The swarm mode check got the same treatment. Two tests cover it: the process default is applied on the startup path, and it is not applied when an env is given without the key (the address stays unset). Tests: 16 in the file, 155 in the traefik suite. Typecheck and Biome pass. |
|
One clarification on the test results, to be precise: The full suite has 4 failures on my machine, and all 4 also fail on pristine
Everything else passes: 1570 passed, 5 skipped. The traefik suite is 155/155. @greptile-apps the review on the latest commit is clean, and the four findings from the previous rounds are addressed:
|
|
Thanks for clarifying. I’ll record the test results precisely: 1,570 passed and 5 skipped; the 4 failures also reproduce on pristine Tip: You can customize Greptile's behavior for this repo with |
On an overlay network the swarm assigns the addresses of the tasks from the same pool as the network, so a pinned address can be taken by a task after a reboot without anyone changing the setting - and Traefik would then not start. The recreate now warns about it, and the error of a taken address explains it instead of only naming the container that holds the address.
What is this PR about?
Dokploy recreates the Traefik container whenever the webserver is reloaded, and the container is created without an address, so it gets a new one from the
dokploy-networkevery time:Any setup that points at Traefik's address breaks on the next recreate:
--ip.This adds a
TRAEFIK_STATIC_IPsetting that pins the container to a fixed address on thedokploy-network, so those setups keep working across recreates.Changes
The setting.
TRAEFIK_STATIC_IPis read from the "Traefik Env" setting (Settings -> Web Server -> Edit Traefik Env), which is read from the running container and written back on every recreate. No schema migration is needed and the address survives a recreate. Callers that don't pass an env (the startup path inapps/dokploy/setup.ts) inherit it from the running container.Applying the address. In standalone mode the address is passed to the container:
Validation. A wrong address otherwise fails with Docker's
invalid endpoint settings, which says nothing about the cause. The address is checked before the container is created:dokploy-networkSwarm mode. Swarm assigns an address per task, so there is nothing to pin. Instead of ignoring the setting silently, swarm mode fails with
TRAEFIK_STATIC_IP is not supported in swarm mode.Known limitation: overlay networks
On an overlay network the swarm assigns the addresses of the tasks from the same pool as the network. A pinned address can therefore be taken by a task after a reboot, without anyone changing the setting, and Traefik would then not start.
The recreate warns about it, and the error of a taken address names the container that holds the address and explains the overlay case:
To avoid it, the address can be placed outside the pool of the network, or the network can use a smaller pool. Happy to restrict the setting to bridge networks if that is preferred - the validation knows the driver of the network.
Notes for review
TRAEFIK_PORT/TRAEFIK_SSL_PORT/TRAEFIK_VERSIONpattern. Happy to move it to awebServerSettingscolumn instead if that is preferred.aux-address, or leaving swarm out of scope. I chose the explicit error because the VIP route needs a different mechanism and shouldn't be guessed - happy to switch to it if you prefer.Testing
apps/dokploy/__test__/traefik/standalone-traefik-static-ip.test.ts: the address is applied, no address leaves the endpoint untouched, the address and the env are inherited from the running container, the address survives two consecutive recreates, several IPv4 subnets, a dual-stack network, the Traefik containers are not a conflict, the validation errors, the pre-save validation and the overlay caveat.EndpointsConfig+IPAMConfig.IPv4Addressresults in the container actually getting that address, and that Docker rejects a conflicting address withAddress already in use- which is the failure the new validation turns into an actionable message.Checklist
canarybranch.Issues related (if applicable)
Closes #5793
Screenshots (if applicable)
N/A - no UI change beyond a placeholder hint in the "Traefik Env" editor.


Confidence Score: 5/5
The PR appears safe to merge, with no new actionable issues found.Summary
Adds
TRAEFIK_STATIC_IPto keep Traefik at a fixed container address across recreates.Reviews (4) · Last reviewed commit: "feat(traefik): warn about the overlay ca..." · Reviewed by Greptile