Docker Exec – How to Run a Command Inside a Docker Image or Container
Docker has revolutionized how we build, package, and deploy applications. By packaging an app and its dependencies into an isolated container, Docker makes it easy to run that app consistently across different environments. But sometimes you need to peek inside that container and execute commands directly. That‘s where docker exec
comes in.
In this article, we‘ll take an in-depth look at docker exec
– what it is, how it works, when to use it, and best practices to keep in mind. Whether you‘re a developer troubleshooting an issue or a DevOps engineer performing maintenance on production containers, understanding docker exec
is key to effectively working with Docker.
What is docker exec?
Simply put, docker exec
is a command that allows you to run a new command inside an already running Docker container. It‘s like SSH‘ing into a remote server to run commands, except in this case the remote server is a Docker container running locally or in the cloud.
The basic syntax looks like this:
docker exec [OPTIONS] CONTAINER COMMAND [ARG...]
For example, to run the ls
command inside a container named my-app
, you would do:
docker exec my-app ls
This would list the files in the default working directory inside the my-app
container.
Some common options you can pass to docker exec
include:
-i
or--interactive
– Keep STDIN open even if not attached. This allows you to provide input to the command.-t
or--tty
– Allocate a pseudo-TTY. This essentially gives you an interactive terminal session inside the container.-w
or--workdir
– Set the working directory for the command. By default it uses the container‘s default working directory.-e
or--env
– Set environment variables for the command.-u
or--user
– Run the command as a particular user.
So a more involved example might look like:
docker exec -it -w /app -e DEBUG=true my-app bash
This would give you an interactive bash shell session inside the my-app
container, starting in the /app
directory, with the DEBUG
environment variable set to "true"
.
How to use docker exec
Using docker exec
is fairly straightforward once you understand the basic syntax. Here‘s a step-by-step example.
Let‘s say we have a Docker container running a Node.js app. We can get the ID or name of the container using docker ps
:
$ docker ps
CONTAINER ID IMAGE COMMAND NAMES
bcd753e2a933 node:14 "docker-entrypoint.sā¦" my-app
To start an interactive bash session inside this container, we can run:
docker exec -it my-app bash
This will drop us into a bash shell inside the running container:
root@bcd753e2a933:/app#
From here, we can run any commands we want. For example, to see the Node.js version:
root@bcd753e2a933:/app# node -v
v14.17.0
Or to see the contents of the /app
directory:
root@bcd753e2a933:/app# ls
Dockerfile index.js node_modules package.json
When we‘re done, we can exit the shell with the exit
command:
root@bcd753e2a933:/app# exit
exit
$
And we‘re back to our host machine‘s shell.
We can also run commands directly without starting an interactive shell. For example, to see the contents of a file:
docker exec my-app cat /app/package.json
This would output the contents of the package.json
file inside the container.
Common use cases for docker exec
So when would you actually want to use docker exec
? Here are some common scenarios.
Debugging and troubleshooting
Perhaps the most common use case is debugging and troubleshooting a misbehaving container. If your app is crashing or not working as expected, docker exec
allows you to get inside the container and poke around.
You can inspect log files, check environment variables, run diagnostic commands, and generally try to figure out what‘s going wrong. Interactive shell access via docker exec -it
is especially useful for exploratory debugging.
Inspecting the container filesystem
docker exec
also allows you to inspect the filesystem of a running container. You can list files, view file contents, check permissions, and so on.
This can be useful for checking configuration files, verifying that expected files are present, or investigating disk space issues.
Running maintenance tasks
Sometimes you may need to run one-off maintenance tasks in a production container. For example, you might need to run a database migration, clear a cache, or update a dependency.
docker exec
allows you to run these tasks without having to rebuild the container image or start a new container. Just exec in, run the task, and you‘re done.
Automating tests and checks
In a CI/CD pipeline or other automated workflow, you may want to run tests or checks against a freshly built container before deploying it. docker exec
allows you to run these automation steps.
For example, you could build a container, start it, exec in to run unit tests, and only proceed with deployment if the tests pass. All of this can be automated in a script or pipeline.
Exec vs other approaches
docker exec
is not the only way to run commands in a container. Let‘s compare it to some other common approaches.
docker run
docker run
is used to start a new container. You can pass a command to docker run
which will be executed inside the container when it starts.
For example:
docker run my-app node -v
This would start a new container from the my-app
image and run the command node -v
inside it.
The key difference from docker exec
is that docker run
starts a new container each time, whereas docker exec
runs a command inside an existing container.
docker attach
docker attach
is used to attach your terminal‘s standard input, output, and error streams to a running container. This allows you to view its ongoing output and also send input to it.
For example:
docker attach my-app
This would attach to the my-app
container. Any output from the container would be displayed in your terminal, and any input you type would be sent to the container.
The difference from docker exec
is that attach
doesn‘t run a new command, it just attaches to the container‘s existing primary process.
nsenter
nsenter
is a Linux command that allows you to enter any namespace. In the context of Docker, you can use it to enter a container‘s namespaces, giving you a shell inside the container.
For example:
nsenter --target $(docker inspect --format {{.State.Pid}} my-app) --mount --uts --ipc --net --pid
This would enter the namespaces of the my-app
container and give you a shell.
The advantage of nsenter
over docker exec
is that it works on any Linux system, not just those with Docker installed. However, it‘s more complex to use and requires privileged access to the host system.
Best practices and gotchas
While docker exec
is a powerful tool, there are some things to keep in mind when using it.
Be careful with privileged access
When you docker exec
into a container, you are running as the root user inside that container by default. This means you have full privileged access to the container‘s filesystem and processes.
While this is often necessary for debugging or maintenance tasks, it also means you can easily make accidental and destructive changes. Always double-check your commands before running them, and consider using a non-root user where possible.
Avoid unintended changes
When using docker exec
for debugging or exploration, it‘s easy to accidentally make changes to the container that you didn‘t intend. For example, creating or modifying files, installing packages, or changing configurations.
If you want to ensure your exec session doesn‘t make any permanent changes, you can use the --read-only
flag:
docker exec --read-only my-app bash
This starts a bash session but with a read-only filesystem, preventing any changes.
Consider if exec is the right approach
While docker exec
is useful in many situations, it‘s not always the best approach. For tasks that modify the container state (e.g. updating a package), it‘s often better to build a new image with the changes and start a new container from that image.
For long-running or complex tasks, it‘s often better to use docker run
to start a new container specifically for that task. And for viewing logs or output, consider using docker logs
or docker attach
instead.
Limitations of docker exec
It‘s also important to understand what docker exec
can‘t do.
Firstly, docker exec
can only be used on running containers. If a container has exited or hasn‘t started yet, docker exec
will fail.
Secondly, docker exec
is for running one-off commands, not for starting long-running services or daemons. If you need to start a new background process, it‘s better to use docker run
.
Lastly, because docker exec
runs commands inside the existing container environment, it may not be suitable for tasks that require a clean or isolated environment. In these cases, starting a new container with docker run
is a better choice.
Conclusion
docker exec
is a powerful tool that allows you to run commands inside a running Docker container. It‘s useful for debugging, maintenance, inspection, and automation.
To use docker exec
, simply specify the container name or ID and the command you want to run. You can use options like -it
for an interactive session, -w
to set the working directory, and -e
to set environment variables.
However, it‘s important to use docker exec
responsibly. Be careful with privileged access, avoid unintended changes, and consider if it‘s the right approach for your task. Also keep in mind the limitations – docker exec
can‘t start containers, run long-running processes, or provide a clean environment.
By understanding how and when to use docker exec
, you can more effectively manage and interact with your Docker containers.