Post

Docker Full Walkthrough

Full Docker walkthrough — containers, images, volumes, networks, Dockerfile, and Docker Compose from the ground up

Docker Full Walkthrough

Docker Full Walkthrough


What is Docker?

Docker is a containerisation platform. Instead of shipping your app + a full VM, you ship a container — a lightweight, isolated process that packages your app and its dependencies but shares the host OS kernel.

Key concepts:

TermWhat it is
ImageRead-only blueprint (like a class)
ContainerRunning instance of an image (like an object)
DockerfileRecipe to build an image
RegistryRemote store for images (Docker Hub, GHCR, ECR)
VolumePersistent storage that outlives containers
NetworkVirtual network connecting containers

Installation

Linux (Ubuntu/Debian)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# Remove old versions
sudo apt remove docker docker-engine docker.io containerd runc

# Install via convenience script
curl -fsSL https://get.docker.com | sh

# Add your user to docker group (no sudo needed after logout/login)
sudo usermod -aG docker $USER

# Start and enable the daemon
sudo systemctl enable --now docker

# Verify
docker --version
docker run hello-world

Check installation

1
2
3
docker version               # client + server version
docker info                  # system-wide info (storage driver, network, etc.)
docker system df             # disk usage by images, containers, volumes

Images

Pulling images

1
2
3
4
docker pull ubuntu                    # latest tag
docker pull ubuntu:22.04              # specific version
docker pull nginx:alpine              # alpine variant (smaller)
docker pull ghcr.io/user/myapp:v1.2  # from GitHub Container Registry

Listing and inspecting images

1
2
3
4
5
6
docker images                         # list local images
docker images -a                      # include intermediate layers
docker image ls --format "table {{.Repository}}\t{{.Tag}}\t{{.Size}}"

docker inspect ubuntu:22.04           # full JSON metadata
docker history nginx:alpine           # layers that built the image

Removing images

1
2
3
4
docker rmi nginx                      # remove by name
docker rmi abc123                     # remove by image ID
docker image prune                    # remove dangling (untagged) images
docker image prune -a                 # remove all unused images

Containers

Running containers

1
2
3
4
5
6
7
8
9
docker run nginx                               # foreground, blocking
docker run -d nginx                            # detached (background)
docker run -d -p 8080:80 nginx                 # map host:8080 → container:80
docker run -d --name webserver nginx           # give it a name
docker run -it ubuntu:22.04 bash               # interactive terminal
docker run --rm alpine echo "hello"            # remove container after it exits
docker run -e MY_VAR=hello nginx               # set environment variable
docker run -v /host/path:/container/path nginx # bind mount a volume
docker run --memory="256m" --cpus="1.0" nginx  # resource limits

Listing containers

1
2
3
4
docker ps                     # running containers
docker ps -a                  # all containers (including stopped)
docker ps -q                  # only container IDs (useful for scripting)
docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}"

Managing container lifecycle

1
2
3
4
5
6
7
8
docker stop webserver          # graceful stop (SIGTERM → wait → SIGKILL)
docker start webserver         # start a stopped container
docker restart webserver       # stop + start
docker kill webserver          # immediate kill (SIGKILL)
docker pause webserver         # freeze (SIGSTOP)
docker unpause webserver       # resume
docker rm webserver            # delete stopped container
docker rm -f webserver         # force delete (even if running)

Interacting with running containers

1
2
3
4
5
6
7
8
9
docker exec -it webserver bash              # open a shell inside a running container
docker exec webserver cat /etc/nginx.conf   # run a single command
docker logs webserver                       # stdout/stderr logs
docker logs -f webserver                    # follow logs (like tail -f)
docker logs --tail 50 webserver             # last 50 lines
docker logs --since 1h webserver            # logs from the last hour
docker top webserver                        # processes running inside container
docker stats                                # live resource usage (CPU, RAM, net, disk)
docker stats --no-stream                    # one-shot snapshot

Copying files

1
2
docker cp file.txt webserver:/tmp/file.txt   # host → container
docker cp webserver:/etc/nginx.conf ./       # container → host

Dockerfile

A Dockerfile is a script that builds an image layer by layer.

Minimal example

1
2
3
FROM ubuntu:22.04
RUN apt-get update && apt-get install -y curl
CMD ["bash"]

Full example with best practices

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# --- Build stage ---
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production

# --- Runtime stage ---
FROM node:20-alpine
WORKDIR /app

# Create non-root user
RUN addgroup -S appgroup && adduser -S appuser -G appgroup

