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 listleft / right or tab cycle sources; d, k, y jump to Docker, Kubernetes and History1-3 still work as optional source shortcutsenter drill into source/context/namespace lists or open a selected targetb go back in Kubernetes navigations 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 and refuses unverifiable release assets without checksums.txt.
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
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. If there are no commits since the latest version tag, or if the commits are not release-eligible conventional commits such as ci:/docs:, the bump task exits successfully and says no bump is needed. If Commitizen already bumped the version but tag creation was interrupted, the task recreates the missing vX.Y.Z tag without GPG signing. Pushing a v* tag runs the Release workflow, publishes and keyless-signs the GHCR debug image, then creates the GitHub Release with checksummed CLI archives. Release binaries pin their default debug image to the matching versioned image tag; dev builds use latest. The same workflow can also be run manually for an existing pushed tag.
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 a command is not available in the debug image, debux can either run the target binary via chroot wrappers or offer to install the tool with Nix.
Use debux locally exactly where docker exec stops being useful: distroless images, missing tools, or debugging without rebuilding.
# 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
# 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
# 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
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.
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
# Standalone toolbox pod
debux pod -n prod
# Keep standalone pod after exit
debux pod -n prod --keep
--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 | Creates a temporary copied pod, enables process namespace sharing, then deletes it on exit. |
debux pod | Cluster network troubleshooting | Standalone debug pod in the current kube-context namespace by default, optionally with host networking. |
Prefixes make the runtime explicit. A bare target defaults to Docker.
| Format | Runtime | Meaning |
|---|---|---|
my-container | Docker | Debug Docker container by name or ID. |
docker:// | Docker | Open the Docker container picker. |
docker://my-container | Docker | Debug a specific Docker container. |
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://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. |
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
--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.
# Run a one-shot command through the debug toolbox
debux docker://my-app -- curl -I localhost
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
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.
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 kill [target] | Stop a running debux debug session. |
debux completion | Generate shell completions for bash, zsh, fish, or PowerShell. |
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. |
--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. |
--pull-policy | Debug image pull policy for Docker/Kubernetes: Always, IfNotPresent, Never. |
--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.
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.
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.