api.example.com, with no reloads or API calls.
Who owns Traefik?
Traefik is an external service that you install, configure, and run. Russel does not start a private Traefik instance or take ownership of ports 80 and 443. Russel publishes each app on a host port and writes that app’s dynamic route file. Traefik reads the files, forwards requests, and handles certificates when configured for HTTPS. You manage DNS, Traefik’s static configuration, and the public listeners. The setup below uses one shared Traefik instance on the VPS. It can serve both Russel apps and applications you run outside Russel. Keep the other applications’ routes in configuration you own; Russel manages only its service route files. Its router, service, and middleware names start withrussel-.
Choose how traffic reaches Russel apps
Traefik is currently the only implemented automatic ingress adapter. Caddy and Nginx can route directly to published app ports, but Russel does not generate or update their configuration.
Using an existing shared Traefik
Merge the settings below into your existing static configuration. Preserve its other entry points, providers, routes, and certificate resolvers. Russel’s generated routes expect entry points namedweb and, when RUSSEL_TRAEFIK_TLS=1, websecure. The certificate resolver name must match RUSSEL_TRAEFIK_CERT_RESOLVER.
If Traefik already has a file-provider directory, preserve the existing files when adding Russel’s routes. Set RUSSEL_TRAEFIK_DYNAMIC_DIR to a writable directory that the file provider reads. Do not replace the existing watched directory with an empty Russel directory and drop your other routes. Reserve Russel’s service filenames for Russel; do not edit its generated files by hand.
Russel checks hostname uniqueness among its service route files in its configured directory. It cannot detect every conflicting router from another directory or provider. Avoid claiming the same hostname in both Russel and your other proxy configuration, and reserve the russel- names for Russel.
Dedicated Traefik behind another proxy
Run the dedicated Traefik with itsweb entry point on an unused internal address, such as 127.0.0.1:8080, and its file provider watching Russel’s route directory. Keep RUSSEL_TRAEFIK_TLS off if the outer proxy terminates HTTPS. Configure the outer proxy to forward the Russel hostnames to that address, preserving the original Host header.
If Traefik runs in a separate container network namespace, configure an address it can reach for RUSSEL_TRAEFIK_BACKEND; its own loopback is not the VPS host’s loopback.
Manual routing
For an existing proxy that you want to configure yourself, give the app a fixed host port:http://127.0.0.1:4100 on the VPS. Set the hostname, TLS, and other routing options in that proxy’s configuration. A proxy in another network namespace needs a reachable host address instead of its own loopback.
The pinned port holds across updates, rollbacks, and restarts. Russel stops the old generation before starting the new one because they cannot share port 4100, so updates have a short outage. An unpinned port can change during an update; a manually configured proxy would keep pointing at the retired port unless you update its route too. A manually configured Traefik route has the same limitation.
There is currently no switch that disables Russel’s ingress adapter entirely. Even without an [ingress].host, Russel writes a default Traefik route. If no Traefik reads those files, they do not configure your existing proxy, but route-directory access and route validation still run during deploy. Keep those files outside any proxy configuration you intend to manage manually. A live Traefik watching the directory will still consume the generated routes; simply leaving out [ingress] does not opt out.
Known limitations and related work
- Russel’s generated routes expect Traefik entry points named
weband, for HTTPS,websecure. Existing installations with other names need compatible static configuration. - Manual proxy configuration needs a pinned backend port. Turning off a future adapter alone would not make an unpinned port stable or provide automatic zero-downtime routing.
- If your Russel release writes route files with a
.jsonextension, Traefik’s file provider does not load them. This was fixed in the cutover fix, which changes these files to.yamland adds proxy-confirmed cutover and drain handling. RUSSEL_TRAEFIK_ENTRYPOINTselects the address for cutover confirmation. A dedicated Traefik on an internal port needs this variable set to its internal address, for examplehttp://127.0.0.1:8080.offdisables only the probe, not route-file creation or hostname checks. Confirmation at an internal Traefik does not validate DNS or the outer proxy.
What you’ll need
- An externally managed Traefik v2 or v3 instance on the server, either shared or dedicated.
- A DNS record for each app name, pointing at the server. Russel doesn’t manage DNS.
- Ports 80 and 443 open on the public proxy. The direct setup below gives those listeners to Traefik.
1. Give Traefik a folder it can read
By default Russel writes routes to/var/lib/russel/traefik/dynamic/. /var/lib/russel is private to the russel account (mode 0700), so a Traefik running as its own user can’t read it. Give the routes their own folder, writable by russel and readable by Traefik’s group:
traefik with the group your Traefik runs as. Russel writes each file with mode 0644.
2. Configure Traefik
A minimal static config (/etc/traefik/traefik.yml) for plain HTTP:
3. Pick host names
With Traefik configured to load Russel’s routes and no[ingress] table, a service is reachable at <service name>.<RUSSEL_TRAEFIK_DOMAIN>. The domain defaults to russel.local, so the example api service is api.russel.local.
Set your own domain:
host is used exactly as written (lowercased), and can be a bare domain like example.com. Two services can’t claim the same name. To go back to the default name, delete the [ingress] table and run russel update <id> --refresh.
Test it once DNS points at the server:
4. Add HTTPS
Add awebsecure entry point and a Let’s Encrypt resolver to the static config:
acme.json with mode 0600, owned by Traefik’s user, and on storage that survives reboots:
web and websecure, and Traefik gets a certificate for each host name on first use. The name must be public (not russel.local), resolve to this server, and be reachable on port 80.
Settings
All of these go in/etc/russel/env, followed by sudo systemctl restart russel-ctrl.
What Russel writes
For a service namedapi on port 3100, the file api.yaml looks like this. It is JSON, which Traefik reads as YAML; Traefik skips files with other extensions, such as the .json files earlier Russel releases wrote. Russel removes an old .json file the next time it writes the route.
stop or destroy the file is deleted. Networking explains the switch.
The X-Russel-Route header names the backend the route points at. During an update Russel sends requests for the host to RUSSEL_TRAEFIK_ENTRYPOINT until the new version’s value comes back, and only then retires the old version. What zero downtime covers explains what happens when Traefik doesn’t switch, or isn’t there.