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.-ikeeps stdin open;-tallocates a TTY. Both are needed for interactive shells.- Most Linux base images have
/bin/sh. Some (Ubuntu, Debian) also have/bin/bash. Distroless andFROM scratchhave 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 execis for running things in a container that is already running. To get a shell when starting a container, usedocker run -it <image> shinstead.
Quick example
# 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 shThe -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 base | Shell 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 scratch | None — 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
# 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 shCommon mistakes
Forgetting -i or -t
# 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
$ docker exec -it nginx-alpine bash
OCI runtime exec failed: ... "bash": executable file not found in $PATHAlpine ships with busybox sh, not bash. Use sh instead.
Trying to exec into a FROM scratch image
$ docker exec -it tiny-go-app sh
OCI runtime exec failed: ... "sh": executable file not foundThe 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
# 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 shdocker 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 shand poke around. - Running migrations against a DB container:
docker exec -it db psql -U postgres -f /tmp/migrate.sql(ordocker 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
$ docker exec -it db sh
/ # whoami
postgres
/ # psql
psql (16.4)
Type "help" for help.
postgres=# \l
# database list
postgres=# \q
/ # exitExec gives you the shell; from there it is just a normal shell session.
Run a one-off backup without an interactive shell
$ 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
$ 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 redisCompose'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 readyA concise answer to help you respond confidently on this topic during an interview.
Comments
No comments yet