COPY --from=builder /app/node_modules ./node_modules
COPY . .

# Set ownership
RUN chown -R appuser:appgroup /app

USER appuser
EXPOSE 3000
ENV NODE_ENV=production

HEALTHCHECK --interval=30s --timeout=5s --retries=3 \
    CMD wget -qO- http://localhost:3000/health || exit 1

CMD ["node", "server.js"]

Dockerfile instructions

InstructionPurpose
FROMBase image (every Dockerfile starts with this)
RUNExecute a command at build time
COPYCopy files from host into the image
ADDLike COPY but also handles URLs and tar extraction
WORKDIRSet working directory for subsequent instructions
ENVSet environment variables
ARGBuild-time variable (not available at runtime)
EXPOSEDocument which port the container listens on
CMDDefault command when container starts (can be overridden)
ENTRYPOINTMain command (CMD becomes arguments to it)
USERSwitch to a non-root user
VOLUMEDeclare a mount point for volumes
HEALTHCHECKDefine how Docker checks if the container is healthy
LABELAdd metadata key-value pairs

CMD vs ENTRYPOINT

1
2
3
4
5
6
7
8
9
# CMD alone — fully overridden by docker run args
CMD ["nginx", "-g", "daemon off;"]

# ENTRYPOINT alone — not overridden, docker run args appended
ENTRYPOINT ["nginx"]

# Combined — ENTRYPOINT is the executable, CMD is default args
ENTRYPOINT ["nginx"]
CMD ["-g", "daemon off;"]

Build an image

1
2
3
4
docker build -t myapp:latest .                    # build from current directory
docker build -t myapp:v1.0 -f Dockerfile.prod .  # specify Dockerfile
docker build --no-cache -t myapp:latest .         # ignore layer cache
docker build --build-arg VERSION=1.2 -t myapp .  # pass build args

Multi-stage builds

Multi-stage builds keep the final image small by not including build tools:

1
2
3
4
5
6
7
8
FROM golang:1.22 AS builder
WORKDIR /src
COPY . .
RUN go build -o /bin/app

FROM scratch              # empty base image
COPY --from=builder /bin/app /app
CMD ["/app"]

The final image contains only the compiled binary — no Go toolchain.


Volumes

Volumes persist data beyond the container’s lifecycle and are managed by Docker.

Types of storage

TypeDescription
VolumeManaged by Docker, stored in /var/lib/docker/volumes/
Bind mountMaps a host path directly into the container
tmpfs mountIn-memory only, not persisted

Volume commands

1
2
3
4
5
docker volume create mydata              # create a named volume
docker volume ls                         # list volumes
docker volume inspect mydata            # details (mount point, driver)
docker volume rm mydata                 # delete a volume
docker volume prune                     # delete all unused volumes

Using volumes

1
2
3
4
5
6
7
8
9
10
11
# Named volume (recommended)
docker run -d -v mydata:/var/lib/postgresql/data postgres

# Bind mount (good for dev — live code reload)
docker run -d -v $(pwd)/src:/app/src myapp

# Read-only bind mount
docker run -d -v $(pwd)/config:/etc/app:ro myapp

# tmpfs (in-memory, for secrets or temp data)
docker run --tmpfs /tmp:size=100m myapp

Networking

Default networks

1
2
docker network ls                           # list all networks
docker network inspect bridge              # inspect the default bridge network
NetworkDescription
bridgeDefault. Containers talk via IP, isolated from host
hostContainer shares host’s network stack (no isolation)
noneNo networking
Custom bridgeLike bridge but with automatic DNS by container name

Custom networks (always use these)

1
2
3
4
docker network create mynet                         # create a bridge network
docker network create --driver bridge --subnet 172.20.0.0/16 mynet
docker network rm mynet                             # delete
docker network prune                                # remove unused networks

Connect containers to a network:

1
2
docker run -d --network mynet --name db postgres
docker run -d --network mynet --name app myapp

On a custom bridge network, containers can reach each other by name (db, app) — no need for IPs.

1
2
docker network connect mynet webserver    # add a running container to a network
docker network disconnect mynet webserver # remove it

Port publishing

1
2
3
4
docker run -p 8080:80 nginx             # host:8080 → container:80
docker run -p 127.0.0.1:8080:80 nginx  # bind only to localhost
docker run -P nginx                     # auto-assign host ports for all EXPOSE'd ports
docker port webserver                   # show port mappings

Docker Compose

Docker Compose manages multi-container applications with a YAML file.

