Connecting to Docker Containers
How to connect to a running Docker container and get a shell inside it with docker exec, when docker attach fits better, and what the usual errors mean.
I wrote the first version of this post in August 2014, when the usual way into a running container was a third-party tool called nsenter. I’d been installing SSH on most of the containers I was deploying, which I knew was wrong, and I wanted to get in and have a look around without exposing a port or setting up users.
That October Docker 1.3 added docker exec (I wrote about installing it at the time), and nsenter stopped being necessary. The post has carried on turning up in searches for things like “docker enter container” ever since, so I’ve rewritten it to describe what you should be typing today. The old method is still at the bottom, for the sake of history.
The short answer
If you just want to connect to a running container and get a shell inside it:
docker exec -it my-container bashSwap my-container for the name or ID of your container (docker ps will tell you), and swap bash for sh if the image is Alpine or otherwise minimal. Type exit to leave. The container carries on running as though you’d never been there.
Trying it on a test container
Start something to poke around in. The official nginx image will do:
docker run -d --name web -p 8080:80 nginxdocker psNow open a shell inside it:
docker exec -it web bashYou’ll land at a root prompt where, by default, the hostname is the container’s ID. That’s a quick way to confirm you’re inside the container rather than on the host.
The two flags do different jobs. -i keeps standard input open so you can type, and -t allocates a pseudo-terminal so you get a proper prompt and line editing. You want both for an interactive shell. Drop -t when you’re piping data in, and drop both when you’re only running a command.
Running a single command
You don’t have to open a shell at all. Leave off -it and put the command straight after the container name:
docker exec web nginx -vdocker exec web ls -lha /usr/share/nginx/htmldocker exec web cat /etc/nginx/conf.d/default.confThis is what docker-enter testing ls -lha /var/lib/mysql/ did in the original post, minus the extra tool.
docker exec starts a new process inside the container’s existing namespaces and leaves the main process (PID 1) alone. That means it only works while the container is running, and anything you change from inside lives in the container’s writable layer, so it disappears when the container is removed.
Useful flags
A handful of flags come up often:
| Flag | What it does | Example |
|---|---|---|
-u / --user | Run as a different user | docker exec -u root -it web bash |
-w / --workdir | Start in a specific directory | docker exec -w /etc/nginx -it web bash |
-e / --env | Set an environment variable for that command | docker exec -e DEBUG=1 web env |
-d / --detach | Run the command in the background | docker exec -d web touch /tmp/hello |
-u root matters more than it looks. Plenty of images switch to an unprivileged user at the end of the Dockerfile, which is sensible until you need to install a debugging tool inside one.
sh or bash?
A lot of images don’t ship bash. Alpine-based images have sh (BusyBox’s ash), and distroless or scratch-based images usually have neither. If you try bash on an image without it, you’ll get this:
OCI runtime exec failed: exec failed: unable to start container process: exec: "bash": executable file not found in $PATH: unknownUse sh instead. If that fails too, the image has no shell at all and docker exec can’t conjure one. docker logs and docker cp still work, and for anything more you’ll need to rebuild the image with a shell in it.
With Docker Compose
If the container is part of a Compose project, use the service name from your compose file rather than the container name:
docker compose exec web shCompose allocates a terminal by default, so you don’t need -it. If you’re running the command from a script or a CI job, where there is no terminal, add -T to turn that off.
docker attach is not the same thing
docker attach connects your terminal to the container’s main process rather than starting a new one. If that process is a shell, you can type into it. If it’s a web server, you’ll watch its output scroll past and not much else.
The catch is that Ctrl+C is aimed at that main process, and for many servers that stops the container. To leave without stopping it, press Ctrl+P followed by Ctrl+Q. Use docker exec for looking around, and docker attach only when you specifically want the main process’s own input and output.
Podman and Kubernetes
Podman accepts the same syntax, so podman exec -it web bash does what you’d expect (I covered getting it running in Running Podman on macOS). In Kubernetes the equivalent is kubectl exec -it my-pod -- sh, with -c to pick a container when the pod has more than one.
When it goes wrong
Most failures come down to one of four messages:
| Message | What it means | What to do |
|---|---|---|
No such container: web | The name or ID is wrong | Check docker ps -a for the exact name |
... is not running | The container has stopped or crashed | Read docker logs web, then start it again |
the input device is not a TTY | You used -t from a script, cron or CI job | Drop -t, or use -T with Compose |
"bash": executable file not found in $PATH | The image has no bash | Use sh |
If the container keeps exiting before you can get into it, override the entrypoint and start a fresh one from the same image instead:
docker run --rm -it --entrypoint sh nginxThat gives you the image’s contents to look at, though not the state of the container that crashed.
Why not just run SSH?
It’s what I was doing in 2014, and I’d still talk you out of it. An SSH daemon inside a container is an extra process to supervise and an extra port to expose, all to do something the Docker CLI already does over the Docker socket.
What nsenter and docker-enter were
For completeness, here is what this post used to be about. Before docker exec existed, nsenter started a process inside a container’s Linux namespaces, and docker-enter was a small wrapper that looked up the container’s PID for you. Running docker-enter testing dropped you into a shell.
docker exec does the same job and ships with Docker, which is why I stopped using both. nsenter itself lives on as part of util-linux, if you ever need to enter a container’s namespaces from the host without going through Docker at all.
Summing up
Twelve years on, getting into a container takes one line, which is a big improvement on pulling a binary out of an image and copying it into /usr/local/bin by hand. If you’re new to Docker and want the wider tour, that’s the ground I cover in Mastering Docker, Fourth Edition.
Related terms: Agent SkillsAI Coding AssistantApplication Programming InterfaceClaude CodeCodexCommand-Line Interface
Keep reading
Comments


