Docker

Introduction to Docker

Welcome to the world of Docker - a revolutionary platform transforming the way software is developed, shipped, and run across diverse computing environments. In this introductory guide, we'll embark on a journey to explore the fundamental concepts, capabilities, and benefits of Docker.


What is Docker?

Docker is an open-source platform that enables developers to package applications and their dependencies into lightweight, portable containers. These containers encapsulate everything an application needs to run, including code, runtime, libraries, and system tools. Unlike traditional virtualization methods, Docker containers virtualize the operating system layer, making them highly efficient and portable across different environments.

That difference is the whole story:

       VIRTUAL MACHINES               CONTAINERS
   ┌───────┬───────┬───────┐    ┌───────┬───────┬───────┐
   │  App  │  App  │  App  │    │  App  │  App  │  App  │
   ├───────┼───────┼───────┤    ├───────┼───────┼───────┤
   │  libs │  libs │  libs │    │  libs │  libs │  libs │
   ├───────┼───────┼───────┤    ├───────┴───────┴───────┤
   │ Guest │ Guest │ Guest │    │     Docker Engine     │
   │   OS  │   OS  │   OS  │    ├───────────────────────┤
   ├───────┴───────┴───────┤    │        Host OS        │
   │       Hypervisor      │    ├───────────────────────┤
   ├───────────────────────┤    │        Hardware       │
   │        Host OS        │    └───────────────────────┘
   ├───────────────────────┤
   │        Hardware       │
   └───────────────────────┘

    gigabytes each,              megabytes each,
    boots in minutes             starts in milliseconds
    own kernel                   shares the host kernel

A virtual machine ships an entire guest operating system per application. A container ships only the application and its libraries, and borrows the kernel it is running on. That is why a container starts in the time it takes a process to start - because that is all it is.

Key Components

At the core of Docker is the Docker Engine, a powerful runtime and set of tools that facilitate the creation, deployment, and management of containers. Docker Engine runs on top of the host operating system and orchestrates containerized applications seamlessly.

Docker Images and Containers

Central to Docker's workflow are Docker images and containers. Docker images are read-only templates that serve as the blueprint for containers. They are created using Dockerfiles, which contain instructions for building the image layer by layer. Once an image is built, it can be instantiated into a container - a runnable instance of the image that isolates the application and its dependencies.

   ┌────────────────────────┐
   │ Dockerfile             │
   │                        │
   │ FROM python:3.12-slim  │
   │ COPY requirements.txt  │
   │ RUN  pip install ...   │
   │ COPY app.py            │
   │ CMD  ["python", ...]   │
   └────────────┬───────────┘
                │ docker build

   ┌────────────────────────┐
   │ IMAGE - read-only      │    each instruction here
   │  ┌──────────────────┐  │    becomes one layer of
   │  │ CMD              │  │    the image, and is
   │  ├──────────────────┤  │    rebuilt only when it
   │  │ COPY app.py      │  │    or something below
   │  ├──────────────────┤  │    it changes
   │  │ RUN pip install  │  │
   │  ├──────────────────┤  │
   │  │ python:3.12-slim │  │
   │  └──────────────────┘  │
   └──┬──────────────────┬──┘
      │                  │
      │ docker push      │ docker run
      ▼                  ▼
   ┌──────────────┐   ┌─────────────────────┐
   │ Docker Hub   │   │ CONTAINER           │
   │ (registry)   │   │ the image + a thin  │
   └──────────────┘   │ writable layer      │
                      └─────────────────────┘

The layering matters more than it first appears. Each instruction in a Dockerfile produces a layer, and Docker caches them. Put the instructions that rarely change (installing dependencies) above the ones that change constantly (copying your source), and a rebuild after a one-line code edit takes a second instead of a minute. Get the order backwards and you reinstall every dependency on every build.

Docker Hub

Docker Hub is a cloud-based registry service that hosts a vast repository of Docker images. It provides a centralized platform for developers to discover, share, and collaborate on containerized applications. Docker Hub simplifies the process of finding and distributing Docker images, accelerating the development and deployment of software.

Benefits of Docker

Docker offers numerous advantages for developers, operations teams, and businesses alike. By standardizing the application packaging process, Docker streamlines development workflows, improves resource utilization, and enhances application portability. With Docker, organizations can build, ship, and run applications consistently across diverse environments, from developer laptops to production servers and cloud platforms.


