Docker Fundamentals: Images, Containers and the Docker Daemon (2026)

Docker solves the "works on my machine" problem by packaging an application and everything it needs — runtime, libraries, config — into a single portable unit called a container. Containers share the host OS kernel but are isolated from each other via Linux namespaces and cgroups, giving you near-native performance with VM-like isolation. This phase covers the core mental model: what images and containers are, how the Docker daemon works, and the fundamental commands every developer needs daily.

Architecture Overview

# Docker has three components:
#
# 1. Docker Daemon (dockerd)
#    — Background service that manages images, containers, networks, volumes
#    — Listens on a Unix socket: /var/run/docker.sock
#    — Does the actual work: pulling images, starting containers, etc.
#
# 2. Docker Client (docker CLI)
#    — The command you type: docker run, docker build, docker ps
#    — Sends API calls to the daemon over the socket
#    — Client and daemon can be on different machines (Docker contexts)
#
# 3. Docker Registry
#    — Stores and distributes images
#    — Docker Hub is the default public registry
#    — Private registries: GHCR, ECR, GCR, your own Harbor instance
#
# Flow: docker run nginx
#   Client → API request → Daemon → checks local image cache
#   If not found → pulls from Docker Hub → starts container

# Verify your installation
docker version        # Shows client + daemon versions
docker info           # Full daemon config: storage driver, cgroup, total containers
docker system df      # Disk usage: images, containers, volumes, build cache

Images and Layers

# An image is a read-only template made of stacked layers.
# Each instruction in a Dockerfile creates one layer.
# Layers are content-addressed (SHA256) and shared across images —
# if two images use the same Ubuntu base, that layer is stored once on disk.
#
# Image naming: [registry/][namespace/]name[:tag]
#   nginx             → docker.io/library/nginx:latest
#   node:20-alpine    → docker.io/library/node:20-alpine
#   ghcr.io/org/app:v1.2.3

# Pull an image without running it
docker pull node:20-alpine

# List local images
docker images
# REPOSITORY   TAG         IMAGE ID       CREATED        SIZE
# node         20-alpine   3f9a0c123456   2 weeks ago    133MB

# Inspect image layers
docker history node:20-alpine
# IMAGE          CREATED       CREATED BY                    SIZE
# 3f9a0c123456   2 weeks ago   CMD ["node"]                  0B
# <missing>      2 weeks ago   ENV NODE_VERSION=20.15.0      0B
# ...

# See the full image manifest and config
docker inspect node:20-alpine

# Image ID vs Digest
# ID: short hash of the image config (changes with rebuild)
# Digest: sha256 of the manifest — immutable, use this for production pins
docker pull nginx@sha256:a4c4106...   # Pin to exact digest

Container Lifecycle

# Container states:
#   created  → allocated but never started
#   running  → process is active
#   paused   → SIGSTOP sent, process frozen
#   stopped  → process exited or killed, filesystem still on disk
#   removed  → gone (--rm flag or docker rm)
#
# A container = image layers (read-only) + writable layer (container-specific)
# When a container writes files, they go in the writable layer (copy-on-write)
# When the container is removed, the writable layer is deleted

# Full lifecycle
docker create --name myapp nginx      # Create, don't start
docker start myapp                    # Start it
docker pause myapp                    # Freeze
docker unpause myapp                  # Unfreeze
docker stop myapp                     # SIGTERM → wait 10s → SIGKILL
docker kill myapp                     # SIGKILL immediately
docker rm myapp                       # Delete stopped container
docker rm -f myapp                    # Force-remove running container

# run = create + start (most common)
docker run nginx

# List containers
docker ps            # Running only
docker ps -a         # All (including stopped)
docker ps -q         # Just IDs (useful in scripts)

Running Containers

# Key flags for docker run:

# -d / --detach: run in background
docker run -d nginx

# --name: give it a name (otherwise Docker assigns a random adjective_noun)
docker run -d --name webserver nginx

# -it: interactive terminal (for shells, REPLs)
docker run -it ubuntu bash
docker run -it node:20-alpine node   # Drop into Node REPL

# --rm: auto-remove when stopped (great for one-off tasks)
docker run --rm alpine echo "Hello from Alpine"