docker-compose.yml structure

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
services:
  db:
    image: postgres:16-alpine
    environment:
      POSTGRES_USER: appuser
      POSTGRES_PASSWORD: secret
      POSTGRES_DB: myapp
    volumes:
      - pgdata:/var/lib/postgresql/data
    restart: unless-stopped

  backend:
    build: ./backend
    environment:
      DATABASE_URL: postgresql://appuser:secret@db:5432/myapp
    ports:
      - "8000:8000"
    depends_on:
      db:
        condition: service_healthy
    restart: unless-stopped

  nginx:
    image: nginx:alpine
    ports:
      - "80:80"
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf:ro
    depends_on:
      - backend

volumes:
  pgdata:

Docker Compose commands

1
2
3
4
5
6
7
8
9
10
11
12
13
14
docker compose up                   # start all services (foreground)
docker compose up -d                # start in background
docker compose up --build           # force rebuild images
docker compose down                 # stop and remove containers
docker compose down -v              # also remove volumes
docker compose ps                   # status of all services
docker compose logs                 # all service logs
docker compose logs -f backend      # follow a specific service
docker compose exec backend bash    # open shell in a service
docker compose run backend pytest   # run a one-off command
docker compose pull                 # pull latest images
docker compose restart backend      # restart a specific service
docker compose stop                 # stop without removing
docker compose start                # start stopped services

Environment variables in Compose

1
2
3
4
5
6
7
8
9
10
11
12
# Inline
environment:
  DEBUG: "true"
  PORT: "8000"

# From .env file (auto-loaded from same directory)
env_file:
  - .env

# Substitute from shell
environment:
  API_KEY: ${API_KEY}

Healthchecks in Compose

1
2
3
4
5
6
7
8
9
services:
  db:
    image: postgres:16
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U appuser"]
      interval: 10s
      timeout: 5s
      retries: 5
      start_period: 30s

Docker Registry

Docker Hub

1
2
3
4
5
docker login                               # login to Docker Hub
docker login ghcr.io                       # login to GitHub Container Registry
docker tag myapp:latest username/myapp:latest
docker push username/myapp:latest          # push to Docker Hub
docker pull username/myapp:latest          # pull from Docker Hub

Run a local registry

1
2
3
4
5
6
7
8
docker run -d -p 5000:5000 --name registry registry:2

# Tag and push to local registry
docker tag myapp localhost:5000/myapp:latest
docker push localhost:5000/myapp:latest

# Pull from local registry
docker pull localhost:5000/myapp:latest

System Cleanup

Docker accumulates a lot of disk space over time.

1
2
3
4
5
6
7
8
9
docker system df                   # show disk usage breakdown
docker system prune                # remove stopped containers, unused networks, dangling images
docker system prune -a             # also remove unused images (not just dangling)
docker system prune -a --volumes   # everything including volumes (destructive!)

docker container prune             # remove all stopped containers
docker image prune -a              # remove all unused images
docker volume prune                # remove unused volumes
docker network prune               # remove unused networks

Dockerfile Best Practices

Layer caching — Docker caches each layer. Put things that change least at the top:

1
2
3
4
5
6
7
8
# Good — dependencies change less often than source code
COPY package.json ./
RUN npm install
COPY . .              # source code copied last

# Bad — cache busted on every source change
COPY . .
RUN npm install

Combine RUN commands to reduce layers:

1
2
3
4
5
6
7
8
9
# Bad — 3 layers
RUN apt-get update
RUN apt-get install -y curl
RUN rm -rf /var/lib/apt/lists/*

# Good — 1 layer
RUN apt-get update \
    && apt-get install -y curl \
    && rm -rf /var/lib/apt/lists/*

Use .dockerignore to exclude files from build context:

1
2
3
4
5
node_modules
.git
.env
*.log
dist

Never run as root in production — always add and switch to a non-root user.

Use specific tags — never FROM ubuntu:latest in production. Pin to ubuntu:22.04.


Quick Reference

TaskCommand
Pull an imagedocker pull nginx:alpine
Run a containerdocker run -d -p 8080:80 nginx
Open a shelldocker exec -it name bash
View logsdocker logs -f name
Stop containerdocker stop name
Remove containerdocker rm name
List imagesdocker images
Remove imagedocker rmi nginx
Build imagedocker build -t myapp .
Push imagedocker push user/myapp
Create volumedocker volume create mydata
Create networkdocker network create mynet
Compose updocker compose up -d
Compose logsdocker compose logs -f
Compose downdocker compose down
Clean everythingdocker system prune -a
Disk usagedocker system df
This post is licensed under CC BY 4.0 by the author.