Try it on a Raspberry Pi: where "runs anywhere" meets its limit

Docker's portability promise is real, but it has exactly one important asterisk, and a Raspberry Pi is the cheapest way to run into it. Your laptop is almost certainly x86_64 (or arm64 if it is an Apple Silicon Mac). A Pi is arm64. An image is only portable across machines that share its architecture - so this is the ideal place to learn what that actually means.

Install Docker on the Pi

On a 64-bit Raspberry Pi OS:

curl -fsSL https://get.docker.com | sh

# so you can drop the sudo - log out and back in afterwards
sudo usermod -aG docker $USER

docker run --rm hello-world

Meet the architecture trap

Before building anything, provoke the error deliberately - it is much less confusing when you have seen it on purpose:

# arch of the Pi itself
uname -m                      # aarch64

# a normal, multi-arch image: works fine
docker run --rm alpine uname -m

# force the x86 build of the same image on an ARM machine
docker run --rm --platform linux/amd64 alpine uname -m
#  exec /bin/uname: exec format error

That exec format error is the single most common surprise when moving containers between a laptop and a Pi. The image pulled perfectly - it just contains binaries the CPU cannot run.

Build something Pi-specific

Here is a small app that reports the Pi's own CPU temperature. It is worth building precisely because of how it works: the container has no special privileges, yet it can read the host's thermal sensor - because a container shares the host kernel rather than virtualising one.

# app.py
from flask import Flask

app = Flask(__name__)

def cpu_temp():
    with open("/sys/class/thermal/thermal_zone0/temp") as f:
        return int(f.read()) / 1000

@app.route("/")
def index():
    return {"cpu_temp_c": round(cpu_temp(), 1)}

app.run(host="0.0.0.0", port=8000)
# requirements.txt
flask

The Dockerfile, ordered so the dependency layer stays cached:

FROM python:3.12-slim

WORKDIR /app

# Dependencies first. This layer is only rebuilt when
# requirements.txt changes - not on every code edit.
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# Source last, because it changes constantly
COPY app.py .

EXPOSE 8000
CMD ["python", "app.py"]

Build and run it directly on the Pi:

docker build -t pitemp .
docker run -d --name pitemp -p 8000:8000 pitemp

curl http://localhost:8000
#  {"cpu_temp_c":47.8}

Now run docker history pitemp and you will see the layers from the diagram above, with their individual sizes.

Building for the Pi from your laptop

Building on the Pi works but is slow. buildx cross-builds for arm64 on your laptop and pushes a multi-arch image - one tag that serves the right binaries to whichever machine pulls it:

docker buildx create --use            # once

docker buildx build \
  --platform linux/amd64,linux/arm64 \
  -t youruser/pitemp:latest \
  --push .

Then on the Pi, docker pull youruser/pitemp fetches the arm64 variant automatically. This is what "multi-arch" means on Docker Hub, and why alpine worked earlier while the forced --platform did not.

Keep it running with Compose

A single docker run does not survive a reboot. This is the version you actually want on a Pi doing a job:

# compose.yml
services:
  pitemp:
    image: youruser/pitemp:latest
    ports:
      - "8000:8000"
    restart: unless-stopped
    deploy:
      resources:
        limits:
          memory: 128M
docker compose up -d
docker compose logs -f

Pi-specific gotchas

  • Not every image has an arm64 build. Check the tags on Docker Hub before you plan around an image. Older or niche images are frequently amd64-only.
  • Docker writes a lot. Images, layers and logs all land on the SD card. Boot from an SSD if you can, and cap log growth in /etc/docker/daemon.json with "log-opts": {"max-size": "10m"}.
  • Set memory limits. A container with no limit can take the whole Pi down. The limits block above costs nothing to add.
  • GPIO needs to be passed in. Containers do not see hardware by default. Reading /sys works as shown, but toggling pins needs --device /dev/gpiomem - grant the narrowest access that works rather than reaching for --privileged.
  • docker system prune is your friend. Old layers accumulate quietly and a Pi has nowhere to hide them.

Getting Started

Whether you're a seasoned developer or new to containerization, Docker provides a user-friendly experience for building and deploying applications. In the upcoming sections, we'll delve deeper into Docker's features, best practices, and real-world use cases to empower you on the Docker journey.

Previous
TypeScript 7 - the compiler went native