Skip to main content

How to get inside a running Docker container?

Getting a shell inside a running container is the bread-and-butter debugging move. The command is docker exec, the magic flags are -it, and the gotcha is that not every image has a shell.

Theory

TL;DR

  • docker exec -it <name> <cmd> runs <cmd> as a NEW process inside the container, not in addition to PID 1.
  • -i keeps stdin open; -t allocates a TTY. Both are needed for interactive shells.
  • Most Linux base images have /bin/sh. Some (Ubuntu, Debian) also have /bin/bash. Distroless and FROM scratch have neither.
  • The exec'd process inherits the container's namespaces (it sees the same filesystem, network, processes) but is parented by the daemon, not by your app's PID 1.
  • docker exec is for running things in a container that is already running. To get a shell when starting a container, use docker run -it <image> sh instead.

Quick example

bash
# Most common: shell into a running container $ docker exec -it web sh / # ls / bin dev etc home lib proc root run sbin sys tmp usr var / # exit # Run a one-off command and exit $ docker exec web ls /etc/nginx # Returns the listing without dropping into a shell. # As root, in a specific dir, with an env var $ docker exec -it -u root -w /etc/nginx -e DEBUG=1 web sh

The -it combo is so common it gets memorized as one flag. Without it: -i only = stdin works but no TTY-aware tools (like vi, less); -t only = TTY but you cannot type back; neither = the shell exits immediately.

Picking the right shell

Image baseShell available
alpine, anything Alpine-based/bin/sh (busybox ash)
debian, ubuntu, node, python/bin/sh and /bin/bash
nginx:1.27-alpine etc./bin/sh
distroless (gcr.io/distroless/...)None — exec is impossible
FROM scratchNone — only your binary
distroless:debug variants/busybox/sh

When in doubt, try sh first. If that fails, try bash. If both fail, the image probably has no shell.

Common flags worth knowing

bash
# Force user (default: image's USER, often root) docker exec -u 1000:1000 web id # Set working directory docker exec -w /app web ls # Pass an environment variable for this exec only docker exec -e DEBUG=1 web env | grep DEBUG # Run detached (background, do not wait for exit) docker exec -d web touch /tmp/done # Pass --privileged to lift capability restrictions (for debugging) docker exec --privileged -it web sh

Common mistakes

Forgetting -i or -t

bash
# WRONG: shell exits immediately because there is no TTY $ docker exec web sh $

With no TTY, sh reads from a closed stdin and dies. Always -it for interactive.

Trying bash on Alpine

bash
$ docker exec -it nginx-alpine bash OCI runtime exec failed: ... "bash": executable file not found in $PATH

Alpine ships with busybox sh, not bash. Use sh instead.

Trying to exec into a FROM scratch image

bash
$ docker exec -it tiny-go-app sh OCI runtime exec failed: ... "sh": executable file not found

The image has no shell. To debug, either rebuild with a shell layer, or run docker run --entrypoint /bin/sh -it <image> (which still fails if no shell is in the image), or build a separate *-debug variant. The standard pattern: distroless :debug tags include busybox; use them for debugging only.

Confusing docker exec with docker attach

bash
# attach: connects to PID 1's stdio. Killing the shell may stop the container. $ docker attach web # exec: spawns a NEW process. Killing it leaves the container running. $ docker exec -it web sh

docker exec is what you usually want. attach is for cases where you need to see PID 1's stdout/stderr live (sometimes for interactive REPLs as PID 1).

Real-world usage

  • Debugging a misbehaving service: docker exec -it web sh and poke around.
  • Running migrations against a DB container: docker exec -it db psql -U postgres -f /tmp/migrate.sql (or docker compose exec).
  • Triggering a one-off task: docker exec api npm run cache:flush.
  • Checking config inside a running container: docker exec web cat /etc/nginx/nginx.conf.

Follow-up questions

Q: Why does my exec'd shell die when the container restarts?


A: The container's restart kills all processes inside, including yours. Exec sessions are not preserved across container restarts.

Q: Can I exec into a stopped container?


A: No. docker exec requires a running container. For a stopped one, you can docker start -ai <name> to revive it with stdio attached, or commit it to a new image and run that with a shell as the entrypoint.

Q: What is the difference between docker exec and docker compose exec?


A: docker compose exec <service> is the Compose-aware wrapper — it knows the service name, project context, and connects to the right container automatically. Underneath, it calls docker exec. Same flags work.

Q: How do I run a shell as a different user?


A: docker exec -u root -it web sh (or any UID/username). Useful when the container's default USER is non-root and you need to install/inspect something privileged.

Q: (Senior) When should you exec vs. just rebuild the image?


A: Exec for diagnosing — check files, env, network state in the live state. Rebuild for fixes — anything you change inside via exec is lost on next restart. The temptation to exec-fix-and-pretend-it-is-good is a real anti-pattern; always reflect the fix in the Dockerfile or config.

Examples

Walking around inside a Postgres container

bash
$ docker exec -it db sh / # whoami postgres / # psql psql (16.4) Type "help" for help. postgres=# \l # database list postgres=# \q / # exit

Exec gives you the shell; from there it is just a normal shell session.

Run a one-off backup without an interactive shell

bash
$ docker exec db pg_dump -U postgres app > backup.sql # Backup file lands on your host. Container keeps running.

No -it needed because there is no interaction; pg_dump writes to stdout, the host shell redirects to file.

Compose: exec into a service by name

bash
$ docker compose exec api sh /app # cat /etc/hosts # /etc/hosts auto-generated by Compose with all sibling service names 127.0.0.1 localhost 172.18.0.3 api 172.18.0.2 db 172.18.0.4 redis

Compose's exec is the one you will reach for daily during dev — it knows your project's services without typing container IDs.

Short Answer

Interview ready
Premium

A concise answer to help you respond confidently on this topic during an interview.

Comments

No comments yet