Ingress and instance URLs
Mark a service's HTTP port and every instance of it gets a stable, predictable URL. No per-deploy proxy configuration, no host-port allocation, no collisions between instances.
How the URL is built
{namespace}-{service}.{ingress-domain}
└────────────┬───────┘
the container name shop-staging-pr42-web.envs.example.com
The namespace is {project}-{environment}-{instance}, so the whole hostname is a single DNS
label — that is what lets one wildcard record and one wildcard certificate cover every instance
you will ever deploy. Multi-level subdomains would break both.
The ingress domain is a property of the runtime, which is how traffic
reaches the right host when you run several: envs.example.com for local, eu.envs.example.com
for eu-prod. A runtime with no ingress domain has no instance URLs — publishing host ports still
works everywhere.
Setting it up
- Point a wildcard DNS record at the runtime host:
*.envs.example.com → 203.0.113.10. The record points at the Docker host the runtime runs on, not at the Fadebox server: instance traffic goes straight to the Traefik on that host and never passes through the control plane. With several runtimes, each ingress domain points at its own host. For a local try-out,localtest.meresolves*.localtest.meto127.0.0.1with no DNS setup at all. - Set the ingress domain on the runtime under Settings → Runtimes, along with whether URLs are HTTPS and whether they require a Fadebox sign-in.
- Install the ingress stack from the runtime's Ingress action in the list. Fadebox deploys Traefik plus a read-only Docker socket proxy onto that runtime, labelled as system containers, and the dialog shows their live status and whether they still match the saved settings: up to date, or outdated once a setting, a Fadebox release or the egress proxy changed since they were created. Update applies the saved settings; Remove tears them down. Saving ingress settings never touches the running stack — the save ends by offering the dialog, and the dialog decides on the host's own word, not on what was typed. On a daemon two Fadebox installations share, the status, the update and the removal reach this installation's stack only: a neighbour's is neither reported as yours nor replaced by an update of yours.
Traefik derives its routes from container labels by watching the daemon's event stream, so a deploy needs nothing pushed to it and there is no proxy config to drift out of sync. Fadebox stamps the labels; it never manages proxy state.
Do not run the managed stack and a hand-installed proxy on the same ports — by default the stack binds 80 and 443. On a host where those are taken, move the ingress to a free port — see custom ports below.
Hosts that cannot reach Docker Hub
The stack is two images Fadebox pins per release and pulls from Docker Hub — in this release
traefik:v3.7 and tecnativa/docker-socket-proxy:v0.5.0. Templates name their own images and
registries; these two are the only ones Fadebox chooses for you, so on an air-gapped runtime host
they are the only ones you have to provide. Either of these makes the install go through:
- Load them onto the host. Pull both on a connected machine,
docker savethem, anddocker loadthem on the runtime host. The stack pulls only what the daemon does not already hold, so an image present on the host is never fetched. - Mirror them into a registry the host can reach, then set the registry prefix on
Administration → System (global
admin):registry.corp:5000/mirror, no scheme. It is prepended verbatim, so the stack pullsregistry.corp:5000/mirror/traefik:v3.7— the version pins stay Fadebox's and follow each release, only the source moves. If that registry needs a login, add it to the runtime under Settings → Runtimes like any other registry credential; the credential is matched on the registry host of the resolved reference, and the stack's images are what a runtime-level credential exists for. The prefix is installation-wide and read at the next Install or Reinstall / Upgrade of the stack on any runtime — no restart. The page says whether the value in force was saved there, came fromFADEBOX_IMAGE_REGISTRYor is the default, and Reset to default withdraws a saved one; every change is audited.
An install that fails on the pull says so, naming the two references it tried and both remedies,
rather than surfacing the daemon's raw error. A registry-mirrors entry in the daemon's own
configuration also works with no Fadebox setting at all, if you can edit that on every runtime
host.
TLS
For HTTPS, the runtime's HTTPS certificate setting picks how the managed stack certifies it:
- Let's Encrypt (the default): give the runtime ACME settings — an email address, a DNS
provider and its credentials, as
VAR=valuelines. Traefik then issues a wildcard certificate over DNS-01, which is the only challenge type that can cover*.envs.example.com. - Traefik default certificate: Traefik serves the self-signed certificate it mints at startup.
No ACME settings, no public DNS to solve a challenge against, no certificate authority to ask —
which suits an internal or air-gapped runtime that wants deployed apps served as
https://so browsers stop treating them as insecure origins, and nothing more: the connection is encrypted but unauthenticated, so every browser warns and other clients need an explicit trust exception. Switching modes keeps the stored ACME settings; switching back is a mode change and an update of the stack.
The credentials are write-only — accepted by the API, never returned. They end up as environment variables on the Traefik container, which is visible to anyone who can administer that daemon; that is the same trust level as the daemon itself.
Certificates live on a volume that survives upgrade and removal of the stack, so re-installing does not re-issue them. If you already hold certificates and would rather mount them, stay on a hand-installed Traefik.
On a network that only reaches the internet through a proxy, the managed stack inherits the
server's egress proxy: Traefik is started with
HTTP_PROXY, HTTPS_PROXY and NO_PROXY matching QUARKUS_PROXY_HOST, with its backends and the
socket proxy kept direct. There is nothing to set on the runtime; re-apply the stack after changing
the proxy. If the runtime requires a sign-in and Fadebox itself is only reachable directly, list its
host in QUARKUS_PROXY_NON_PROXY_HOSTS so the sign-in check does not go through the proxy.
Provider credentials
Read lego, not Traefik. Traefik keeps no provider list of
its own — it delegates the DNS-01 challenge to the lego library and points at that project's
documentation — so lego's DNS providers page is the source of truth for both fields, and each
provider has a page of its own: cloudflare,
gcloud, and so on.
The provider name is the value in that page's CLI flag name column, exactly as written:
cloudflare, gcloud, route53. The credentials are the variables from the provider's own
page, one VAR=value per line — for token-based providers, a single line:
CF_DNS_API_TOKEN=<token with DNS edit rights on the zone>
Ignore everything lego says about files. It lets any variable take a _FILE suffix and read its
value from a path, and some providers document a file variable first, but the credentials reach
Traefik only as environment variables — nothing mounts a key file into the managed container, so
a path there points at nothing. A provider needs an inline variant to be usable with the managed
stack. Google Cloud DNS (gcloud) is the common case where that variant takes some finding —
lego's page leads with application-default credentials and GCE_SERVICE_ACCOUNT_FILE, and neither
works here:
-
The ingress domain must be a managed zone in Cloud DNS, holding the runtime's wildcard record.
-
Create a service account with the DNS Administrator role and download a JSON key:
gcloud iam service-accounts create fadebox-acmegcloud projects add-iam-policy-binding PROJECT_ID \--member serviceAccount:fadebox-acme@PROJECT_ID.iam.gserviceaccount.com \--role roles/dns.admingcloud iam service-accounts keys create key.json \--iam-account fadebox-acme@PROJECT_ID.iam.gserviceaccount.com -
Set the runtime's credentials to the project id plus the key minified to one line (
jq -c . key.json):GCE_PROJECT=PROJECT_IDGCE_SERVICE_ACCOUNT={"type":"service_account","project_id":"PROJECT_ID",…}GCE_SERVICE_ACCOUNTtakes the key JSON itself;GCE_SERVICE_ACCOUNT_FILEis the path variant and cannot work here. If issuance times out while the challenge record propagates, addGCE_PROPAGATION_TIMEOUT=300as another line — lego's default is 180 seconds.
Reading the ingress logs
The managed Traefik logs at INFO, which is what makes certificate and routing activity
visible — Traefik's own default, ERROR, is silent until something has already failed, so a
certificate that was never requested and one issued fine look identical. Read it on the runtime
host:
docker logs fadebox-ingress-traefik # the socket proxy is fadebox-ingress-socket-proxy
Fadebox does not surface these logs in the UI; the log tail and export cover an instance's own containers, not the shared proxy. These are Traefik's application logs — per-request access logging is not enabled at either level. They are ordinary container logs, so the daemon-wide cap in Container logs bounds them like everything else on the host; Fadebox sets no limit of its own.
When INFO is not enough, Settings → Runtimes has Debug ingress logging on the runtime's Ingress card. It applies on the next Reinstall / Upgrade of the stack, not on save — and that re-apply briefly takes ingress down for every instance on that host, so it is not a free operation on a busy one. Issued certificates survive it.
Turn it off again once you have your answer. Beyond the volume, the Traefik container holds the runtime's DNS-provider credentials as environment variables for ACME, and verbose logging around certificate issuance is not where you want to be reading by default.
If you run Traefik yourself instead of the managed stack, none of this applies — it keeps its own
default level until you add --log.level to its command.
Custom ports and reverse proxies
By default the ingress listens on the scheme's standard port — 80, or 443 with HTTPS. Two per-runtime settings change that, both under Settings → Runtimes:
Ingress port moves the proxy off the standard port for hosts where it is taken or unbindable.
The managed stack binds and publishes that port instead, and the port appears in every generated
instance URL and in {{instance.host}} — shop-staging-pr42-web.envs.example.com:8443. With
HTTPS on a custom port, the usual :80 → https redirect listener is skipped: the standard
ports are not the stack's to bind on such a host. If you run the ingress bundle by hand instead
of installing the managed stack, set INGRESS_PORT in its .env and the matching Ingress
Port on the runtime — they must agree, or generated links point at a port nothing listens on.
Public port covers a reverse proxy in front of the ingress, where the two ports legitimately
differ: Traefik binds, say, 8443 while visitors reach the proxy on 443. Without it, every
generated link would carry the internal port. Set the public port and that is what URLs and
{{instance.host}} advertise instead of the bind port; setting it to the scheme default (80/443)
makes links port-free. It changes nothing about what the managed stack binds.
The public port assumes the fronting proxy passes TLS through to the ingress. Terminating TLS at the proxy and speaking plain HTTP behind it is not supported — the URL scheme is coupled to the runtime's own HTTPS/ACME setup.
Single-origin applications
One hostname per service is wrong for an application served as one origin — an SPA at / whose
API lives under /api/* and which calls it with relative URLs. Those services have to share a
hostname.
Give a port a path and it moves onto the instance host, {namespace}.{ingress-domain}, which
every path-routed service of that instance shares:
services:
web:
image: shop/web:1
labels:
fadebox.ingress.port: "80"
fadebox.ingress.path: "/" # claims the instance host
api:
image: shop/api:1
labels:
fadebox.ingress.port: "8080"
fadebox.ingress.path: "/api"
fadebox.ingress.strip: "true" # the app sees /orders, not /api/orders
- Order does not matter. The longest prefix wins, so
/api/ordersbeats/api, which beats/. Nothing has to be sorted or configured. /is a valid path and does not stop siblings from claiming deeper prefixes. Stripping/is rejected — there would be no rooted path left.- Mixing is fine. A service without a path keeps its own
{namespace}-{service}hostname, so an admin UI can live on a separate origin while the app is single-origin. - Collisions fail the deploy, not the request: if two services in an environment would produce the same route, the deploy is rejected naming both. A proxy would otherwise pick one silently.
A multi-port service assigns paths positionally — one path per port, in the same order as
fadebox.ingress.port.
Requiring a sign-in
Turning on Require fadebox sign-in for a runtime gates every instance URL on it behind a Fadebox session. Anonymous visitors are sent to the sign-in page and bounced back.
The mechanism is worth understanding, because it is designed around one constraint: the Fadebox session cookie must never reach instance hosts. A cookie widened to the parent domain would be sent to every deployed — and therefore untrusted — container, handing any preview app the operator's session. Instead:
- Traefik asks Fadebox whether the request is allowed. Without a valid per-host cookie, the visitor is redirected to Fadebox's own origin.
- There, where the real session is valid, Fadebox checks the target host against the ingress domains of enabled runtimes (so it cannot be used as an open redirect) and mints a 60-second token bound to that exact hostname.
- The token is exchanged for a cookie scoped to the instance host only, valid 12 hours. A cookie issued for one instance is useless against any other.
The deployed application sees only that per-host cookie — which is valid for its own hostname
anyway — and an X-Fadebox-User header naming the signed-in user.
Authorization is authentication-only: any signed-in Fadebox user may open any gated instance URL. Per-instance rules are not implemented.
This flow needs to know Fadebox's own browser-reachable address. Set FADEBOX_PUBLIC_URL if the
URL Traefik reaches Fadebox on is not the same one a browser would use.
What is not covered
- Non-HTTP traffic. Databases and other TCP services keep using published host ports — routing raw TCP through Traefik requires TLS with SNI, which does not apply here.
- Instance-to-instance isolation on the edge network. Every ingress-exposed container shares one bridge network with Traefik, so those containers can reach each other's exposed ports directly. That is the same trust domain as the shared host itself.