docker exec runs a command inside a running container. It’s how you open a shell to look around, run a database client, check a config file or trigger a maintenance task — without restarting anything.
Open a shell
docker exec -it myapp bash
-ikeeps STDIN open (interactive).-tallocates a terminal (so you get a prompt, colours and line editing).
Many images — Alpine-based ones especially — don’t include bash. Use sh:
docker exec -it myapp sh
If you’re not sure which exists, try bash and fall back:
docker exec -it myapp sh -c 'command -v bash >/dev/null && exec bash || exec sh'
Type exit (or press Ctrl+D) to leave. The container keeps running — exec’d processes are separate from the container’s main process.
Run a single command
No -it needed when you just want output:
docker exec myapp ls -la /app
docker exec myapp cat /etc/os-release
docker exec nginx nginx -t # test Nginx config
docker exec nginx nginx -s reload # reload it
docker exec myapp env # environment variables
Commands with pipes, redirects or && must run inside a shell in the container:
docker exec myapp sh -c 'ps aux | grep node'
docker exec myapp sh -c 'echo "debug=true" >> /app/config.ini'
Without sh -c, the pipe would run on the host.
Databases
docker exec -it postgres psql -U postgres
docker exec -it mariadb mariadb -u root -p
docker exec -it redis redis-cli
docker exec -it mongo mongosh
Dump a database straight to a file on the host:
docker exec postgres pg_dump -U postgres appdb > appdb.sql
docker exec mariadb sh -c 'mariadb-dump -u root -p"$MARIADB_ROOT_PASSWORD" appdb' > appdb.sql
Restore by piping in (-i but not -t):
docker exec -i postgres psql -U postgres appdb < appdb.sql
Using -t with redirected input gives “the input device is not a TTY”.
As another user
Containers often run as an unprivileged user; exec uses the same user by default. To act as root (for installing a debugging tool, say):
docker exec -u root -it myapp sh
docker exec -u 1000 myapp id
docker exec -u www-data wordpress wp plugin list # WP-CLI as the web user
Working directory and environment
docker exec -w /app myapp ls
docker exec -e DEBUG=1 myapp node scripts/check.js
Docker Compose
Use the service name rather than the container name:
docker compose exec app sh
docker compose exec db psql -U app
docker compose exec -T db pg_dump -U app app > backup.sql # -T disables the TTY
docker compose run --rm app <command> is different: it starts a new container for the service, which is right for one-off tasks like migrations when the service isn’t running.
When there’s no shell at all
Distroless and scratch images contain only the application — no shell, no ls. Options:
- docker debug (Docker Desktop and recent Docker versions with a subscription) attaches a toolbox shell.
- Run a tools container in the same namespaces:
docker run --rm -it --pid container:myapp --network container:myapp nicolaka/netshoot
You can now see the app’s processes and network from a container that has tools.
- Copy files out for inspection:
docker cp myapp:/app/config.json .
Container not running?
docker exec only works on running containers:
Error response from daemon: container … is not running
Look at why it stopped with docker logs myapp and docker ps -a. To explore the image instead, start a new container with a shell as the entrypoint:
docker run --rm -it --entrypoint sh myimage:tag
Don’t treat exec as configuration management
Changes made inside a container with exec — installed packages, edited files — are lost when the container is recreated (for example on the next image update). Use exec for inspection and one-off tasks; make permanent changes in the Dockerfile, environment variables, or mounted volumes.
Troubleshooting
OCI runtime exec failed: exec: "bash": executable file not found— no bash in the image; usesh.the input device is not a TTY— you used-tin a script or with redirected input; drop the-t.permission deniedinside — you’re running as the image’s non-root user; add-u rootif appropriate.
More commands in the Docker commands cheat sheet, and for logs see Docker logs.