Skip to main content

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.

How to follow

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?​

Check Docker version
docker --version
Docker version 24.x.x, build xxxxxxx
Verify Docker is running
docker run hello-world
Hello from Docker!
This message shows that your installation appears to be working correctly.
Remember

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:

Pull and run Ubuntu
docker run -it ubuntu bash
root@a1b2c3d4e5f6:/#

You're now inside an Ubuntu container. Explore:

Inside the container
whoami
root
Check the OS
cat /etc/os-release | head -3
PRETTY_NAME="Ubuntu 22.04.3 LTS"
NAME="Ubuntu"
VERSION_ID="22.04"
Exit the container
exit

The container is now stopped. Check:

See stopped containers
docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS NAMES
a1b2c3d4e5f6 ubuntu "bash" 2 minutes ago Exited (0) 30 seconds ago ...
Remember

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:

Start Nginx in detached mode
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.

Check it's running
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
See the Nginx logs
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 "-"
Stop and remove it
docker stop my-web
docker rm my-web
Remember

-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:

Create project folder
mkdir campus-library
cd campus-library
Create a simple Node.js app
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
Create package.json
cat > package.json << 'EOF'
{
"name": "campus-library",
"version": "1.0.0",
"main": "server.js"
}
EOF
Create the Dockerfile
cat > Dockerfile << 'EOF'
FROM node:20-alpine
WORKDIR /app
COPY . .
EXPOSE 3000
CMD ["node", "server.js"]
EOF
Build the image
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
Run it
docker run -d --name lib -p 3000:3000 campus-library

Open http://localhost:3000 — you should see "Welcome to Campus Library!"

Clean up
docker stop lib
docker rm lib
Remember

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:

Create the project folder
mkdir campus-compose
cd campus-compose
Create docker-compose.yml
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
Start everything
docker compose up -d
[+] Running 3/3
✔ Network campuscompose_default Created
✔ Container campuscompose-db-1 Started
✔ Container campuscompose-web-1 Started
Verify both are running
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.

Clean up (and remove the volume)
docker compose down -v
Common mistake

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:

Start Nginx
docker run -d --name debug-web nginx:alpine
See the logs
docker logs debug-web
127.0.0.1 - - [10/Oct/2024:12:00:00 +0000] "GET / HTTP/1.1" 200 615 "-"
Open a shell inside the running container
docker exec -it debug-web sh
/ # ls /usr/share/nginx/html/
index.html
/ # cat /usr/share/nginx/html/index.html
<!DOCTYPE html>
...
/ # exit
Run a single command without entering
docker exec debug-web cat /etc/nginx/nginx.conf
Clean up
docker rm -f debug-web
Remember

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:

Start Nginx with a health check
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
Watch the health status
docker inspect --format='{{.State.Health.Status}}' healthy-web

Wait a few seconds and run again:

Check again
docker inspect --format='{{.State.Health.Status}}' healthy-web
healthy
Clean up
docker rm -f healthy-web
Remember

Health checks catch problems before users do. A container can be "running" but unhealthy — the health check tells you the difference.