Created
March 4, 2026 08:10
-
-
Save nperez/4a15ca70f86f7babfb8ff1aa16efb4c0 to your computer and use it in GitHub Desktop.
Reconstruct a `docker run` command from a running container.
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 | |
| # | |
| # docker-reconstruct.sh — Reconstruct a `docker run` command from a running container. | |
| # Usage: ./docker-reconstruct.sh <container_name_or_id> | |
| # | |
| set -euo pipefail | |
| if [[ $# -lt 1 ]]; then | |
| echo "Usage: $0 <container_name_or_id>" | |
| exit 1 | |
| fi | |
| C="$1" | |
| if ! docker inspect "$C" &>/dev/null; then | |
| echo "Error: Container '$C' not found." | |
| exit 1 | |
| fi | |
| i() { docker inspect --format="$1" "$C"; } | |
| IMAGE=$(i '{{.Config.Image}}') | |
| NAME=$(i '{{.Name}}' | sed 's/^\///') | |
| HOSTNAME=$(i '{{.Config.Hostname}}') | |
| USER=$(i '{{.Config.User}}') | |
| WORKING_DIR=$(i '{{.Config.WorkingDir}}') | |
| RESTART=$(i '{{.HostConfig.RestartPolicy.Name}}') | |
| MAX_RETRY=$(i '{{.HostConfig.RestartPolicy.MaximumRetryCount}}') | |
| NETWORK=$(i '{{.HostConfig.NetworkMode}}') | |
| PID_MODE=$(i '{{.HostConfig.PidMode}}') | |
| PRIVILEGED=$(i '{{.HostConfig.Privileged}}') | |
| READONLY=$(i '{{.HostConfig.ReadonlyRootfs}}') | |
| RUNTIME=$(i '{{.HostConfig.Runtime}}') | |
| INIT=$(i '{{.HostConfig.Init}}') | |
| MEMORY=$(i '{{.HostConfig.Memory}}') | |
| CPUS=$(i '{{.HostConfig.NanoCpus}}') | |
| STDIN_OPEN=$(i '{{.Config.OpenStdin}}') | |
| TTY=$(i '{{.Config.Tty}}') | |
| AUTO_REMOVE=$(i '{{.HostConfig.AutoRemove}}') | |
| ARGS=() | |
| ARGS+=(docker run) | |
| ARGS+=(--name "$NAME" -d) | |
| DEFAULT_HOST=$(i '{{slice .Id 0 12}}') | |
| [[ -n "$HOSTNAME" && "$HOSTNAME" != "$DEFAULT_HOST" ]] && ARGS+=(--hostname "$HOSTNAME") | |
| [[ -n "$USER" ]] && ARGS+=(--user "$USER") | |
| [[ -n "$WORKING_DIR" ]] && ARGS+=(--workdir "$WORKING_DIR") | |
| [[ "$STDIN_OPEN" == "true" ]] && ARGS+=(-i) | |
| [[ "$TTY" == "true" ]] && ARGS+=(-t) | |
| [[ "$AUTO_REMOVE" == "true" ]] && ARGS+=(--rm) | |
| [[ "$PRIVILEGED" == "true" ]] && ARGS+=(--privileged) | |
| [[ "$READONLY" == "true" ]] && ARGS+=(--read-only) | |
| [[ "$INIT" == "true" ]] && ARGS+=(--init) | |
| if [[ -n "$RESTART" && "$RESTART" != "no" ]]; then | |
| if [[ "$RESTART" == "on-failure" && "$MAX_RETRY" -gt 0 ]]; then | |
| ARGS+=(--restart "${RESTART}:${MAX_RETRY}") | |
| else | |
| ARGS+=(--restart "$RESTART") | |
| fi | |
| fi | |
| [[ -n "$NETWORK" && "$NETWORK" != "default" ]] && ARGS+=(--network "$NETWORK") | |
| [[ -n "$PID_MODE" ]] && ARGS+=(--pid "$PID_MODE") | |
| [[ -n "$RUNTIME" && "$RUNTIME" != "runc" ]] && ARGS+=(--runtime "$RUNTIME") | |
| [[ "$MEMORY" -gt 0 ]] && ARGS+=(--memory "$MEMORY") | |
| [[ "$CPUS" -gt 0 ]] && ARGS+=(--cpus "$(awk "BEGIN {printf \"%.2f\", $CPUS / 1000000000}")") | |
| # Port bindings | |
| while IFS= read -r port; do | |
| [[ -z "$port" ]] && continue | |
| ARGS+=(-p "$(echo "$port" | sed 's/^://')") | |
| done <<< "$(docker inspect --format='{{range $cp, $b := .HostConfig.PortBindings}}{{range $b}}{{.HostIp}}:{{.HostPort}}:{{$cp}}{{println}}{{end}}{{end}}' "$C")" | |
| # Volume / bind mounts | |
| while IFS= read -r mount; do | |
| [[ -z "$mount" ]] && continue | |
| TYPE=$(echo "$mount" | cut -d'|' -f1) | |
| SRC=$(echo "$mount" | cut -d'|' -f2) | |
| DST=$(echo "$mount" | cut -d'|' -f3) | |
| MODE=$(echo "$mount" | cut -d'|' -f4) | |
| if [[ "$TYPE" == "tmpfs" ]]; then | |
| ARGS+=(--tmpfs "$DST") | |
| else | |
| M="${SRC}:${DST}" | |
| [[ -n "$MODE" ]] && M+=":${MODE}" | |
| ARGS+=(-v "$M") | |
| fi | |
| done <<< "$(docker inspect --format='{{range .Mounts}}{{.Type}}|{{.Source}}|{{.Destination}}|{{.Mode}}{{println}}{{end}}' "$C")" | |
| # Cap add/drop | |
| while IFS= read -r cap; do [[ -n "$cap" ]] && ARGS+=(--cap-add "$cap"); done <<< "$(i '{{range .HostConfig.CapAdd}}{{.}}{{println}}{{end}}')" | |
| while IFS= read -r cap; do [[ -n "$cap" ]] && ARGS+=(--cap-drop "$cap"); done <<< "$(i '{{range .HostConfig.CapDrop}}{{.}}{{println}}{{end}}')" | |
| # Devices | |
| while IFS= read -r dev; do [[ -n "$dev" ]] && ARGS+=(--device "$dev"); done <<< "$(i '{{range .HostConfig.Devices}}{{.PathOnHost}}:{{.PathInContainer}}:{{.CgroupPermissions}}{{println}}{{end}}')" | |
| # Image | |
| ARGS+=("$IMAGE") | |
| # Command args — only if no custom entrypoint (let the image handle it) | |
| ENTRYPOINT=$(i '{{json .Config.Entrypoint}}') | |
| if [[ "$ENTRYPOINT" == "null" || "$ENTRYPOINT" == "[]" ]]; then | |
| CMD_JSON=$(i '{{json .Config.Cmd}}') | |
| if [[ "$CMD_JSON" != "null" && "$CMD_JSON" != "[]" ]]; then | |
| while IFS= read -r arg; do | |
| ARGS+=("$arg") | |
| done <<< "$(echo "$CMD_JSON" | python3 -c "import sys,json | |
| for a in json.load(sys.stdin): print(a)")" | |
| fi | |
| fi | |
| # Output as a properly shell-quoted string, ready for eval | |
| printf '%q ' "${ARGS[@]}" | |
| echo |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment