🐳 Lesson 3: Your First Containers
Stop reading about containers and start running them. This is the hands-on lesson where docker run, ps, logs, exec, and the full container lifecycle become second nature.
🎯 Learning Objectives
By the end of this lesson, you will be able to:
- Run containers with
docker run, decoding every flag in-d --name web -p 8080:80 nginx - List, inspect, and monitor containers with
docker ps,inspect,stats, andtop - Read logs and get a shell inside a running container using
docker logsanddocker exec - Manage the container lifecycle — stop, start, restart, restart policies — and clean up safely
Estimated Time: 45–55 minutes
Prerequisite: Lessons 1–2 (you understand images vs containers and the Docker engine).
In This Lesson
🚀 The docker run Anatomy
Almost everything you do with Docker starts with one command: docker run. It takes an image, creates a fresh container from it, and starts it. Let's run a real web server — nginx — and dissect every part of the command.
docker run -d --name web -p 8080:80 nginx
That one line packs four decisions. Here is what each piece is telling Docker:
docker run reads as flags first, then the image, then any command to run inside.Run it, and Docker prints the new container's long ID and hands your prompt straight back:
Output:
Unable to find image 'nginx:latest' locally
latest: Pulling from library/nginx
a2318d6c47ec: Pull complete
Digest: sha256:5c5a0a2f2a0c8f5b8e3d1f6b0e9c2d4a7b1e3f5a9c8d7e6b4a2c1d3e5f7a9b0c
Status: Downloaded newer image for nginx:latest
9f1c2b3a4d5e6f7089a1b2c3d4e5f60718293a4b5c6d7e8f9012a3b4c5d6e7f8
Now open http://localhost:8080 in your browser — you'll see the "Welcome to nginx!" page, served from inside a container that didn't exist ten seconds ago. No install, no config files, no system services.
How port mapping works
The container listens on port 80 in its own private network namespace. That port is invisible to your machine until you publish it. -p 8080:80 tells Docker: "forward traffic arriving at host port 8080 to container port 80." The format is always -p HOST:CONTAINER.
-p, the container's port 80 stays private. Publishing it bridges a host port to the container port.💡 Tip: The two ports don't have to match.-p 3000:80would serve the same nginx onlocalhost:3000. Use different host ports to run several copies of the same image side by side —-p 8081:80,-p 8082:80, and so on.
🎛️ Detached, Foreground & Interactive
A container runs a single main process. How you attach to that process is your choice, and it changes what your terminal does.
Foreground (the default)
Without -d, the container runs in the foreground and streams its output to your terminal. Your prompt is occupied until the process ends (press Ctrl+C to stop it):
docker run -p 8080:80 nginx
Output:
/docker-entrypoint.sh: Configuration complete; ready for start up
2024/11/18 14:02:11 [notice] 1#1: nginx/1.27.2
2024/11/18 14:02:11 [notice] 1#1: start worker processes
172.17.0.1 - - [18/Nov/2024:14:02:30 +0000] "GET / HTTP/1.1" 200 615 "-" "curl/8.5.0"
Detached (-d)
Add -d and the container runs in the background. Docker prints the container ID and returns your prompt so you can keep working. This is how you run long-lived services like web servers and databases.
Interactive (-it)
Sometimes you want a shell inside a container to poke around. The -it combo does that: -i keeps STDIN open (interactive) and -t allocates a pseudo-TTY (a proper terminal). Pair it with a shell command like bash:
docker run -it ubuntu bash
Output:
root@a3f5d2c81b4e:/# cat /etc/os-release | head -1
PRETTY_NAME="Ubuntu 24.04.1 LTS"
root@a3f5d2c81b4e:/# whoami
root
root@a3f5d2c81b4e:/# exit
exit
You dropped into a root shell inside an isolated Ubuntu environment, ran commands, and exit ended the shell — which stopped the container, because bash was its main process. When the main process exits, the container stops.
Auto-clean with --rm
Every container you run leaves behind a stopped container record (you'll see them with docker ps -a). For throwaway experiments, add --rm so Docker deletes the container automatically the moment it stops:
docker run --rm -it ubuntu bash
📖 Flag cheat sheet
-d— detached, run in the background-it— interactive + TTY, for a shell you can type into--rm— auto-remove the container when it stops--name— give the container a memorable name-p HOST:CONTAINER— publish a port-e KEY=value— set an environment variable inside the container
🔍 Listing, Inspecting, Logs & Exec
Once containers are running, you need to see them, understand them, and reach inside. This is your day-to-day toolkit.
Listing: docker ps
docker ps lists running containers. Add -a to include stopped ones too:
docker ps
Output:
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
9f1c2b3a4d5e nginx "/docker-entrypoint.…" 2 minutes ago Up 2 minutes 0.0.0.0:8080->80/tcp web
docker ps -a
Output:
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
9f1c2b3a4d5e nginx "/docker-entrypoint.…" 2 minutes ago Up 2 minutes 0.0.0.0:8080->80/tcp web
a3f5d2c81b4e ubuntu "bash" 5 minutes ago Exited (0) 3 minutes ago nostalgic_khorana
Notice the exited ubuntu container — that's the leftover from our interactive session. Docker auto-generates a whimsical name (nostalgic_khorana) when you don't supply --name.
Inspecting: docker inspect
docker inspect dumps everything Docker knows about a container as JSON — its IP address, mounts, environment, restart policy, and more. Use --format to pull out just what you need:
docker inspect --format '{{.NetworkSettings.IPAddress}}' web
Output:
172.17.0.2
Monitoring: docker stats and docker top
docker stats is a live, top-style stream of CPU, memory, and network usage per container:
docker stats --no-stream
Output:
CONTAINER ID NAME CPU % MEM USAGE / LIMIT MEM % NET I/O BLOCK I/O PIDS
9f1c2b3a4d5e web 0.00% 3.9MiB / 7.6GiB 0.05% 1.2kB/0B 0B/0B 3
docker top shows the processes running inside a container, straight from the host:
docker top web
Output:
UID PID PPID C STIME TTY TIME CMD
root 14231 14210 0 14:02 ? 00:00:00 nginx: master process nginx -g daemon off;
101 14290 14231 0 14:02 ? 00:00:00 nginx: worker process
Logs: docker logs
A detached container still writes to STDOUT/STDERR — Docker captures it. docker logs replays it, and -f ("follow") streams new lines live, just like tail -f:
docker logs -f web
Output:
2024/11/18 14:02:11 [notice] 1#1: start worker processes
172.17.0.1 - - [18/Nov/2024:14:05:44 +0000] "GET / HTTP/1.1" 200 615 "-" "Mozilla/5.0"
172.17.0.1 - - [18/Nov/2024:14:05:44 +0000] "GET /favicon.ico HTTP/1.1" 404 555 "-" "Mozilla/5.0"
Press Ctrl+C to stop following — that only detaches your log view; the container keeps running.
Getting a shell: docker exec
Here's the one you'll reach for constantly. docker exec runs a new command inside an already-running container. Combine it with -it and a shell to jump inside a live container and look around — without disturbing its main process:
docker exec -it web bash
Output:
root@9f1c2b3a4d5e:/# ls /usr/share/nginx/html
50x.html index.html
root@9f1c2b3a4d5e:/# nginx -v
nginx version: nginx/1.27.2
root@9f1c2b3a4d5e:/# exit
exit
⚠️execvsrun:docker runcreates a brand-new container from an image.docker execreaches into an existing, running container. Exiting anexecshell does not stop the container — you only ended the extra process you started.
♻️ Lifecycle, Restart Policies & Cleanup
A container moves through a predictable set of states. Learn the verbs that move it between them and you're in full control.
The lifecycle verbs
| Command | What it does |
|---|---|
docker stop web | Graceful shutdown — sends SIGTERM, then SIGKILL after a grace period (10s) |
docker start web | Restart a stopped container, keeping its config and data |
docker restart web | Stop then start in one step |
docker pause web | Freeze all processes (they stay in memory, using no CPU) |
docker unpause web | Resume a paused container exactly where it left off |
docker rm web | Delete a stopped container permanently |
docker rm -f web | Force-remove a container even while it's running (stop + remove) |
docker stop web
docker start web
docker restart web
Output:
web
web
web
Each command echoes the name (or ID) it acted on. To remove a container you must stop it first — or use -f to do both at once:
docker rm -f web
Restart policies: keep it running
For real services you want the container to come back automatically after a crash or a host reboot. The --restart flag sets that policy at run time. The most useful for a personal server is unless-stopped:
docker run -d --name web --restart unless-stopped -p 8080:80 nginx
| Policy | Behavior |
|---|---|
no | Default — never restart automatically |
on-failure[:max] | Restart only if it exits with a non-zero code (optionally cap the retries) |
always | Always restart, including after a Docker daemon / host reboot |
unless-stopped | Like always, but respects a manual docker stop — won't come back until you start it |
Cleanup: reclaim disk safely
Stopped containers pile up. docker container prune removes all of them in one sweep (it asks for confirmation first):
docker container prune
Output:
WARNING! This will remove all stopped containers.
Are you sure you want to continue? [y/N] y
Deleted Containers:
a3f5d2c81b4e9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b
Total reclaimed space: 12.4kB
⚠️ Danger:docker system prune -ais the nuclear option. It deletes all stopped containers, all unused networks, and every image not currently used by a running container — including base images you'll have to re-download. Read the confirmation prompt before typingy; on a busy machine it can wipe out gigabytes you meant to keep.
🔎 Modern tooling note
As your projects grow you'll manage multiple containers together with docker compose (Compose v2 — always spelled as a space, never the old docker-compose hyphenated binary). And to check an image for vulnerabilities, today's command is docker scout cves nginx — docker scan was retired. We cover both in later modules; for now, just know the current names.
🏋️ Hands-On Lab
Time to wire it all together. This lab has you run a web server, verify it, step inside it, read its logs, and clean up — the exact loop you'll repeat thousands of times.
🏋️ Exercise: Run, reach into, and retire a container
Objective: Practice the full run → inspect → exec → logs → stop → remove cycle.
Instructions:
- Run nginx detached, named
lab, mapping host port8088to container port80. - Confirm it's running with
docker ps. - Hit it from the command line with
curl http://localhost:8088(or open it in a browser). - Get a shell inside with
docker execand confirm the nginx version. - View the access log entry your curl request produced.
- Stop the container, then remove it.
💡 Hint
Remember the flag order: options first, image last. For step 5, the request you made in step 3 shows up in docker logs. You can collapse the last two steps into one with docker rm -f lab.
✅ Worked Solution
# 1. Run nginx detached, named, with a port mapping
docker run -d --name lab -p 8088:80 nginx
# 2. Confirm it is up
docker ps
# 3. Request the page from the terminal
curl http://localhost:8088
# 4. Get a shell inside and check the version
docker exec -it lab bash
# root@...:/# nginx -v
# nginx version: nginx/1.27.2
# root@...:/# exit
# 5. Read the logs — your curl request is there
docker logs lab
# 6. Stop and remove (or: docker rm -f lab)
docker stop lab
docker rm lab
What you should observe: curl returns the nginx welcome HTML; the exec shell reports the nginx version and exiting it leaves the container running; docker logs shows your GET / HTTP/1.1 200 line; and after rm, the container no longer appears in docker ps -a. That end-to-end loop is the heartbeat of working with Docker.
Quick Quiz
Question 1: In docker run -d --name web -p 8080:80 nginx, what does -p 8080:80 do?
Question 2: You need a shell inside a container that is already running. Which command do you use?
Question 3: Which restart policy brings a container back after a crash or host reboot, but respects a manual docker stop?
Summary
🎉 Key Takeaways
docker runreads flags-first, image-last —-ddetaches,--namelabels,-p HOST:CONTAINERpublishes a port, and the image (with an implied:latest) comes at the end.- Choose your attachment mode: foreground streams logs,
-druns in the background,-itgives you a shell, and--rmauto-cleans throwaway containers. - See and reach inside:
docker ps -alists everything,inspect/stats/topreveal details,logs -ffollows output, andexec -it … bashdrops you into a running container. - Command the lifecycle: stop, start, restart, pause, and rm move a container between states;
--restart unless-stoppedkeeps services alive;container prunecleans up — and treatsystem prune -awith respect.
📚 Additional Resources
- docker run reference — every flag, in detail
- Running containers — the official guide to lifecycle and options
- docker exec reference — running commands in a live container
- Start containers automatically — restart policies explained
🚀 What's Next?
You can now run and manage containers built from existing images. Next, you'll learn to build your own images with a Dockerfile — turning your application into a portable container you control from the ground up.
🎉 Lesson 3 complete!
Running containers is now muscle memory. Time to build your own images!