Docker Hands-on Exercises — Pack Your Own Lunch Boxes
Time to stop reading and start doing. These exercises run entirely on your own computer — no cloud account needed. Install Docker Desktop (or Docker Engine on Linux) first.
Open a terminal, type the commands one by one, and read the output. If your output differs, re-read the step — something small (like Docker not running) is usually the cause.
Exercise 0 · Setup — Is Docker Installed?
docker --version
Docker version 24.x.x, build xxxxxxx
docker run hello-world
Hello from Docker!
This message shows that your installation appears to be working correctly.
If docker run hello-world fails, Docker Desktop isn't running. Start it and try again.
Exercise 1 · First Container — Your First Lunch Box
Run a container from the ubuntu image and step inside:
docker run -it ubuntu bash
root@a1b2c3d4e5f6:/#
You're now inside an Ubuntu container. Explore:
whoami
root
cat /etc/os-release | head -3
PRETTY_NAME="Ubuntu 22.04.3 LTS"
NAME="Ubuntu"
VERSION_ID="22.04"
exit
The container is now stopped. Check:
docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS NAMES
a1b2c3d4e5f6 ubuntu "bash" 2 minutes ago Exited (0) 30 seconds ago ...
docker run -it = interactive + terminal. Without -it, you can't type inside the container.
Exercise 2 · Web Server — Pack an Nginx Lunch Box
Run an Nginx web server in the background:
docker run -d --name my-web -p 8080:80 nginx
a1b2c3d4e5f6g7h8i9j0...
Open your browser and go to http://localhost:8080. You should see the Nginx welcome page.
docker ps
CONTAINER ID IMAGE COMMAND STATUS PORTS NAMES
a1b2c3d4e5f6 nginx "/docker-entrypoint.…" Up 30 seconds 0.0.0.0:8080->80/tcp my-web
docker logs my-web
/docker-entrypoint.sh: Configuration complete; ready for start up
172.17.0.1 - - [10/Oct/2024:12:00:00 +0000] "GET / HTTP/1.1" 200 612 "-"
docker stop my-web
docker rm my-web
-p 8080:80 maps port 8080 on your computer to port 80 inside the container. Format: HOST_PORT:CONTAINER_PORT.
Exercise 3 · Build Your Own Image — Campus Library
Create a simple app and build it into a Docker image:
mkdir campus-library
cd campus-library
cat > server.js << 'EOF'
const http = require('http');
const server = http.createServer((req, res) => {
res.writeHead(200, {'Content-Type': 'text/plain'});
res.end('Welcome to Campus Library!');
});
server.listen(3000, () => console.log('Library running on port 3000'));
EOF
cat > package.json << 'EOF'
{
"name": "campus-library",
"version": "1.0.0",
"main": "server.js"
}
EOF
cat > Dockerfile << 'EOF'
FROM node:20-alpine
WORKDIR /app
COPY . .
EXPOSE 3000
CMD ["node", "server.js"]
EOF
docker build -t campus-library .
[+] Building 8.2s (6/6) FINISHED
=> [1/3] FROM node:20-alpine
=> [2/3] WORKDIR /app
=> [3/3] COPY . .
=> naming to docker.io/library/campus-library
docker run -d --name lib -p 3000:3000 campus-library
Open http://localhost:3000 — you should see "Welcome to Campus Library!"
docker stop lib
docker rm lib
The .dockerignore file (like .gitignore) keeps unnecessary files out of the image. Create one with node_modules and .git.
Exercise 4 · Docker Compose — Two Services
Run a web server and a database together:
mkdir campus-compose
cd campus-compose
cat > docker-compose.yml << 'EOF'
services:
web:
image: nginx:alpine
ports:
- "8080:80"
depends_on:
- db
db:
image: postgres:16-alpine
environment:
POSTGRES_DB: campus
POSTGRES_USER: riya
POSTGRES_PASSWORD: secret
ports:
- "5432:5432"
volumes:
- db-data:/var/lib/postgresql/data
volumes:
db-data:
EOF
docker compose up -d
[+] Running 3/3
✔ Network campuscompose_default Created
✔ Container campuscompose-db-1 Started
✔ Container campuscompose-web-1 Started
docker compose ps
NAME IMAGE STATUS
campuscompose-db-1 postgres:16-alpine Up 10 seconds
campuscompose-web-1 nginx:alpine Up 10 seconds
Open http://localhost:8080 — Nginx is running. The database is ready on port 5432.
docker compose down -v
docker compose down only stops containers. docker compose down -v also deletes the data volume. Use -v only when you want a fresh start.
Exercise 5 · Logs and Exec — Debugging a Container
Start a container and investigate it:
docker run -d --name debug-web nginx:alpine
docker logs debug-web
127.0.0.1 - - [10/Oct/2024:12:00:00 +0000] "GET / HTTP/1.1" 200 615 "-"
docker exec -it debug-web sh
/ # ls /usr/share/nginx/html/
index.html
/ # cat /usr/share/nginx/html/index.html
<!DOCTYPE html>
...
/ # exit
docker exec debug-web cat /etc/nginx/nginx.conf
docker rm -f debug-web
docker exec -it <container> sh opens a shell. docker exec <container> <command> runs a single command. Use sh for Alpine, bash for Ubuntu/Debian.
Exercise 6 · Health Check — Quality Control
Add a health check to a container:
docker run -d --name healthy-web \
--health-cmd "wget -qO- http://localhost/ || exit 1" \
--health-interval 10s \
--health-timeout 3s \
--health-retries 3 \
nginx:alpine
docker inspect --format='{{.State.Health.Status}}' healthy-web
Wait a few seconds and run again:
docker inspect --format='{{.State.Health.Status}}' healthy-web
healthy
docker rm -f healthy-web
Health checks catch problems before users do. A container can be "running" but unhealthy — the health check tells you the difference.