Skip to main content

⌨️ Basic Docker Commands

You've got the mental model and you've seen the architecture. Now it's time to actually drive. This lesson is the practical core of the week: the handful of commands you'll type every single day to pull images, launch containers, publish ports, peek at logs, run a shell inside a container, and clean up afterward. Learn these well and Docker stops feeling like magic and starts feeling like a tool.

Week 11 · Day 1 (Monday: Docker Fundamentals) · Lecture 3

🎯 Learning Objectives

By the end of this lesson, you will be able to:

  • Pull images with docker pull and list what you have with docker images
  • Launch containers with docker run, including detached mode (-d) and port publishing (-p)
  • List, stop, and remove containers with docker ps, docker stop, and docker rm
  • Inspect a running container with docker logs and open a shell inside it with docker exec
  • Explain the difference between removing a container (rm) and removing an image (rmi)
  • Walk a container through its complete lifecycle from pull to cleanup

Estimated Time: 65 minutes

Practice: Run an Nginx web server, view its logs, shell into it, then stop and remove it cleanly.

In This Lesson

The Docker CLI

The Docker command-line interface (CLI) is the docker program you met last lesson — the client that sends your instructions to the daemon. It groups its work into a few areas, and once you see the shape, new commands are easy to guess.

graph LR A["docker CLI"] --> B["Containers
run, ps, stop, rm"] A --> C["Images
pull, images, rmi"] A --> D["Logs & Exec
logs, exec"] A --> E["System
info, prune"]

📖 Your best friend: --help

Every command documents itself. Forget an option? Ask:

docker --help          # all top-level commands
docker run --help      # every option for run
docker ps --help       # every option for ps

Get in the habit early — it's faster than searching the web and always matches your installed version.

Anatomy of a Command

Docker commands follow a predictable structure. Once you can name the parts, long commands stop looking intimidating.

docker [command] [options] [image or container] [arguments]

Take a real one and label every piece:

docker run -d -p 8080:80 --name web nginx
PartWhat it is
dockerThe base command (the client)
runThe command — create and start a container
-dA flag — detached (run in the background)
-p 8080:80An option with a value — publish host port 8080 to container port 80
--name webAn option — give the container a friendly name
nginxThe argument — which image to run

💡 Two spellings, same result

Docker offers a newer, explicit form (docker container run, docker image ls) and a classic shorthand (docker run, docker ps). They do the same thing. The shorthand is what you'll see most in the wild, so we'll use it here — just know both exist.

Working with Images

Before you can run a container, you need an image. Images come from a registry (Docker Hub by default), and docker run will pull one automatically if it's missing — but it's worth knowing the explicit commands.

# Download an image from the registry
docker pull nginx

# Download a specific version (tag) — always prefer a real tag over "latest"
docker pull nginx:1.27

# List the images you have locally
docker images

# Remove an image you no longer need
docker rmi nginx:1.27

Output of docker images

REPOSITORY   TAG       IMAGE ID       CREATED       SIZE
nginx        latest    a72860cb95fd   2 weeks ago   188MB
node         20-alpine 4d3f8e2b1c9a   3 weeks ago   135MB

Pulling an image is like downloading an app from an app store: you grab it once, it's cached locally, and every container you launch from it reuses that download. The SIZE column shows why the Alpine variants (built on tiny Alpine Linux) are so popular for keeping images lean.

⚠️ Avoid relying on latest

nginx:latest is not a fixed version — it's whatever the maintainers most recently tagged as latest, and it changes over time. Pinning a real tag like nginx:1.27 makes your builds reproducible, so a rebuild months later doesn't silently pull a different version.

Running Containers

docker run is the command you'll type most. It creates a container from an image and starts it in one step. Its options configure nearly everything about how the container behaves — here are the ones that matter on day one.

# Simplest form — runs in the foreground, ties up your terminal
docker run nginx

# Detached (-d): run in the background and get your prompt back
docker run -d nginx

# Publish a port (-p HOST:CONTAINER): reach the app from your browser
docker run -d -p 8080:80 nginx

# Give it a name so you don't have to copy container IDs around
docker run -d -p 8080:80 --name web nginx

# Pass environment variables (-e) — common for databases
docker run -d -e POSTGRES_PASSWORD=secret --name db postgres:16

# Interactive shell (-it) into a fresh Ubuntu container
docker run -it ubuntu bash

# Auto-remove the container the moment it stops (--rm) — great for one-offs
docker run --rm hello-world

Two flags to understand deeply: -d and -p

These two show up constantly, so let's be precise about what they do.

Detached mode (-d) runs the container in the background instead of attaching it to your terminal. Without it, a server like nginx would hold your terminal hostage, printing its logs, until you pressed Ctrl+C (which stops it). With -d you get your prompt back and the container keeps running.

Port publishing (-p HOST:CONTAINER) connects a port on your machine to a port inside the container. A container is isolated by default — the web server listening on port 80 inside the container is unreachable from your browser until you map it out. -p 8080:80 says "traffic to port 8080 on my machine goes to port 80 in the container."

A browser request to host port 8080 is forwarded by port publishing into container port 80 where nginx listens Your Browser localhost:8080 Host machine -p 8080:80 port publishing nginx port 80
With -p 8080:80, a request to localhost:8080 is forwarded into the container's port 80, where nginx is listening. Without it, the container stays sealed off.

✅ Try it right now

docker run -d -p 8080:80 --name web nginx

Then open http://localhost:8080 in your browser — you'll see the Nginx welcome page, served from inside a container you started with one line.

The Container Lifecycle

A container moves through predictable states. Knowing the transitions tells you which command to reach for.

graph LR A["Image"] -->|docker run| B["Running"] B -->|docker stop| C["Stopped"] C -->|docker start| B C -->|docker rm| D["Removed"] B -->|docker rm -f| D
# See what's running right now
docker ps

# See everything, including stopped containers
docker ps -a

# Stop a running container gracefully (by name or ID)
docker stop web

# Start a stopped container back up
docker start web

# Restart in one step
docker restart web

Output of docker ps

CONTAINER ID   IMAGE   COMMAND                  STATUS         PORTS                  NAMES
3f9a1b2c4d5e   nginx   "/docker-entrypoint.…"   Up 2 minutes   0.0.0.0:8080->80/tcp   web

Notice the NAMES column — because we passed --name web, we can use web everywhere instead of copying that container ID. And docker ps shows only running containers; add -a to see stopped ones too (a common "where did my container go?" gotcha).

Inspecting & Interacting

Once a container is running, two commands do most of the debugging work: logs to see its output, and exec to step inside it.

Reading logs

A container's logs are whatever its main process wrote to stdout and stderr — for a server, that's the request log and any errors. This is the first place to look when something isn't working.

# Print the container's logs
docker logs web

# Follow the logs live, like tail -f (Ctrl+C to stop watching)
docker logs -f web

# Just the last 50 lines, with timestamps
docker logs --tail 50 -t web

Running a command inside a container

docker exec runs an additional command inside an already-running container. Combined with -it (interactive + terminal), it drops you into a shell — like SSH-ing into a server, but for a container.

# Open an interactive shell inside the running "web" container
docker exec -it web bash

# Some minimal images (like Alpine) have sh but not bash
docker exec -it web sh

# Run a one-off command without an interactive session
docker exec web ls /usr/share/nginx/html

⚠️ exec vs. run — a common mix-up

docker run starts a brand-new container. docker exec runs a command in an existing, already-running one. If you "exec" into a container and make changes, remember those changes live in that container's writable layer — they vanish when the container is removed. To make a change permanent, edit your Dockerfile and rebuild.

You can also get a full JSON dump of a container's configuration:

# Everything Docker knows about the container
docker inspect web

# Live CPU / memory usage of all running containers
docker stats

Cleaning Up

Containers and images accumulate. Left unchecked they eat disk space, so cleanup is a real skill. The key distinction: rm removes containers, rmi removes images.

# Remove a stopped container
docker rm web

# Force-remove a running container (stop + remove)
docker rm -f web

# Remove an image (fails if a container still uses it)
docker rmi nginx

# Remove ALL stopped containers at once
docker container prune

# Remove dangling/unused images
docker image prune

💡 Why can't I remove this image?

If docker rmi nginx complains that the image is "being used by" a container, it's protecting you: an image can't be deleted while a container built from it still exists. Remove the container first (docker rm), then the image. This mirrors the image-vs-container relationship — you can't tear up the blueprint while a house built from it still stands.

⚠️ prune is a bulk delete

docker container prune removes all stopped containers; docker system prune -a goes further and removes unused images and networks too. They're great for reclaiming space, but read the confirmation prompt — this is one of the few Docker commands that deletes many things at once.

Practice & Quiz

🏋️ Exercise 1: Full lifecycle of a web server

Goal: Take an Nginx container from launch to cleanup, touching every command from this lesson. Write the commands to: (1) run nginx detached, named myweb, on host port 8080; (2) confirm it's running; (3) view its logs; (4) open a shell inside it; (5) stop it; (6) remove it.

💡 Hint

You'll need run with -d, -p, and --name; then ps, logs, exec -it, stop, and rm — all referring to the container by its name myweb.

✅ Solution
# 1. Run detached, named, with port published
docker run -d -p 8080:80 --name myweb nginx

# 2. Confirm it's running
docker ps

# 3. View its logs (open http://localhost:8080 first to generate some)
docker logs myweb

# 4. Open a shell inside it (type "exit" to leave)
docker exec -it myweb bash

# 5. Stop it
docker stop myweb

# 6. Remove it
docker rm myweb

Tip: steps 5 and 6 can be combined with docker rm -f myweb, which stops and removes in one go.

🏋️ Exercise 2: Diagnose a "port already in use" error

Goal: You run docker run -d -p 8080:80 --name web2 nginx and get an error that port 8080 is already allocated. What likely happened, and how do you fix it without losing your work?

✅ Solution

An earlier container (from Exercise 1, perhaps) is still using host port 8080. Two containers can't publish to the same host port at once. Fixes: publish the new one on a different host port (-p 8081:80), or stop/remove the container holding 8080 (docker ps to find it, then docker stop/docker rm). Note the container port stays 80 — it's the host side that must be unique.

🎯 Quick Quiz

Question 1: What does the -d flag do in docker run -d nginx?

Question 2: In -p 8080:80, which number is the port inside the container?

Question 3: Which command removes an image (not a container)?

Best Practices & Pitfalls

✅ Do

  • Name your containers with --name so you never wrestle with long IDs
  • Use -d for anything long-running, like a server or database
  • Reach for docker logs first when a container misbehaves
  • Prune stopped containers and unused images regularly to reclaim disk
  • Pin real image tags (node:20-alpine) instead of latest

❌ Don't

  • Forget -adocker ps hides stopped containers, and then you "lose" them
  • Confuse rm (containers) with rmi (images)
  • Rely on changes made via exec to persist — they die with the container
  • Publish two containers to the same host port at once
  • Run docker system prune -a without reading what it will delete

✅ A tiny cheat sheet

GoalCommand
Get an imagedocker pull nginx
Run it in the background on a portdocker run -d -p 8080:80 --name web nginx
See what's runningdocker ps
Read its logsdocker logs -f web
Shell into itdocker exec -it web bash
Stop and remove itdocker rm -f web

Summary

🎉 Key Takeaways

  • docker pull fetches an image; docker images lists what you have
  • docker run creates and starts a container — use -d for the background and -p HOST:CONTAINER to publish a port
  • docker ps shows running containers; add -a to see stopped ones too
  • docker logs shows a container's output; docker exec -it … bash opens a shell inside it
  • docker stop halts a container; rm removes containers and rmi removes images

📚 Additional Resources

🚀 What's Next?

So far you've been running other people's images. Next you'll build your own: Dockerfile Syntax teaches you to write the recipe — FROM, WORKDIR, COPY, RUN, EXPOSE, CMD — that turns your Node.js app into a shippable image.

🎉 You can drive Docker now!

Pull, run, inspect, clean up — this is the daily loop every containerized workflow is built on. Next stop: building images of your own.