Created
August 24, 2026 19:32
-
-
Save x1unix/14d2e192fc9373fcf350b63838909998 to your computer and use it in GitHub Desktop.
docker-debug: kubectl debug replica for Docker
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env bash | |
| # | |
| # A `docker debug` shim for Docker FOSS/CE. | |
| # | |
| # Starts a disposable toolbox container in the target's PID and network | |
| # namespaces, joins its IPC namespace when allowed, and mounts its rootfs at | |
| # /target. Sharing the network namespace also copies the target's hostname; | |
| # Docker CE has no container-scoped --uts mode. | |
| # | |
| # Usage: | |
| # docker-debug <container_name_or_id> | |
| # docker-debug --image busybox <container_name_or_id> | |
| # docker-debug -i alpine <container_name_or_id> | |
| # docker-debug <container_name_or_id> -- sh -c 'ls /target/etc' | |
| # | |
| set -euo pipefail | |
| PROG="$(basename "$0")" | |
| DEFAULT_IMAGE="busybox" | |
| IMAGE="" | |
| TARGET="" | |
| DEFAULT_SHELL="/bin/sh" | |
| # Preserve command argument boundaries and quoting. | |
| CMD_ARGV=() | |
| usage() { | |
| cat <<EOF | |
| Usage: ${PROG} [--image|-i IMAGE] [--shell SHELL] <container_name_or_id> [-- CMD [ARG...]] | |
| Attaches a debug container to a running target container's namespaces, | |
| mimicking 'docker debug' from Docker Desktop. | |
| Options: | |
| -i, --image IMAGE Debug container image to use (default: ${DEFAULT_IMAGE}) | |
| --shell SHELL Shell to run inside the debug container (default: ${DEFAULT_SHELL}) | |
| -h, --help Show this help | |
| Everything after '--' is run inside the debug container instead of the shell, | |
| with quoting preserved. Use it for non-interactive one-liners. | |
| Examples: | |
| ${PROG} my_container | |
| ${PROG} 3a1f9c2b1e4d | |
| ${PROG} --image busybox my_container | |
| ${PROG} -i alpine my_container | |
| ${PROG} --shell /bin/bash -i alpine my_container | |
| ${PROG} my_container -- ls -la /target/etc/alloy | |
| ${PROG} my_container -- sh -c 'cat /target/etc/passwd | wc -l' | |
| EOF | |
| } | |
| # Parse arguments. | |
| while [[ $# -gt 0 ]]; do | |
| case "$1" in | |
| -i|--image) | |
| [[ $# -ge 2 ]] || { echo "${PROG}: --image requires a value" >&2; exit 1; } | |
| IMAGE="$2" | |
| shift 2 | |
| ;; | |
| --image=*) | |
| IMAGE="${1#*=}" | |
| shift | |
| ;; | |
| --shell) | |
| [[ $# -ge 2 ]] || { echo "${PROG}: --shell requires a value" >&2; exit 1; } | |
| CMD_ARGV=("$2") | |
| shift 2 | |
| ;; | |
| --shell=*) | |
| CMD_ARGV=("${1#*=}") | |
| shift | |
| ;; | |
| -h|--help) | |
| usage | |
| exit 0 | |
| ;; | |
| --) | |
| # Pass everything after -- through unchanged. | |
| shift | |
| CMD_ARGV=("$@") | |
| break | |
| ;; | |
| -*) | |
| echo "${PROG}: unknown option '$1'" >&2 | |
| usage >&2 | |
| exit 1 | |
| ;; | |
| *) | |
| if [[ -z "${TARGET}" ]]; then | |
| TARGET="$1" | |
| else | |
| echo "${PROG}: unexpected extra argument '$1'" >&2 | |
| usage >&2 | |
| exit 1 | |
| fi | |
| shift | |
| ;; | |
| esac | |
| done | |
| if [[ -z "${TARGET}" ]]; then | |
| echo "${PROG}: missing required <container_name_or_id> argument" >&2 | |
| usage >&2 | |
| exit 1 | |
| fi | |
| IMAGE="${IMAGE:-${DEFAULT_IMAGE}}" | |
| [[ ${#CMD_ARGV[@]} -gt 0 ]] || CMD_ARGV=("${DEFAULT_SHELL}") | |
| # Validate the target. | |
| command -v docker >/dev/null 2>&1 || { | |
| echo "${PROG}: docker is not installed or not on PATH" >&2 | |
| exit 1 | |
| } | |
| if ! docker inspect "${TARGET}" >/dev/null 2>&1; then | |
| echo "${PROG}: no such container '${TARGET}'" >&2 | |
| exit 1 | |
| fi | |
| RUNNING="$(docker inspect -f '{{.State.Running}}' "${TARGET}")" | |
| if [[ "${RUNNING}" != "true" ]]; then | |
| echo "${PROG}: container '${TARGET}' is not running" >&2 | |
| exit 1 | |
| fi | |
| CID="$(docker inspect -f '{{.Id}}' "${TARGET}")" | |
| PID="$(docker inspect -f '{{.State.Pid}}' "${TARGET}")" | |
| if [[ -z "${PID}" || "${PID}" == "0" ]]; then | |
| echo "${PROG}: could not determine PID for container '${TARGET}'" >&2 | |
| exit 1 | |
| fi | |
| # Prefer the storage driver's merged directory. /proc/<pid>/root is a fallback | |
| # because recursive runc binds of that symlink can fail with EINVAL. | |
| # Do not test either path locally: the caller may lack access, while dockerd can | |
| # resolve and mount it as root. | |
| MERGED="$(docker inspect -f '{{index .GraphDriver.Data "MergedDir"}}' "${TARGET}" 2>/dev/null || true)" | |
| if [[ -n "${MERGED}" && "${MERGED}" != "<no value>" ]]; then | |
| ROOTFS="${MERGED}" | |
| else | |
| ROOTFS="/proc/${PID}/root" | |
| echo "${PROG}: warning: storage driver exposes no MergedDir, falling back to ${ROOTFS}" >&2 | |
| fi | |
| # Join IPC only when the target's mode permits it; Docker's default private | |
| # mode is not shareable and would make `docker run` fail. | |
| IPC_MODE="$(docker inspect -f '{{.HostConfig.IpcMode}}' "${TARGET}")" | |
| IPC_ARGS=() | |
| case "${IPC_MODE}" in | |
| shareable|host|container:*) | |
| IPC_ARGS=(--ipc="container:${CID}") | |
| ;; | |
| *) | |
| echo "${PROG}: note: target IPC mode is '${IPC_MODE:-private}', not joining its IPC namespace" >&2 | |
| echo "${PROG}: (restart the target with --ipc=shareable to share it)" >&2 | |
| ;; | |
| esac | |
| # Pull explicitly to show first-run progress. | |
| if ! docker image inspect "${IMAGE}" >/dev/null 2>&1; then | |
| echo "${PROG}: pulling debug image '${IMAGE}'..." >&2 | |
| docker pull "${IMAGE}" >&2 | |
| fi | |
| echo "${PROG}: attaching '${IMAGE}' debug shell to container '${TARGET}' (${CID:0:12}, pid ${PID})" >&2 | |
| echo "${PROG}: target rootfs mounted at /target" >&2 | |
| # Avoid -t for pipelines and other non-TTY callers. | |
| TTY_ARGS=(-i) | |
| if [[ -t 0 && -t 1 ]]; then | |
| TTY_ARGS=(-i -t) | |
| fi | |
| exec docker run "${TTY_ARGS[@]}" --rm \ | |
| --pid="container:${CID}" \ | |
| --network="container:${CID}" \ | |
| "${IPC_ARGS[@]+"${IPC_ARGS[@]}"}" \ | |
| --volume "${ROOTFS}:/target" \ | |
| --label "docker-debug.target=${CID}" \ | |
| "${IMAGE}" "${CMD_ARGV[@]}" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment