What containers actually are under the hood — namespaces, cgroups, Union FS. Images vs containers. Docker architecture. The lifecycle. Week 4 starts here: understand what you've been running.
| Feature | VM | Container |
|---|---|---|
| OS | Full Guest OS | Shared host kernel |
| Size | 1–40 GB | 5 MB–500 MB |
| Startup | 30–60 sec | <1 sec |
| Isolation | Full (hardware) | Process (namespace) |
| Overhead | High | Near-zero |
eth0, IP address, port space./ (the image layers). Cannot see host filesystem.docker run -d --name test nginx:alpinedocker exec test ps aux — sees PID 1ps aux | grep nginx — host sees real PID
# Set memory limit — OOM killed if exceeded docker run -d \ --name limited-app \ --memory 512m \ --memory-swap 512m \ # no swap --cpus 1.5 \ # 1.5 CPU cores max --cpu-shares 512 \ # relative weight (default 1024) nginx:alpine # Verify limits are applied docker inspect limited-app | grep -A5 '"Memory"' # "Memory": 536870912 ← 512 * 1024 * 1024 bytes # See live resource usage docker stats limited-app # CONTAINER CPU % MEM USAGE / LIMIT # limited-app 0.1% 8MiB / 512MiB # See cgroup files directly (Linux only) cat /sys/fs/cgroup/memory/docker/<container-id>/memory.limit_in_bytes # 536870912 ← Docker wrote this file! # In Kubernetes — same mechanism: # resources: # limits: # memory: "512Mi" # cpu: "1500m" # → K8s translates these to cgroup files
--cpus 1.5: max 1.5 cores. --cpu-shares 512: relative weight when competing for CPU.--memory 512m: hard limit. Process is OOM-killed if exceeded.cgroups = files in /sys/fs/cgroup/. Docker writes these files when you do docker run --memory. The kernel reads them to enforce limits.
--memory, a container can consume all host RAM, starving other containers. In Kubernetes: always set resources.limits.memory. Without it, a single runaway pod can OOM the entire node.
node:20-alpine, the base layer is stored only once on disk.docker pull only downloads layers you don't have. Already have layer 1+2? Only layer 3+4 download.# See image layers docker history node:20-alpine docker history my-devops-app:local # See layer sizes docker image inspect my-devops-app:local \ --format '{{json .RootFS.Layers}}' # Check disk usage docker system df # total Docker disk usage docker system df -v # verbose — per image/container
# === Images — read-only blueprints === docker images # list all local images docker pull nginx:alpine # pull from registry docker image inspect nginx:alpine # full metadata docker history nginx:alpine # show layers docker rmi nginx:alpine # remove image docker image prune # remove dangling images # One image → multiple containers docker run -d -p 8080:80 --name web-1 nginx:alpine docker run -d -p 8081:80 --name web-2 nginx:alpine docker run -d -p 8082:80 --name web-3 nginx:alpine # 3 containers, 1 image, ~zero extra disk space # === Containers — running instances === docker ps # running containers docker ps -a # all (inc. stopped) docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}" # Container lifecycle docker stop web-1 # SIGTERM → SIGKILL after 10s docker start web-1 # restart docker restart web-1 # stop + start docker pause web-1 # freeze (cgroup freezer) docker unpause web-1 # unfreeze docker kill web-1 # immediate SIGKILL docker rm web-1 # delete (must be stopped) docker rm -f web-1 # force delete (stop+rm) docker rm $(docker ps -aq) # remove all stopped
created → running → paused
↑ ↓
start pause
↓ ↓
stopped ← unpause
↓
removed
Stopped containers still exist on disk (docker ps -a). They consume disk space but not CPU/memory. Always docker rm containers you no longer need.
/var/run/docker.sock).clone() and unshare() system calls to create namespaces and cgroups.Pull, run, exec, inspect layers, volumes, container lifecycle — see namespaces and cgroups in action
docker run -d -p 8080:80 --name my-web nginx:alpine → curl localhost:8080. See the welcome page. Containers serving real traffic in under 1 second.docker exec -it my-web sh → ps aux inside (sees PID 1). Exit. ps aux | grep nginx on host (sees real PID). Same process, two different views.docker image history nginx:alpine — see each layer, its size and the instruction that created it. docker system df — total Docker disk usage.docker run -d --name limited --memory 100m --cpus 0.5 nginx:alpine → docker stats limited. See the MEM LIMIT column showing your limit.mkdir ~/html && echo "<h1>My page</h1>" > ~/html/index.html. Run nginx with -v ~/html:/usr/share/nginx/html. Hit port 8081 — see your custom page.docker stop my-web → docker ps -a (still exists) → docker start my-web → docker rm -f my-web. Clean up all lab containers.# === 1. Run nginx === docker run -d -p 8080:80 --name my-web nginx:alpine curl http://localhost:8080 # <!DOCTYPE html> ... Welcome to nginx! # === 2. Namespaces demo === docker exec -it my-web sh # Inside container: ps aux # PID 1 nginx: master process # PID 7 nginx: worker process hostname # random container ID exit # On host — same process, different PID ps aux | grep nginx # bastianjoe 4521 nginx: master (real PID!) # === 3. Inspect layers === docker image history nginx:alpine # IMAGE CREATED CREATED BY SIZE # <hash> ... /bin/sh -c #(nop) CMD 0B # <hash> ... COPY file... 1.7kB # <hash> ... RUN /bin/sh -c ... 4.1MB docker system df # TYPE TOTAL ACTIVE SIZE RECLAIMABLE # Images 3 1 28.5MB 22MB # Containers 1 1 0B 0B # === 4. cgroup resource limits === docker run -d --name limited \ --memory 100m \ --cpus 0.5 \ nginx:alpine docker stats limited --no-stream # CONTAINER CPU% MEM USAGE / LIMIT # limited 0.0% 4.1MiB / 100MiB ← our limit!
# === 5. Volume mount === mkdir -p ~/html cat > ~/html/index.html << 'EOF' <h1>Hello from Day 16!</h1> <p>This file lives on the HOST machine.</p> EOF docker run -d \ -p 8081:80 \ --name my-custom-web \ -v ~/html:/usr/share/nginx/html:ro \ nginx:alpine curl http://localhost:8081 # <h1>Hello from Day 16!</h1> # Edit the host file — container sees changes! echo "<h1>Updated!</h1>" > ~/html/index.html curl http://localhost:8081 # <h1>Updated!</h1> ← instant update! # === 6. Full lifecycle cleanup === docker stop my-web docker ps -a # still exists (stopped) docker start my-web curl http://localhost:8080 # back online # Remove everything docker rm -f my-web my-custom-web limited docker ps -a # empty! # Clean up unused images docker image prune # remove dangling images docker system prune # remove all unused
-v ~/html:/container/path = bind mount (host path)-v myvolume:/container/path = named volume (Docker managed)3 questions · 5 minutes · image vs container, cgroups, exec command
my-app?docker run -it my-app shdocker attach my-appdocker exec -it my-app shdocker shell my-appcurl localhost:8080 works ✓docker stats limited shows 100MiB limit ✓| Namespace | Isolates | Without it | Docker flag |
|---|---|---|---|
| PID | Process IDs | Containers see all host processes | --pid=host (share) |
| NET | Network interfaces, routing | Containers share host network stack | --network=host |
| MNT | Filesystem mount points | Container sees entire host filesystem | -v /:/host (bind) |
| UTS | Hostname, domain name | Container has host's hostname | --hostname=name |
| IPC | Shared memory, semaphores | Containers share IPC resources | --ipc=host |
| USER | User/group IDs | root in container = root on host | --userns=host |
--network=host means the container has full access to the host network stack — any port the container binds is bound on the host. Avoid in production unless you have a specific reason.
# Run docker run nginx:alpine # foreground docker run -d nginx:alpine # detached (background) docker run -it alpine sh # interactive docker run --rm nginx:alpine # auto-remove on exit docker run -p 8080:80 # port mapping docker run -v /host:/cont # volume docker run -e KEY=value # env var docker run --name myapp # named container docker run --memory 512m # memory limit docker run --cpus 1.5 # CPU limit docker run --restart always # auto-restart # Lifecycle docker stop myapp # graceful docker kill myapp # immediate docker start myapp # restart stopped docker restart myapp # stop + start docker pause myapp # freeze docker rm myapp # delete stopped docker rm -f myapp # force delete # Inspect docker logs myapp # stdout/stderr docker logs -f myapp # follow docker logs --tail 50 myapp # last 50 lines docker stats # live resource usage docker top myapp # processes in container docker inspect myapp # full JSON metadata docker exec -it myapp sh # interactive shell docker cp myapp:/file . # copy file out
# Images docker images # list docker pull nginx:alpine # pull docker push ghcr.io/user/app # push docker build -t app:1.0 . # build docker tag app:1.0 app:latest # tag docker rmi app:1.0 # remove docker history app:1.0 # show layers docker inspect app:1.0 # metadata docker save app:1.0 > app.tar # export docker load < app.tar # import # System docker system df # disk usage docker system prune # remove unused docker system prune -a # remove all unused docker image prune # dangling images docker container prune # stopped containers docker volume prune # unused volumes docker network prune # unused networks docker info # Docker system info docker version # client + server # Volumes docker volume create mydata docker volume ls docker volume inspect mydata docker volume rm mydata docker run -v mydata:/app/data app
| Day | Topic | Key Skills | Status |
|---|---|---|---|
| Day 16 ✅ | Docker Fundamentals | Namespaces, cgroups, Union FS, architecture, lifecycle | ✅ |
| Day 17 | Writing Dockerfiles | Layer caching, bad→good, .dockerignore, distroless | Tomorrow |
| Day 18 | Docker Compose | Multi-container stacks, networking, health checks | Wed |
| Day 19 | Container Networking | Bridge, host, overlay networks, DNS resolution | Thu |
| Day 20 | Container Security | Trivy scanning, rootless containers, signing | Fri |
docker build + docker push in CI without knowing what happens inside. Week 4: you now understand every step — namespace creation, cgroup files, layer mounting, containerd snapshots. Week 5: run these containers at scale in Kubernetes.
docker run -d -p -v --memory --cpus docker exec -it <name> sh docker logs -f <name> docker stats <name> docker image history <image> docker system df docker stop/start/rm/rm -f
| Problem | Cause | Fix |
|---|---|---|
| Container exits immediately | App crashes on startup | docker logs container-name to see the crash output. |
| "port already in use" | Another process using that port | Use different host port: -p 8081:80. Or find and stop the conflicting process. |
| Container killed — OOMKilled | Exceeded memory limit | Increase memory: --memory 1g. Or fix memory leak in app. |
| "Cannot connect to Docker daemon" | Docker Desktop not running | Start Docker Desktop. Wait for whale icon in taskbar. Then retry. |
| "Permission denied" on volume | Container user ≠ host file owner | Add --user $(id -u):$(id -g) or use chmod 755 on the mounted directory. |
| Container running but app unreachable | Forgot -p flag or wrong port | docker ps — check PORTS column. EXPOSE doesn't publish; only -p does. |
| Low disk space | Accumulated images and stopped containers | docker system prune -a reclaims unused space. Check with docker system df. |
| Changes lost after container restart | Data in writable container layer | Use volumes for persistent data: -v mydata:/app/data. Container layer is ephemeral. |
# Container internals Namespace = isolation (what you see) cgroups = limits (how much you get) OverlayFS = layered filesystem # Container is a PROCESS, not a VM docker run nginx → ps aux | grep nginx # PID 1 inside = PID 4521 outside # Image → Container relationship docker run -d -p 8080:80 nginx:alpine # run-1 docker run -d -p 8081:80 nginx:alpine # run-2 docker run -d -p 8082:80 nginx:alpine # run-3 # 3 containers · 1 image · minimal extra disk # Essential daily commands docker ps # running? docker ps -a # all states docker logs -f myapp # debug docker exec -it myapp sh # inspect docker stats # resources docker stop myapp # graceful docker rm -f myapp # force clean # Resource limits docker run --memory 512m --cpus 1.5 app # Writes: /sys/fs/cgroup/memory/.../limit # Kubernetes: resources.limits.memory: "512Mi"
Namespaces — isolation
cgroups — limits
--memory 512m → kernel file → OOM killerOverlayFS — layers
docker run nginx: