

Sarthak Varshney is a Docker Captain, 5x C# Corner MVP, and 2x Alibaba Cloud MVP, with over six years of hands-on experience in the IT industry, specializing in cloud computing, DevOps, and modern application infrastructure. He is an Author and Associate Consultant, known for working extensively with cloud platforms and container-based technologies in real-world environments.
So you've got a Docker container running. Maybe it's an Nginx server, maybe a Node app, maybe just an Ubuntu box you spun up for testing. And now you're wondering — how do I actually get inside this thing?
That's exactly what docker exec is for. And by the end of this article, you'll not only know how to use it, you'll understand why it works the way it does.
Let's get into it.
Before we even talk about the command, let's get one thing straight. A container is not a virtual machine. It's not like spinning up a separate computer that lives somewhere on your hard drive.
Think of it more like a really isolated process. When you run a container, Docker starts a process (or a group of processes) on your actual machine, but it wraps them in their own little bubble — their own filesystem, their own network, their own process tree. From inside the container, it looks like a tiny fresh Linux machine. From outside, it's just a process running on your host.
So when you use docker exec, you're not "logging into a server." You're telling Docker: hey, start a new process inside that same bubble where my container is already running.
That's a useful mental model. Keep it in mind.
Here's the full command we're going to break down:
docker exec -it my_container bash
Four parts. Let's go through each one.
docker execThis is the base command. It tells Docker you want to execute something inside an already-running container.
Notice it says exec, not run. That's intentional.
docker run — starts a brand new container from an imagedocker exec — runs a command inside a container that's already runningIf the container isn't running, docker exec won't work. It'll throw an error. The container needs to be up and alive first.
-itThis is actually two flags squished together: -i and -t.
-i means interactive. It keeps stdin (standard input) open. Without this, you can't type anything — the shell would open and immediately close because Docker would see no input coming in.
-t means TTY (terminal). This allocates a pseudo-terminal. Without this, you'd technically be connected but the output would look like garbage — no prompt, weird formatting, colors broken.
Together, -it gives you a fully functional, interactive terminal session. It's like the difference between a proper SSH connection versus just piping raw data back and forth.
You'll use -it almost every time you exec into a container. If you're running a one-off non-interactive command (like checking a file), you can skip it. But for an interactive shell? Always use -it.
my_containerThis is the name or ID of the container you want to exec into.
When you start a container, Docker assigns it a random name (like hopeful_goldberg or angry_stallman — seriously, Docker picks these) unless you give it a name yourself with the --name flag.
To see your running containers and their names:
docker ps
You'll get output like this:
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
a3f2c1d4e5b6 ubuntu "/bin/bash" 5 minutes ago Up 5 minutes my_ubuntu
You can use either the container ID (a3f2c1d4e5b6) or the name (my_ubuntu) — both work. And you don't even need the full ID, just enough characters to make it unique. a3f2 would work fine if no other container starts with those same characters.
bashThis is the actual command you want to run inside the container.
In this case, we're running bash — the Bash shell. This drops you into an interactive shell session inside the container.
But here's the thing — you're not limited to bash. You can run any command that exists inside the container:
docker exec my_container ls /etc
docker exec my_container cat /etc/hosts
docker exec my_container python3 --version
These run and exit immediately, printing the output back to your terminal. No shell session, just output.
And about bash specifically — not every container has it. Slim or Alpine-based images often use sh instead of bash. If bash throws an error like executable file not found, try:
docker exec -it my_container sh
Enough theory. Let's get our hands dirty.
docker pull ubuntu
This grabs the official Ubuntu image from Docker Hub. If you've done this before, it'll just confirm the image is already there.
docker run -d --name my_ubuntu ubuntu sleep infinity
Breaking this down quickly:
-d runs it in detached mode (background)--name my_ubuntu gives it a name we can remembersleep infinity keeps the container alive (otherwise Ubuntu would start, find nothing to do, and exit)docker ps
You should see my_ubuntu in the list with status Up.
docker exec -it my_ubuntu bash
Boom. You're in. Your prompt changes to something like:
root@a3f2c1d4e5b6:/#
That weird string after root@ is the container ID. You're now inside the container's filesystem and environment.
Try a few things from inside the container:
# Where are we?
pwd
# What does the filesystem look like?
ls /
# Check the OS info
cat /etc/os-release
# What processes are running?
ps aux
# Any environment variables?
env
Notice how ps aux shows almost nothing — just the few processes Docker is running. No systemd, no cron, no network manager. This is a very lean environment. That's by design.
This container is running as root, and it has apt available:
apt update && apt install -y curl
Now run:
curl --version
You just installed software inside a running container. Cool, right?
But here's an important thing to understand: this change is not permanent. If you stop this container and start a fresh one from the ubuntu image, curl won't be there. The image itself hasn't changed. You only modified the running container's writable layer.
This is why Dockerfiles exist — but that's a topic for another day.
exit
You're back on your host machine. The container is still running in the background (verify with docker ps). You just left the shell session.
Sometimes you don't need a full shell session. You just want to check something quickly.
# Print the contents of a file
docker exec my_ubuntu cat /etc/hostname
# Check if a process is running
docker exec my_ubuntu ps aux | grep sleep
# Create a file inside the container
docker exec my_ubuntu touch /tmp/hello.txt
# Confirm it's there
docker exec my_ubuntu ls /tmp
These are all single-shot commands. They run, print output, and return you to your host terminal immediately. No -it needed.
By default, docker exec runs as the user the container was started with — usually root for most base images.
But sometimes you need to run as a specific user. Use the -u flag:
docker exec -u www-data my_container bash
Or by user ID:
docker exec -u 1001 my_container bash
This is especially useful when working with web server containers where files need specific ownership, or when you want to test how your app behaves under a non-root user.
You can pass environment variables into your exec command with -e:
docker exec -e MY_VAR=hello my_ubuntu bash -c 'echo $MY_VAR'
The -e flag works just like it does in docker run. Useful for passing secrets or config values into one-off commands without baking them into the container permanently.
Mistake 1: Trying to exec into a stopped container
docker exec -it my_ubuntu bash
# Error: container is not running
The container must be running. Check with docker ps. If it's stopped, start it first:
docker start my_ubuntu
docker exec -it my_ubuntu bash
Mistake 2: Forgetting -it for interactive sessions
If you just do docker exec my_ubuntu bash, the shell opens and immediately exits because there's no terminal attached. Always use -it when you want an interactive session.
Mistake 3: Using bash on Alpine containers
Alpine Linux uses sh, not bash. If you exec into an Alpine-based container and get "executable not found," switch to sh:
docker exec -it my_alpine_container sh
Mistake 4: Thinking changes inside exec'd sessions are permanent
Everything you do inside a running container (installing packages, creating files, modifying configs) lives only in that container's writable layer. It won't survive docker rm and it won't be in the image. To make things permanent, put them in a Dockerfile.
Mistake 5: Confusing docker exec with docker attach
docker attach connects you to the main process of the container (PID 1). If you hit Ctrl+C, you kill the container's main process and possibly stop it. docker exec starts a new, separate process. Much safer for debugging. Stick with exec unless you specifically need attach.
Run a command in a specific working directory:
docker exec -w /var/log my_ubuntu ls
The -w flag sets the working directory for the command you're running.
Run multiple commands in one shot:
docker exec my_ubuntu bash -c "apt update && apt install -y wget && wget --version"
The -c flag on bash lets you pass a string of commands to execute.
Exec into a specific container when you have many running:
If you have a bunch of containers and can't remember names, docker ps --format "table {{.Names}}\t{{.Image}}\t{{.Status}}" gives you a cleaner view. Then exec by name.
| Command | What it does |
|---|---|
docker exec -it name bash | Interactive bash shell |
docker exec -it name sh | Interactive sh shell (Alpine) |
docker exec name ls /app | Run a quick command |
docker exec -u username name bash | Exec as specific user |
docker exec -e VAR=val name bash | Exec with env variable |
docker exec -w /path name bash | Exec in specific directory |
docker exec name bash -c "cmd1 && cmd2" | Run chained commands |
Alright, here's a challenge to make sure this actually sticks. Do this from scratch without peeking at the solutions above.
The Challenge:
ubuntu image (if you haven't already)lab_box that stays alive in the backgroundvim text editor using apt/root/notes.txt and add some text to it using vim (or echo)/root/notes.txt without entering the containerubuntu container and confirm that your /root/notes.txt file is gone — proving that your changes didn't surviveBonus challenge: Do the whole thing again, but this time use sh instead of bash everywhere, just to get comfortable with the alternative.
If you got through all 10 steps and understood what happened at each one — especially step 10 — you've genuinely got a solid grasp of how docker exec works and why containers are ephemeral by nature.
docker exec is one of those commands you'll use constantly once you start working with containers day-to-day. It's your window into a running container — whether you're debugging a misbehaving app, checking logs, inspecting configuration, or just poking around to understand what's going on inside.
The full command broken down one more time:
docker exec -it my_container bash
| | | | |
Docker Execute Interactive Target Command
command terminal container to run
Simple once you see each piece for what it is.
The one thing to always remember: docker exec is for running containers only, and anything you change inside won't persist unless you commit it or bake it into your image. Use it for debugging and exploration — not as a substitute for writing proper Dockerfiles.
Now go break stuff. That's how you learn.