# --restart: restart policy
docker run -d --restart unless-stopped nginx
# Policies: no (default), on-failure[:N], always, unless-stopped

# Execute a command in a running container
docker exec -it webserver bash       # Open a shell
docker exec webserver nginx -t       # Test nginx config without shell
docker exec -e DEBUG=1 webserver env # Pass env var to exec command

# Copy files between host and container
docker cp ./config.json webserver:/etc/app/config.json
docker cp webserver:/var/log/nginx/error.log ./error.log

Port Mapping

# Containers have their own network namespace — ports are not accessible from
# the host unless explicitly mapped.
#
# -p hostPort:containerPort
docker run -d -p 8080:80 nginx
# Now: http://localhost:8080 → container port 80

# Map to a specific host IP (e.g., only localhost, not external interfaces)
docker run -d -p 127.0.0.1:8080:80 nginx

# Ephemeral port (Docker picks a free host port)
docker run -d -p 80 nginx
docker port container_name 80   # See which host port was assigned

# Multiple ports
docker run -d \
  -p 8080:80 \
  -p 8443:443 \
  nginx

# UDP port
docker run -d -p 53:53/udp dns-server

# See all port mappings for a container
docker port webserver
# 80/tcp -> 0.0.0.0:8080
# 443/tcp -> 0.0.0.0:8443

# EXPOSE in Dockerfile is documentation only — it doesn't publish ports.
# You still need -p at runtime to access them from the host.

Environment Variables

# Pass configuration to containers via environment variables (12-factor app)

# Single variable
docker run -d -e NODE_ENV=production -e PORT=3000 myapp

# From a file (one KEY=VALUE per line, no quotes)
# .env file:
# NODE_ENV=production
# DATABASE_URL=postgres://...
docker run -d --env-file .env myapp

# Inherit from host shell (no = means: use the value from your shell)
export DB_PASSWORD=secret
docker run -d -e DB_PASSWORD myapp   # Passes $DB_PASSWORD from shell

# View env vars in a running container
docker exec myapp env
docker inspect myapp --format='{{json .Config.Env}}'

# Never bake secrets into images — always inject at runtime via env vars or
# Docker secrets (Swarm) / Kubernetes secrets.

Inspecting Containers

# docker inspect: full JSON metadata about a container or image
docker inspect webserver

# Extract specific fields with --format (Go template)
docker inspect webserver --format='{{.State.Status}}'        # running
docker inspect webserver --format='{{.NetworkSettings.IPAddress}}'  # 172.17.0.2
docker inspect webserver --format='{{json .HostConfig.PortBindings}}'

# Logs
docker logs webserver           # All logs so far
docker logs -f webserver        # Follow (like tail -f)
docker logs --tail 50 webserver # Last 50 lines
docker logs --since 10m webserver  # Last 10 minutes
docker logs -t webserver        # With timestamps

# Resource usage (live)
docker stats                    # All running containers
docker stats webserver          # Single container
# Shows: CPU%, MEM USAGE/LIMIT, MEM%, NET I/O, BLOCK I/O, PIDS

# Top-like process list inside a container
docker top webserver

Cleanup

# Stopped containers, unused images and dangling layers accumulate fast.

# Remove a stopped container
docker rm container_id_or_name

# Remove all stopped containers
docker container prune

# Remove an image
docker rmi nginx:latest
docker rmi image_id

# Remove unused images (not referenced by any container)
docker image prune       # Dangling images only (untagged)
docker image prune -a    # All unused images

# Remove unused volumes
docker volume prune

# Remove unused networks
docker network prune

# Nuclear option — remove everything unused at once
docker system prune
docker system prune -a --volumes   # Also removes unused images and volumes

# Check what you'd free before running prune
docker system df
# TYPE            TOTAL   ACTIVE   SIZE       RECLAIMABLE
# Images          12      3        4.2GB      3.1GB (73%)
# Containers      5       2        124MB      89MB (71%)
# Local Volumes   8       3        2.3GB      1.8GB (78%)
# Build Cache     -       -        890MB      890MB
Next: Phase 2 — Docker CLI Mastery covers the full command toolkit: filtering, formatting output, image tagging, pushing to registries and scripting with docker commands.