How to Access a Docker Container: Shell, Exec, Attach and SSH
"Log in to a container", "SSH into a container" and "get bash in a container" all mean the same thing in practice: start a process inside the container's namespaces and talk to it. Docker does that with docker exec. No SSH server is involved.
Find the container
docker ps
Use the name from the NAMES column or the container ID. A unique prefix of the ID is enough, for example docker exec -it 3f2a sh.
Open a shell with docker exec
docker exec -it my-app bash
-i keeps stdin open and -t allocates a terminal. Without both you get no prompt or a broken one. Typing exit ends only your shell. The container keeps running, because its main process is untouched.
Many images (Alpine, slim and distroless images) do not ship bash. Fall back to sh:
docker exec -it my-app sh
Useful options:
docker exec -it -u root my-app sh # as root, even if the image runs as another user
docker exec -it -w /app my-app sh # start in a specific directory
docker exec -it -e DEBUG=1 my-app sh # extra environment variable for this session
For a single command you do not need a shell at all:
docker exec my-app cat /etc/os-release
docker exec my-app env
docker exec my-app ls -la /app/config
docker exec works only on running containers. If the container keeps crashing, start a new container from the same image with a shell instead of its normal command:
docker run -it --rm --entrypoint sh my-app:1.0
To open a shell in a fresh container from any image:
docker run -it --rm ubuntu:24.04 bash
--rm removes the container when you exit.
docker attach is different
docker attach my-app connects your terminal to the container's main process (PID 1), not to a new shell. You see its output and your keystrokes go to its stdin. That is useful for an interactive program started as the main process, and confusing for a web server that never reads stdin.
Be careful with Ctrl+C: it is sent to the main process and usually stops the container. If the container was started with -it, detach with Ctrl+P followed by Ctrl+Q and leave it running. For a long-running service, docker logs -f my-app is usually what you wanted from attach.
Containers without a shell
Distroless and scratch-based images contain the application and little else. docker exec my-app sh fails with "executable file not found". Options:
Run a debug container in the same namespaces. It brings the tools, the target stays untouched:
docker run -it --rm \
--pid container:my-app \
--network container:my-app \
--cap-add SYS_PTRACE \
nicolaka/netshoot
Inside, ps aux shows the app's processes, ss -tlnp shows its sockets and curl localhost:8080 hits the app directly. The app's filesystem is visible under /proc/<pid>/root/, where <pid> comes from ps.
Use nsenter from the host (Linux). It runs host binaries inside the container's namespaces:
PID=$(docker inspect -f '{{.State.Pid}}' my-app)
sudo nsenter -t "$PID" -n ss -tlnp # network namespace only
sudo nsenter -t "$PID" -m -u -i -n -p sh # all namespaces, needs sh inside the image
Use docker debug on Docker Desktop. docker debug my-app opens a shell with a toolbox in any container or image, even without a shell. It ships with Docker Desktop and has been free for all users since Docker Desktop 4.50.
Why not run SSH inside the container
Running sshd in a container means a second long-running process, host keys and credentials baked into or mounted into the image, and an open port to protect. Everything SSH would give you is already available through docker exec, and access to it is controlled by who can reach the Docker daemon.
What people usually need is access to a container on another machine. Do it in two steps, or let the Docker CLI do the SSH part:
# SSH to the host, then exec
ssh user@203.0.113.10
docker exec -it my-app sh
# Or point the local CLI at the remote daemon over SSH
docker context create my-server --docker "host=ssh://user@203.0.113.10"
docker --context my-server exec -it my-app sh
On AWS, reach the host with EC2 Instance Connect or SSM Session Manager instead of an open SSH port, then use docker exec as usual. For ECS tasks with ECS Exec enabled:
aws ecs execute-command --cluster my-cluster --task <task-id> \
--container my-app --interactive --command "/bin/sh"
In Kubernetes the equivalents are kubectl exec -it deploy/my-app -- sh and, for images without a shell, kubectl debug -it <pod> --image=busybox --target=my-app.
Reaching the container's service from the host
Getting a shell and reaching the app are separate things. To call a service in a container from the host, publish its port when you start it:
docker run -d --name my-app -p 8080:80 nginx:stable
curl http://localhost:8080
On Linux you can also reach the container IP directly, without publishing:
docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' my-app
That does not work from a macOS or Windows host with Docker Desktop, because containers run inside a VM. Use published ports there.
Security notes
- Anyone who can run
docker execcan run anything as root in any container, and through bind mounts on the host. Access to the Docker socket is root access. Grant it accordingly. - Prefer
docker execwithout-u rootwhen the image runs as a non-root user. Switch to root only when you must. - Changes made inside a running container are lost when it is recreated. Treat a shell session as a debugging tool and put the actual fix into the image or its configuration.
Quick reference
docker ps # find the container
docker exec -it my-app bash # shell (or sh)
docker exec -it -u root my-app sh # shell as root
docker exec my-app <command> # one command, no shell
docker run -it --rm --entrypoint sh my-app:1.0 # shell in a new container from the image
docker attach my-app # main process; detach with Ctrl+P Ctrl+Q
docker logs -f my-app # follow output instead of attaching
docker --context my-server exec -it my-app sh # remote host over SSH
