Last week you built your first Docker image. This week we shift from building to running — the everyday commands you'll reach for constantly once containers are part of your workflow. If Week 01 was the blueprint, this week is moving into the house.
The docker run command does two things at once: it creates a new container from an image and starts it. If the image isn't already on your machine, Docker pulls it first, then launches a container from it. Every container you start gets its own filesystem, process space, and network interface, isolated from the host and from other containers. Once a container is running, docker ps shows you what's active — container ID, image, status, and ports. But docker ps only shows running containers by default. Add the -a flag (docker ps -a) to see everything, including containers that have exited or crashed. New learners are often confused when a container they ran "disappears" — it didn't disappear, it stopped, and you need -a to see it. A related but different command is docker exec. Where docker run always creates a brand-new container, docker exec reaches into a container that is already running and executes an additional command there, without restarting or recreating anything. This distinction matters: if you want a fresh container, use run; if you want to poke around inside one that's already alive, use exec.
| Command | What it does | Creates a new container? |
|---|---|---|
docker run <image> |
Creates and starts a container from an image | Yes |
docker ps |
Lists running containers | No |
docker ps -a |
Lists all containers, running or stopped | No |
docker exec -it <container> bash |
Runs a command/shell inside a running container | No |
docker start/stop/restart <container> |
Changes the running state of an existing container | No |
docker rm <container> |
Deletes a stopped container | No |
Two flags decide how you experience a running container. -it combines -i (interactive, keeps STDIN open) and -t (allocates a pseudo-TTY), which together give you a live interactive shell — this is what you want when you run something like docker run -it ubuntu bash and expect a prompt you can type into. -d (detached) is the opposite: the container starts, runs in the background, and control returns to your terminal immediately. This is how you run long-lived services — a web server, a database, an inference API — without tying up your terminal session. You'll use -d far more often than -it once you move past debugging and into running actual services.
On a shared machine running multiple AI workloads (say, a training job, an inference server, and a monitoring stack all on one GPU box), one runaway container can starve the others. Docker lets you cap resource usage at launch time with flags like --memory (or -m) and --cpus. For example, docker run -m 512m my-image limits that container to 512 MB of RAM; --cpus="1.5" limits it to the equivalent of one and a half CPU cores. These limits are enforced by the container runtime, so a memory leak or infinite loop in one container can't take down every other workload sharing the host.
Containers are isolated by default, which means a web server running inside one is invisible from outside unless you explicitly open a path. That's what port mapping does. The syntax is -p host_port:container_port — for example, docker run -p 8080:80 nginx forwards traffic arriving at port 8080 on your host machine to port 80 inside the container, which is where nginx listens by default. Get the order backwards and nothing works the way you expect.
By default, anything a container writes to its own filesystem lives only as long as that container exists. Stop the container and restart it — fine, the data is still there, because it's the same container. But remove the container (docker rm) and start a new one from the same image, and all of that written data is gone, because a new container gets a fresh filesystem copied from the image, not from the container you deleted. Persisting data across container removal and recreation requires an explicit volume or bind mount, which we'll cover in a later week.
"I can just install a package or edit a file inside my running container to fix it." You can, and it will even work — until the container is removed. The change lives only in that container's writable layer, not in the image it was built from. If you remove the container and start a new one from the same image, your fix is gone, because the image itself was never touched. The professional habit is: whenever you find yourself fixing something by hand inside a container, capture that fix as a line in the Dockerfile and rebuild the image so the fix is permanent and reproducible for everyone who uses that image. "Port mapping order doesn't matter, it's just two numbers." It matters a great deal, and the order is always host:container. Writing -p 80:8080 binds your host's port 80 to the container's internal port 8080. If you meant to expose a service listening on port 80 inside the container to port 8080 on your host, you'd need to write it the other way: -p 8080:80. Reversing the two numbers is one of the most common reasons a "working" container seems unreachable — always read the mapping as "traffic hits my machine here, and gets forwarded to the container there." Next week we'll build on this by writing custom Dockerfiles and managing multi-container setups with Docker Compose. For now, get comfortable running, inspecting, and cleaning up containers — these commands are the ones you'll type dozens of times a day for the rest of this course.