Hardened Image (Distro Zero)
Traefik Labs' Distro Zero image (or hardened image) is a vendor-supported secure runtime delivered as Traefik Hub in license-free Proxy Mode. It gives platform and security teams a container whose entire executable content is a single memory-safe binary, with validated cryptography built inside it. With a license, you can unlock every advanced capability in that same binary, from API gateway and API management to AI and MCP gateway.
Unlike the
standard Alpine-based image, it is built FROM scratch, adding only what the
statically-linked binary needs to run.
Why use the hardened image
- Smaller attack surface: no shell, no package manager, and no shared libraries beyond what the static binary needs, so there's nothing extra on disk for an attacker to abuse if the container is compromised.
- No interactive shell: even with a container escape or RCE, there's no
sh/bashto drop into. - Nonroot by default: runs as uid/gid 65532 out of the box, satisfying Kubernetes restricted Pod Security Standards and OpenShift restricted SCC without extra configuration.
- Pairs with FIPS: available as a combined
-fips-hardenedvariant, so teams needing both a minimal attack surface and FIPS compliance get one image, not a tradeoff between the two. - Works with Proxy Mode: run the hardened image without a license using Proxy Mode, then add a token later without changing the image. The gateway reads the token at startup, so restart it after you add the token.
The hardened image is a paid feature and must be enabled on your workspace. Contact sales to request access.
Runtime differences from the standard image
-
Runs as a nonroot user (uid/gid 65532) by default, unlike the standard image.
-
The Helm chart sets the entrypoints to ports 8000 and 8443 instead of 80 and 443. Outside Helm, Traefik Hub listens on port 80 unless you configure an entrypoint, so set the entrypoint addresses yourself.
-
Because a nonroot process can't bind ports below 1024, deployments that expose Hub directly on 80/443 (Docker, Compose, Swarm, Nomad without a fronting load balancer) need either:
- entrypoints on a port of 1024 or above, for example
--entryPoints.web.address=:8000, with the host port mapped to it (80:8000), or runAsUser: 0to opt back into root.
On its own,
cap_add: [NET_BIND_SERVICE]doesn't let the nonroot user bind these ports. Use one of the options above instead.Kubernetes
Serviceport remapping (for exampleport: 443→targetPort: 8443) absorbs this automatically, so it mainly affects non-Kubernetes platforms. - entrypoints on a port of 1024 or above, for example
Available variants
| Tag suffix | Base | FIPS |
|---|---|---|
-hardened | scratch | No |
-fips-hardened | scratch | Yes |
Both are published to a private container registry, separate from the public registry used by the standard and FIPS images.
The table lists the tags in the registry. Docker, Swarm, and Nomad use these
tags as they are. With Helm, set hub.hardened to true and use the tag
without the -hardened suffix. See
Deploying the hardened image.
Generating registry credentials
Workspace owners and admins can generate registry credentials from Workspace Settings > Registry credentials (only visible if your workspace has the hardened image feature):
- Click Create. The password is shown once: copy it immediately, since it cannot be retrieved again.
- Use the generated username/password with
docker loginagainst the registry host provided in your workspace settings. - Revoke a credential by typing its username to confirm.
Enabling the hardened image when creating a gateway
Once you have registry credentials, the simplest way to deploy the hardened image is to enable it directly in the gateway creation flow, rather than editing the install command by hand.
- Go to Gateways and create a gateway.
- Select a container platform. The Hardened image toggle is only displayed for Kubernetes, Docker Compose, Swarm, and Nomad: it isn't available for the Linux binary install as there is no container image involved.
- Turn on the Hardened image option. If you haven't generated registry credentials yet, an inline reminder links back to workspace settings.
- The generated install snippet updates automatically to use the hardened
image, so you can copy it as-is:
- Kubernetes: adds
--set hub.hardened=trueto the Helm command. - Docker, Swarm, Nomad: points the image reference at the private
registry with the
-hardenedtag suffix.
- Kubernetes: adds
The manual per-platform steps in the next section are useful if you're deploying outside this quick-config flow, for example scripting an install or adding the hardened image to an existing gateway's config directly.
Deploying the hardened image
The hardened image ships the same Traefik Hub binary as the standard image. Follow your platform's installation guide, then apply the changes below.
- Kubernetes
- Docker
- Nomad
In addition to the Kubernetes installation steps, create an image pull secret from your registry credentials and reference it in the Helm install:
kubectl create secret docker-registry hub-hardened-registry --namespace traefik \
--docker-server=<registry-host> \
--docker-username=<registry-username> \
--docker-password=<registry-password>
helm install traefik -n traefik --wait \
--set hub.token=license \
--set hub.hardened=true \
--set deployment.imagePullSecrets[0].name=hub-hardened-registry \
traefik/traefik
When hub.hardened is true, the chart pulls
registry.traefik.io/traefik-hub/traefik-hub and adds -hardened to the tag.
Set image.tag to the version without -hardened. A tag that already ends in
-hardened produces the image name ...-hardened-hardened, which doesn't
exist. If you set image.digest, the chart doesn't add the suffix, so use the
digest of the hardened image.
The hardened image also works with Proxy Mode. Replace --set hub.token=license
with --set hub.enabled=true, and set --set image.tag=v3.21.0 or newer since Proxy
Mode requires Traefik Hub >= v3.21.0. The registry pull secret and
hub.hardened=true flag stay the same.
Following the Docker installation guide, log in to the registry and swap the image:
docker login <registry-host> -u <registry-username> -p <registry-password>
services:
hub:
image: <registry-host>/traefik-hub/traefik-hub:v3-hardened
command:
- --providers.docker
- --entryPoints.web.address=:8000
- --entryPoints.websecure.address=:8443
- --hub.token=YOUR-TRAEFIK-HUB-TOKEN
ports:
- "80:8000"
- "443:8443"
group_add:
- "<docker-group-id>"
volumes:
- /var/run/docker.sock:/var/run/docker.sock
The entrypoints listen on ports 8000 and 8443 because the nonroot user can't
bind ports below 1024. The ports entries map host ports 80 and 443 to them.
The group_add entry gives the nonroot user access to the Docker socket. For
details, see Grant access to the Docker socket.
Omit the --hub.token=YOUR-TRAEFIK-HUB-TOKEN line to run the hardened image in
Proxy Mode, same as the standard image.
This also applies to Docker Swarm, with one exception. docker stack deploy
rejects group_add in a stack file. To give the nonroot user access to the
Docker socket, run docker service create --group <docker-group-id>, or set
user: "0:0" in the stack file.
Following the Nomad installation guide, add registry auth and swap the image in the task config:
config {
image = "<registry-host>/traefik-hub/traefik-hub:v3-hardened"
auth {
username = "<registry-username>"
password = "<registry-password>"
}
}
The hardened image supports Proxy Mode the same way the standard image does. See the Nomad installation guide for the token vs. no-token job configuration; the image and registry auth above apply either way.
Grant access to the Docker socket
The Docker and Swarm providers read the Docker socket. The socket is usually
owned by root and the docker group, with mode 660. The nonroot user (uid
65532) is in neither, so it can't read the socket and the gateway receives no
configuration from Docker. The Kubernetes and Nomad providers don't use the
Docker socket.
To grant access without running as root, add the socket's group to the container.
-
Get the group ID of the socket on the Docker host.
stat -c %g /var/run/docker.sock -
Set
<docker-group-id>to that value ingroup_add, or pass--group-add <docker-group-id>todocker run.
Setting user: "0:0" also grants access, but it removes the nonroot default of
the hardened image.
Combining with FIPS
A hardened FIPS image is available with the -fips-hardened tag suffix,
requiring both the hardened image and FIPS workspace features. For details,
see the FIPS 140-3 Compliance Reference.
Contact sales to request access to the
hardened image and FIPS features.
Debugging without a shell
The hardened image has no shell or package manager, so kubectl exec /
docker exec won't work. Before reaching for exec-based debugging, check
whether the existing observability surface already answers the question.
Hub's metrics, tracing, and (with --api.debug=true) pprof endpoints cover
most troubleshooting without needing a shell at all.
When you do need to inspect the running container:
Kubernetes: ephemeral debug container
Find the name of the Traefik Hub container.
kubectl get pod <pod-name> -o jsonpath='{.spec.containers[*].name}'
The Helm chart derives the container name from the release name, or from
fullnameOverride when you set it. With helm install traefik ..., the
container name is traefik.
Start the debug container with that name as the target.
kubectl debug -it <pod-name> --image=busybox --target=<container-name> -- sh
Attaches a toolbox container into the pod, sharing its network namespace, with
no changes to the running container. Once inside, the target container's
filesystem is available at /proc/1/root/.
Docker: namespace-sharing sidecar
docker run --rm -it --pid=container:<name> \
--mount=type=image,source=busybox:musl,destination=/dbg,ro \
--entrypoint /dbg/bin/sh \
<registry-host>/traefik-hub/traefik-hub:v3-hardened \
-c 'export PATH=/dbg/bin:$PATH; exec /dbg/bin/sh'
Runs a new instance of the hardened image itself, sharing the running
container's process namespace, with BusyBox's tools mounted read-only at
/dbg and the entrypoint overridden to /dbg/bin/sh since the hardened image
has no shell of its own.
Docker Desktop / Docker Business
docker debug <container>
Does the sidecar trick automatically if you have Docker Desktop.
Troubleshooting
listen tcp :80: bind: permission denied
The nonroot user can't bind ports below 1024. Set the entrypoints to ports 1024 or above and map the host ports to them. For an example, see Deploying the hardened image.
The Docker provider receives no configuration
The nonroot user can't read the Docker socket. Add the socket's group to the container. For steps, see Grant access to the Docker socket.
Image name ends with -hardened-hardened
The Helm chart adds -hardened to the tag when hub.hardened is true.
Remove -hardened from image.tag.
kubectl debug fails with targetContainerName: Not found
The --target value doesn't match a container in the pod. Find the container
name as described in
Kubernetes: ephemeral debug container.
