No shell? No problem.
Scratch, distroless and minimal images usually cannot run sh, curl, ps or strace. debux brings those tools from a dedicated debug image.
debux gives you an OrbStack/Docker Debug-style shell for Docker and Kubernetes: a rich Nix-powered toolbox that joins the target container's namespaces and lets you inspect distroless, scratch, Alpine and minimal images without rebuilding them.
Modern production images are small, locked-down and often shell-less. That is great for shipping. It is painful when something breaks. debux adds a temporary toolbox next to the container instead of changing the container.
Scratch, distroless and minimal images usually cannot run sh, curl, ps or strace. debux brings those tools from a dedicated debug image.
The debug container shares the target network and process namespaces, then exposes the target filesystem at $DEBUX_TARGET_ROOT.
Start with a curated set of tools, then install more with dctl install. Docker stores persist globally; Kubernetes keeps tools when you reuse the same debug container.
Install the release binary with one command, then point debux at a Docker container or Kubernetes pod. Docker pulls the debug image automatically; Kubernetes pulls it in-cluster.
curl -fsSL https://raw.githubusercontent.com/clement-tourriere/debux/main/install.sh | sh
debux docker://
# Or browse Docker, Kubernetes and recent sessions
# in the full-screen TUI
debux tui
# Docker picker
debux docker://
# Kubernetes picker, current kube-context namespace
debux k8s://
# Kubernetes picker in another kube context
debux k8s://@eks-preprod-01
# Force a brand-new debug container
debux k8s://prod/api-pod/app --fresh
Use the TUI when you want to find a target visually instead of typing an exact container or pod name. It presents Docker, Kubernetes and History as clear source sections, supports keyboard navigation beyond number shortcuts, and avoids loading every pod at startup.
# Open the full-screen browser
debux tui
# Same command, shorter alias
debux ui
/ filter the current listtab / shift+tab cycle between the Docker, Kubernetes and History sources1 / 2 / 3 jump straight to Docker, Kubernetes or Historyenter drill into source/context/namespace lists or open a selected targetb or esc go back in Kubernetes navigation (esc first clears an active filter)s search pods across the selected namespace when the initial pod list is too larget open externally only when DEBUX_TERMINAL is explicitly configuredr reload the current viewf, c, v, o, p, i toggle fresh, copy mode, volumes, read-only volumes, profile and pull policys from the pod view to search the whole namespace by substring.
enter always uses the current terminal and returns to the TUI when the shell exits. The optional t shortcut is disabled unless you explicitly set DEBUX_TERMINAL.
$XDG_STATE_HOME/debux/history.json, or ~/.local/state/debux/history.json when XDG_STATE_HOME is unset, so the TUI can quickly reopen previous debug sessions.
The recommended install path downloads a GitHub Release binary for Linux/macOS on amd64/arm64. Source builds still use mise for Go, hk, pkl and build tooling.
curl -fsSL https://raw.githubusercontent.com/clement-tourriere/debux/main/install.sh | sh
Installs to ~/.local/bin by default, refuses unverifiable release assets without checksums.txt, and verifies signed checksums when cosign is available.
curl -fsSL https://raw.githubusercontent.com/clement-tourriere/debux/main/install.sh | sh -s -- --version v1.2.3
curl -fsSL https://raw.githubusercontent.com/clement-tourriere/debux/main/install.sh | sh -s -- --bin-dir /usr/local/bin
curl -fsSL https://raw.githubusercontent.com/clement-tourriere/debux/main/install.sh | DEBUX_ALLOW_SOURCE_BUILD=1 sh
debux update --check
debux update
debux doctor
debux doctor --strict
debux version --json
debux completion zsh
git clone https://github.com/clement-tourriere/debux.git
cd debux
mise run install
mise run image-build
mise run release:bump then mise run release:push. Pushing a vX.Y.Z tag reachable from main runs the Release workflow, which keyless-signs and publishes the GHCR debug image and creates the GitHub Release with signed checksums and CLI archives. The bump is a no-op when there are no release-eligible conventional commits. Release binaries pin the matching versioned debug image; dev builds use latest.
debux does not mutate your target container. It creates a debug companion with tools, then joins what matters.
Docker uses a sidecar container. Kubernetes uses an ephemeral container by default or a copied pod with --copy.
Network and PID namespaces let you inspect localhost services, process trees and attach tools like strace.
The target filesystem is reachable through /proc/1/root and exposed as $DEBUX_TARGET_ROOT.
target to jump into the target filesystem. If you run a command the toolbox lacks but the target has, debux runs the target's own binary via chroot into $DEBUX_TARGET_ROOT with the target's original environment — just like docker exec. Otherwise it offers to install the tool with Nix.
Use debux locally exactly where docker exec stops being useful: distroless images, missing tools, stopped containers, or debugging without rebuilding. Docker Compose services and Podman containers work through the same workflow.
# Start something tiny
docker run -d --name my-nginx nginx:alpine
# Debug by name
debux my-nginx
# Or with an explicit runtime prefix
debux docker://my-nginx
# A Docker Compose service (one replica if scaled)
debux compose://web
debux compose://my-project/web
# A Podman container via its Docker-compatible socket
debux podman://my-app
# Full-screen browser with Docker, Kubernetes and history
debux tui
# Lightweight picker for running containers
debux docker://
# Force a new debug sidecar
debux docker://my-nginx --fresh
# Inject env and Linux capabilities
debux docker://my-nginx --env LOG=debug --cap-add NET_ADMIN
# Do not mount target volumes into the debug container
debux docker://my-nginx --no-volumes
# Or mount target volumes read-only to reduce accidental writes
debux docker://my-nginx --read-only-volumes
/target. The original container is never started, and changes outside mounted volumes are discarded on exit. This matches a key docker debug capability.
Docker mode uses image-specific Nix volumes such as debux-nix-store-<image-id>. This keeps installed packages persistent while avoiding stale store volumes after rebuilding the debug image. Treat those volumes as trusted debug-session state; tools installed with dctl can affect later sessions using the same debug image.
Use ephemeral containers for in-place debugging. If RBAC or policy blocks them, use copy mode to create a temporary duplicate pod; use copy mode carefully for workloads with side effects.
# Current kube-context namespace
debux k8s://
debux k8s://api-pod
# Explicit namespace and container
debux k8s://prod/api-pod/app
# Or pass the namespace as a flag
debux k8s://api-pod/app --namespace prod
# Explicit kube context in the target
debux k8s://@eks-preprod-01/prod/api-pod/app
# Or use --context for complex context names
debux k8s://prod/api-pod/app --context eks-preprod-01
# Not exact? debux proposes matching running pods
debux k8s://prod/webapp-internal-api
# Pull a fresh debug image and force a new container
debux k8s://prod/api-pod/app --fresh --pull-policy=Always
# When pods/ephemeralcontainers RBAC is denied
debux k8s://prod/api-pod/app --copy
# Long-lived copy session: survives rollouts, kept after exit,
# self-destructs after 48h (kubelet-enforced activeDeadlineSeconds)
debux k8s://prod/api-pod/app --copy --keep --ttl=48h
debux k8s://prod/debux-copy-abc12 # reattach later
debux kill k8s://prod/debux-copy-abc12 # delete it
# Standalone toolbox pod
debux pod -n prod
# Keep standalone pod after exit
debux pod -n prod --keep
# Debug a node (like kubectl debug node/, with the Nix toolbox)
debux node # node picker
debux node worker-1 --profile=sysadmin --keep
debux node [node-name] schedules a host-namespace toolbox pod (hostPID/hostNetwork/hostIPC), mounts the node root filesystem at /host ($DEBUX_TARGET_ROOT), and tolerates taints so cordoned or NotReady nodes can still be debugged. Node binaries like crictl, journalctl and systemctl run through the chroot fallback with their original host paths.
--image.
FROM ghcr.io/clement-tourriere/debux:latest
ARG NIXPKGS_REF=github:NixOS/nixpkgs/1c3fe55ad329cbcb28471bb30f05c9827f724c76
RUN NIX_CONFIG="experimental-features = nix-command flakes" \
nix profile add --profile /nix/var/debux-profile \
"${NIXPKGS_REF}#postgresql" \
"${NIXPKGS_REF}#redis" \
"${NIXPKGS_REF}#kubectl"
| Mode | Best for | Notes |
|---|---|---|
ephemeral container | Debugging a live pod in-place | Shares target container PID namespace when supported; cannot be removed from the pod spec after creation. |
--copy | RBAC or PodSecurity blocks ephemeral containers; sessions that must survive rollouts or Karpenter consolidation | Creates a copied pod (process namespace sharing enabled), deleted on exit unless --keep. Annotated karpenter.sh/do-not-disrupt and expired by a kubelet-enforced TTL (--ttl, default 24h) so kept or orphaned copies cannot leak forever. |
debux pod | Cluster network troubleshooting | Standalone debug pod in the current kube-context namespace by default, optionally with host networking. |
debux node | Node-level debugging | Host-namespace toolbox pod pinned to a node, node root at /host, tolerates taints. Like kubectl debug node/. |
Prefixes make the runtime explicit. A bare target defaults to Docker.
| Format | Runtime | Meaning |
|---|---|---|
my-container | Docker | Debug Docker container by name or ID (running or stopped). |
docker:// | Docker | Open the Docker container picker. |
docker://my-container | Docker | Debug a specific Docker container. |
compose://service | Docker | Debug a Docker Compose service by name (one replica if scaled). |
compose://project/service | Docker | Debug a Compose service in a specific project. |
podman://my-container | Podman | Debug a Podman container via its Docker-compatible socket. |
k8s:// | Kubernetes | Open the pod picker in the current kube-context namespace. |
k8s://pod | Kubernetes | Debug a pod in the current kube-context namespace. |
k8s://namespace/pod | Kubernetes | Debug a pod in an explicit namespace (or use --namespace / -n). |
k8s://pod/container --namespace namespace | Kubernetes | Debug a specific container using the namespace flag. |
k8s://namespace/pod/container | Kubernetes | Debug a specific container in a multi-container pod. |
k8s://@context | Kubernetes | Open the pod picker in a specific kube context. |
k8s://@context/pod | Kubernetes | Debug a pod in a specific context and that context's namespace. |
k8s://@context/namespace/pod | Kubernetes | Debug a pod in a specific context and namespace. |
k8s://@context/namespace/pod/container | Kubernetes | Debug a specific container in a specific context. |
Generated completions provide live Docker and Kubernetes targets without turning Tab into a cluster-wide scan.
# zsh example
debux completion zsh > ~/.zfunc/_debux
# If replacing an older zsh completion script
rm -f ~/.zcompdump*
exec zsh
docker://: running containers.--image and debux image: local Docker images.k8s://: kube contexts, the default namespace, and pods from the selected namespace.k8s://namespace/: pods in that namespace; .../pod/: containers.Kubernetes pod completion is scoped and cached for speed. Typing 3+ characters also matches substrings, so k8s://inte<Tab> can find webapp-internal-api-....
The debug shell is zsh with common tools, target-aware helpers, and a small package manager named dctl.
echo $DEBUX_TARGET_ROOT
target
ls /target
cat $DEBUX_TARGET_ROOT/etc/hosts
dctl search postgres
dctl install postgresql
dctl list
dctl remove postgresql
ps aux
curl localhost:8080
strace -p 1
tcpdump -i any
Run a command the toolbox does not have but the target does, and debux runs the target's own binary via chroot into $DEBUX_TARGET_ROOT with the target's original environment — like docker exec. Great for app-specific CLIs (a bundled node, php, psql, or a custom entrypoint helper) without installing anything.
Use --tools to auto-install nixpkgs packages before the shell opens. A value can be literal packages or the name of a tool set defined in your config file.
debux my-app --tools py-spy --tools gdb
debux k8s://prod/api --tools python
--profile=sysadmin for a fully privileged session or --profile=netadmin for network capabilities. --profile=restricted supports shell startup and dctl install with the current debug image, but deep chroot/target-root integration can be limited by Linux permissions. You can also add capabilities ad hoc with --cap-add and inject environment with --env.
# Run a one-shot command through the debug toolbox; exit code propagates
debux docker://my-app -- curl -fsS localhost/health
debux k8s://prod/api/app -- ps aux
Sometimes the target image cannot start at all. debux can copy its filesystem into a debug container and expose it at /target.
# Works with scratch and distroless images too
debux image gcr.io/distroless/static-debian12
# Keep the debug container after exit
debux image my-app:broken --rm=false
Reach a running target and move files in or out — even for containers started without -p and distroless pods where kubectl cp fails.
# Forward local 8080 to the target's port 80
debux forward my-app 8080:80
# Same local and remote port
debux forward docker://my-app 5432
# A Compose service
debux forward compose://web 8080:3000
# Multiple ports to a pod
debux forward k8s://prod/api-pod 8080:8080 9090:9090
debux forward k8s://api-pod -n prod 8080:8080
# Pull a log file out of a container
debux cp my-app:/var/log/app.log ./app.log
# Pull a heap profile out of a distroless pod
debux cp k8s://prod/api-pod:/app/heap.prof ./
# Push a tool into a pod (destination is a directory)
debux cp ./debug-tool k8s://prod/api-pod:/tmp
# Works with Compose services too
debux cp compose://web:/usr/share/nginx/html ./html
forward runs a small socat relay on the target's network and publishes the ports on 127.0.0.1, so it works for containers without published ports; Kubernetes uses the pod port-forward API. Kubernetes cp streams through the debux toolbox, so copies work on shell-less images with no tar; Docker cp uses the engine API and also works on stopped containers. Copying into a target treats the destination as a directory.
debux is not just an interactive shell. Pass a command after -- to run it non-interactively in the toolbox, and the target's real exit code propagates — so debux drops into scripts and CI pipelines.
# One-shot command, exit code propagates
debux docker://my-app -- curl -fsS localhost/health
debux k8s://prod/api-pod/app -- ps aux
# Use it as a CI gate
debux k8s://prod/api-pod/app -- sh -c 'test -f /app/ready'
# Know before an incident whether debugging is allowed:
# per-profile Kubernetes RBAC preflight
debux doctor k8s://prod/api-pod/app --profile=restricted
# Machine-readable, non-zero exit on any failing check
debux doctor k8s://prod/api-pod/app --json --strict
debux doctor: with no target it checks the debux binary, Docker, and the current Kubernetes context. With a target it focuses on that runtime and, for Kubernetes, evaluates the chosen security profile's required RBAC — a good CI check before granting or relying on debug access.
Set defaults once in $XDG_CONFIG_HOME/debux/config.yaml (or the path in $DEBUX_CONFIG). A missing file is fine, and flags always override the config.
image: ghcr.io/clement-tourriere/debux:latest
profile: restricted
pull-policy: IfNotPresent
terminal: wezterm
tools:
python: [python3, py-spy, gdb]
net: [socat, mtr, iperf3]
image, profile, pull-policy, terminal set defaults for the matching flags.tools maps a set name to a list of nixpkgs packages.--tools python expands a configured set; --tools socat also accepts literal packages.# Auto-installs python3, py-spy and gdb at session start
debux k8s://prod/api --tools python
debux is a debugger, not a sandbox. The default Kubernetes profile is intentionally powerful because production debugging often needs process, filesystem, and network visibility.
general profile, a debux session can usually run as root inside the pod, use the pod network namespace, target the selected container's PID namespace, list pod processes, inspect /proc/1/root, mount target volumes directly, and use debugging capabilities such as SYS_PTRACE, SYS_ADMIN, and SYS_CHROOT.
localhost.ps and, when allowed, strace.$DEBUX_TARGET_ROOT.| Profile | Purpose | Typical use |
|---|---|---|
general | Default. Runs as root and adds useful debugging capabilities like ptrace/chroot. | Best UX, highest access inside the pod. |
baseline | No explicit security context. Not a non-root guarantee because the image default user is root. | Clusters where policy should decide defaults. |
restricted | Non-root, drops capabilities, runtime default seccomp. Shell startup and dctl install work with the current debug image, but chroot/target filesystem integration is limited by Linux permissions. | Policy-restricted environments. |
netadmin | Adds network capabilities. | Network debugging with tools like tcpdump. |
sysadmin | Privileged container. | Last resort for deep kernel/namespace debugging. |
--no-volumes only disables direct volume mounts into the debug container, and --read-only-volumes makes those direct mounts read-only to reduce accidental writes. Neither is a security boundary if the debug container can still access the target through /proc/1/root. Kubernetes ephemeral containers also cannot add new volumes, so debux cannot mount your local Docker toolbox/history into arbitrary pods. Reusing the same debug container on the same pod keeps its installed tools; across pods, bake common tools into a custom debug image and pass it with --image.
RBAC implication: granting a user permission to update pods/ephemeralcontainers and create pods/exec is effectively granting the ability to run code inside selected pods. Treat it like production shell access.
# Minimal namespace-scoped RBAC for ephemeral-container debugging
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: debux-debugger
namespace: prod
rules:
- apiGroups: [""]
resources: ["pods"]
verbs: ["get", "list", "create"]
- apiGroups: [""]
resources: ["pods/exec"]
verbs: ["create"]
- apiGroups: [""]
resources: ["pods/ephemeralcontainers"]
verbs: ["update"]
# Non-root restricted Kubernetes debug shell
debux k8s://prod/api/app \
--profile=restricted \
--fresh \
--pull-policy=Always
# Full privileged debug session
debux k8s://prod/api/app \
--profile=sysadmin
Docker installed packages and shell history persist in image-specific Nix volumes. Use the store commands to inspect or clean those Docker stores. Runtime dctl install uses the debug image's pinned nixpkgs revision unless a custom image overrides NIXPKGS_REF.
debux store info
debux store clean
Clean the store if you want to reclaim disk space, reset installed tools, or remove old image-specific Docker Nix volumes after frequent debug image rebuilds. Kubernetes does not use these local Docker volumes.
The most important commands and flags in one place.
| Command / flag | Description |
|---|---|
debux [target] | Debug a running Docker container or Kubernetes pod. |
debux exec [target] | Explicit form of the default debug command. Aliases: debug, shell. |
debux tui | Open the full-screen target browser for Docker, Kubernetes and recent sessions. Alias: ui. |
debux image <image> | Debug a Docker image filesystem without starting it. |
debux pod | Create a standalone Kubernetes debug pod. |
debux node [node-name] | Debug a Kubernetes node with a host-namespace toolbox pod. Node root at /host. |
debux forward <target> [LOCAL:]REMOTE... | Forward local ports to a container or pod. |
debux cp <source> <destination> | Copy files between a target and the local filesystem. |
debux kill [target] | Stop a running debux debug session. |
debux completion | Generate shell completions for bash, zsh, fish, or PowerShell with cached Docker/Kubernetes target suggestions. |
debux docs | Print the documentation URL. Use --open to open it in a browser. |
debux doctor | Diagnose Docker/Kubernetes connectivity, RBAC, target pod status, and selected security profile. |
debux update | Check GitHub Releases and update the current debux binary in place. |
debux version | Show version metadata. Use --json for automation. |
--image <image> | Override the debug image. |
--fresh | Force a new debug container instead of reusing an existing one. |
--copy | Kubernetes: create a copied debug pod instead of an ephemeral container. |
--keep | Kubernetes: with --copy, keep the copy pod after the session ends. Reattach by targeting it; delete it with debux kill. |
--ttl | Kubernetes: with --copy, kubelet-enforced deadline (activeDeadlineSeconds) after which the copy pod is stopped. Default 24h, 0 disables. The deadline fails the pod, which also ends karpenter.sh/do-not-disrupt protection (Karpenter ignores terminal pods). |
--no-volumes | Do not mount target volumes directly. This is not an isolation boundary if the debug container can access /proc/1/root. |
--read-only-volumes | Mount target volumes read-only in the debug container to reduce accidental writes. This is not an isolation boundary if /proc/1/root is accessible. |
--env KEY=VALUE | Inject an extra environment variable into the debug container (repeatable, both runtimes). |
--cap-add CAP | Add a Linux capability to the debug container (repeatable, both runtimes). |
--tools name-or-packages | Auto-install a config tool set or nixpkgs packages at session start (repeatable). |
--pull-policy | Debug image pull policy for Docker/Kubernetes: Always, IfNotPresent, Never. |
--privileged | Run privileged (Docker); Kubernetes alias for --profile=sysadmin. |
--context | Kubernetes kube context name. Prefer this for context names containing slashes. |
-n, --namespace | Kubernetes namespace for pod pickers, targets without an inline namespace, kill, and doctor. |
--profile | Kubernetes security profile: general, baseline, restricted, netadmin, sysadmin. |
--user uid[:gid] | Run the debug container as a specific user. |
Most failures come from image pull policy, cluster security policy, stale Docker volumes, or trying to debug a container that is not running.
debux honors the docker CLI's daemon selection: DOCKER_HOST first, then the active docker context (DOCKER_CONTEXT or docker context use), then the default socket — so colima, Docker Desktop and remote daemons work. If debux cannot see a container that docker ps shows, check your context. SSH-based contexts are not supported directly; forward the socket and set DOCKER_HOST.
docker context ls
docker context use colima
# or pin it for one command
DOCKER_HOST=unix:///path/to/docker.sock debux my-app
openat etc/passwd: path escapes from parentYour cluster runtime rejected NixOS-style absolute symlinks in /etc/passwd or /etc/group. Rebuild and push the latest debug image, then retry with --pull-policy=Always.
mise run image-build
docker push ghcr.io/clement-tourriere/debux:latest
debux k8s://prod/api/app --fresh --pull-policy=Always
/bin/sh: no such file or directory in DockerThis usually means an old Nix store volume was mounted over a rebuilt debug image. Recent debux versions use image-specific volumes. Upgrade and clean old stores if needed.
mise run install
debux store clean
debux docker://my-container --fresh
Your RBAC may not allow pods/ephemeralcontainers, or PodSecurity/admission policy blocks the profile. Try copy mode or a less privileged profile.
debux k8s://prod/api/app --copy
# or
debux k8s://prod/api/app --profile=baseline
Check the debug image name, registry access and imagePullSecrets. For freshly pushed :latest, use --pull-policy=Always.
debux k8s://prod/api/app \
--image ghcr.io/clement-tourriere/debux:latest \
--pull-policy=Always
dctl install permission denied in restricted modeThe pod likely pulled an older debug image whose Nix store is root-only. Rebuild and push the current image, then start a fresh restricted session and force a pull.
mise run image-build
docker push ghcr.io/clement-tourriere/debux:latest
debux k8s://prod/api/app \
--profile=restricted \
--fresh \
--pull-policy=Always
Short answers to common newcomer questions.
No. It starts a separate debug container. In Docker it is a sidecar. In Kubernetes it is an ephemeral container or a temporary copied pod.
Yes. debux copies the container's filesystem — including its writable layer — into a debug container at /target without starting the original. Changes outside mounted volumes are discarded on exit.
It includes Nix and a curated toolbox. Docker reports uncompressed local size. The size buys fast, predictable tools in locked-down containers.
debux shows the target filesystem as-is through /proc/1/root. If files are 777 there, the target image or volume has those permissions.
Yes. Try --profile=restricted for non-root/no-capability sessions, or --profile=baseline for clusters that set defaults via policy. If ephemeral containers are blocked by RBAC, use --copy.
Ephemeral containers die with their pod. Use --copy --keep instead: the copy pod is not owned by your Deployment (rollouts skip it), carries karpenter.sh/do-not-disrupt against consolidation, and self-destructs after --ttl (default 24h) so nothing leaks. Reattach by targeting the copy pod; delete it with debux kill.