Docker Tutorial (Beginner → Practical)
What is Docker?
Docker is a containerization platform used to build, ship, and run applications consistently across environments.
Why teams use it:
- Save cost (less heavy infrastructure than full VMs)
- Save time (start containers in seconds)
- Improve quality (more time for coding/testing, fewer “works on my machine” issues)
Docker Engine is a client-server system: CLI (docker) talks to the daemon (dockerd) via APIs; the daemon manages images, containers, networks, volumes. (Docker Documentation)
VM vs Container (your “kernel + rootfs” idea, clarified)
VM (Virtual Machine)
- Each VM includes: hardware virtualization + full OS kernel + user space
- Heavier, slower boot, more overhead.
Container
- Containers share the host kernel
- Each container gets its own isolated “view” of the system (process tree, mounts, network, users) and resource limits (CPU/mem).
- A container is basically: a process + isolation + limits (runtime resources)
Your mental model is solid:
- VM image ≈ OS kernel + rootfs + apps (full machine image)
- Container image ≈ rootfs + apps (uses host kernel at runtime)
Docker internals (your flow: client → server → containerd → kernel)
Human → Docker CLI → Docker daemon → containerd → runtime → kernel
- Docker Engine is client-server (
dockerCLI +dockerddaemon). (Docker Documentation) containerdis the runtime manager underneath Docker (Docker installs/uses it).- The kernel provides isolation + control (namespaces/cgroups conceptually).
1) Install Docker (Ubuntu)
Recommended: install from Docker’s official apt repo
Docker’s Ubuntu install guide includes the repository setup, package install, and verification with hello-world. (Docker Documentation)
Install (official method, current format):
sudo apt update
sudo apt install ca-certificates curl
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc
sudo tee /etc/apt/sources.list.d/docker.sources >/dev/null <<'EOF'
Types: deb
URIs: https://download.docker.com/linux/ubuntu
Suites: $(. /etc/os-release && echo "${UBUNTU_CODENAME:-$VERSION_CODENAME}")
Components: stable
Signed-By: /etc/apt/keyrings/docker.asc
EOF
sudo apt update
sudo apt install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
Code language: JavaScript (javascript)
Verify:
sudo docker run hello-world
Post-install: run Docker without sudo (optional)
Docker docs: daemon uses a Unix socket owned by root; you can add your user to the docker group (warning: this is effectively root-level power). (Docker Documentation)
sudo groupadd docker
sudo usermod -aG docker $USER
newgrp docker
docker run hello-world
Code language: PHP (php)
Important firewall note (Ubuntu)
Docker docs warn: publishing ports can bypass ufw/firewalld rules, and recommend using iptables/ip6tables and the DOCKER-USER chain. (Docker Documentation)
2) Core Docker objects (must-know)
- Image: read-only template (rootfs + app + dependencies)
- Container: running instance of an image (a process)
- Volume: persistent storage managed by Docker
- Network: connectivity between containers and the outside world
3) Image basics (download, list, inspect)
Where do images come from?
Common registries:
- Docker Hub (default):
docker pull nginx - Fully qualified format:
docker.io/library/nginx:latestghcr.io/<org>/<image>:tagpublic.ecr.aws/...
Pull an image
docker pull httpd
docker pull httpd:2.4
docker pull nginx:latest
Code language: CSS (css)
List images:
docker images
Inspect an image:
docker image inspect httpd
docker history httpd
Remove an image:
docker rmi httpd
4) Container lifecycle (create/start/stop/restart/kill/pause/rm)
Your lifecycle list is correct, and DevOpsSchool’s lab walks through these core commands with variants. (DevOps School)
The difference: run vs create + start
docker run (most common)
Creates + starts (and attaches by default):
docker run -it ubuntu /bin/bash
Detached mode:
docker run -d --name web httpd
docker create then docker start
docker create --name web1 httpd
docker start web1
(DevOpsSchool examples show docker create --name ... and docker start ...) (DevOps School)
Monitor containers
docker ps
docker ps -a
Stop / restart / kill
docker stop web1
docker restart web1
docker kill web1
Pause / unpause
docker pause web1
docker unpause web1
Remove
docker rm web1
5) “Go inside a container” (exec vs attach)
Exec (recommended)
Gets a shell in a running container:
docker exec -it web /bin/bash
For Alpine images use:
docker exec -it alpine1 /bin/sh
Your workflow doc uses docker exec -i -t <id> /bin/bash. (DevOps School)
Attach (less common)
Attaches to the main process’ stdin/stdout:
docker attach <container>
Code language: HTML, XML (xml)
Use carefully; it can disrupt the main process.
6) Access a container from outside (ports + networking)
Port publishing (the normal way)
If your app listens on container port 80, publish it to host port 8080:
docker run -d --name web -p 8080:80 httpd
curl http://localhost:8080
Code language: JavaScript (javascript)
Container IP (good to know, not the usual approach)
Your notes show curling the container IP (172.17.x.x). That works from the host, but it’s not stable for real setups.
Get container IP:
docker inspect web | grep -i ipaddress
Better (clean output):
docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' web
Code language: JavaScript (javascript)
Bridge network basics
Docker creates a default bridge network; containers join it unless you specify otherwise. User-defined bridges are better (DNS by name, better isolation). (Docker Documentation)
List networks:
docker network ls
docker network inspect bridge
Create a user-defined bridge:
docker network create mynet
docker run -d --name web --network mynet nginx
docker run -it --rm --network mynet alpine sh
# inside alpine:
apk add --no-cache curl
curl http://web
Code language: PHP (php)
Name-based resolution on user-defined bridge is a key advantage. (Docker Documentation)
7) Volumes and persistence (missing in your notes, but essential)
Named volume (recommended)
docker volume create webdata
docker run -d --name web -p 8080:80 -v webdata:/usr/local/apache2/htdocs httpd:2.4
Code language: JavaScript (javascript)
Check volumes:
docker volume ls
docker volume inspect webdata
Bind mount (map a host folder)
mkdir -p ~/site
echo "Hello from host mount" > ~/site/index.html
docker run -d --name web -p 8080:80 \
-v ~/site:/usr/local/apache2/htdocs \
httpd:2.4
curl http://localhost:8080
Code language: PHP (php)
8) Build your own image (Dockerfile)
This is the step that turns Docker from “running containers” into “packaging applications”.
Example: simple Python app
Create app.py:
from http.server import BaseHTTPRequestHandler, HTTPServer
class H(BaseHTTPRequestHandler):
def do_GET(self):
self.send_response(200)
self.end_headers()
self.wfile.write(b"Hello from Docker!\n")
HTTPServer(("0.0.0.0", 8000), H).serve_forever()
Code language: CSS (css)
Create Dockerfile:
FROM python:3.12-slim
WORKDIR /app
COPY app.py /app/app.py
EXPOSE 8000
CMD ["python", "app.py"]
Code language: JavaScript (javascript)
Build + run:
docker build -t hello-python:1.0 .
docker run -d --name hello -p 8000:8000 hello-python:1.0
curl http://localhost:8000
Code language: JavaScript (javascript)
Docker’s official workshop shows the same “build image → run container” flow. (Docker Documentation)
9) Docker Compose (multi-container apps)
When you have web + db + cache, Compose is the normal workflow.
Example compose.yaml:
services:
web:
image: nginx:latest
ports:
- "8080:80"
redis:
image: redis:7
Code language: CSS (css)
Run:
docker compose up -d
docker compose ps
docker compose logs -f
docker compose down
10) Troubleshooting + observability (must-have commands)
Logs
docker logs web
docker logs -f web
Resource usage
docker stats
What’s running inside the container
docker top web
docker exec -it web ps aux
Full container configuration (gold command)
docker inspect web
Events stream
docker events
Cleanup
docker system df
docker system prune
docker image prune
docker container prune
docker volume prune
11) Mini labs (practice path)
Lab A: lifecycle drill (based on your history)
docker pull httpd
docker create --name web1 httpd
docker ps -a
docker start web1
docker stop web1
docker restart web1
docker kill web1
docker rm web1
Lab B: exec + inspect + curl
docker run -d --name web -p 8080:80 httpd
docker exec -it web /bin/bash
docker inspect web
curl http://localhost:8080
Code language: JavaScript (javascript)
Lab C: user-defined network DNS
docker network create mynet
docker run -d --name web --network mynet nginx
docker run -it --rm --network mynet alpine sh
# inside:
apk add --no-cache curl
curl http://web
Code language: PHP (php)
12) Quick command cheat sheet (daily use)
Images
docker pull IMAGE[:TAG]
docker images
docker rmi IMAGE
Code language: CSS (css)
Containers
docker run [opts] IMAGE [cmd]
docker ps [-a]
docker stop|start|restart|kill NAME
docker rm NAME
docker exec -it NAME /bin/bash
docker logs [-f] NAME
docker inspect NAME
Networking
docker network ls
docker network create NET
docker run --network NET ...
Volumes
docker volume create VOL
docker run -v VOL:/path ...
docker volume ls
Cleanup
docker system prune