07 — Containers and VMs

Containers and VMs solve two problems for a C engineer: reproducible builds (“works on my machine” is a career-limiting phrase) and cross-platform testing (does your code work on musl? on ARM? on an older glibc?). You don’t need Kubernetes; you need three tools: a Docker runtime, QEMU, and a lightweight Linux VM.

Docker on macOS — Colima (pure-OSS alternative)

Colima is the open-source, CLI-only alternative. Slower startup (~10s), 1-way file sharing, but zero closed-source dependencies.

Install: brew install colima docker. Then colima start.

Use Colima if: you object to closed-source tools or your employer’s security policy blocks OrbStack.

Use OrbStack if: you want the fastest daily-driver experience.

Docker on Linux

On Ubuntu 26.04, install Docker Engine directly: https://docs.docker.com/engine/install/ubuntu/. Native performance, no VM overhead.

Reproducible C builds via Docker

A Dockerfile for a reproducible dev environment:

FROM ubuntu:26.04
RUN apt-get update && apt-get install -y \
    build-essential gcc-15 clang-20 lldb gdb \
    make cmake ninja-build meson pkg-config \
    valgrind mold ccache \
    git ripgrep fd-find bat fzf tmux \
    libssl-dev libcurl4-openssl-dev zlib1g-dev \
 && rm -rf /var/lib/apt/lists/*
WORKDIR /work

Build once (docker build -t c-dev .), then docker run --rm -it -v $(pwd):/work c-dev bash. Every teammate gets the same compiler versions.

The “run it in Alpine” trick — test against musl

Most Linux systems use glibc. Alpine Linux uses musl libc. Musl is stricter about pointer alignment, more careful about errno, and often exposes bugs glibc’s forgiveness hides. Serious C code should be tested against both.

docker run --rm -it -v $(pwd):/work -w /work alpine:3.20 sh
# Inside:
apk add build-base gcc make
gcc -std=c17 -Wall -Wextra -O2 foo.c -o foo
./foo

If your code compiles cleanly on both glibc and musl, and passes ASan on both, you have caught 90% of libc-portability bugs.

QEMU — cross-arch testing

QEMU emulates architectures. You write C on your Apple Silicon Mac and want to test on x86_64, RISC-V, or ARMv7? QEMU handles it.

Two modes:

User-mode QEMU (run a binary from another arch):

# On Apple Silicon Mac, run an x86_64 Linux binary
docker run --rm -it --platform linux/amd64 -v $(pwd):/work ubuntu:26.04 bash
# Docker Desktop / OrbStack handle QEMU under the hood

System-mode QEMU (full VM of another arch):

qemu-system-riscv64 -M virt -kernel bbl -append "..."  # boot a RISC-V kernel

For a C learner in 2026, the --platform linux/amd64 container trick is the 90% use case. Full QEMU is a M12+ topic when you want to boot your own kernel.

Lightweight Linux VMs on Mac

If you want a persistent Ubuntu VM (not a per-run container):

Tool

Pros

Cons

OrbStack VM

2s boot, native macOS integration, shared home dir

Requires OrbStack

Lima (brew install lima)

Free, scriptable, YAML-configurable

Slower than OrbStack

Multipass (brew install multipass)

Canonical’s tool, Ubuntu-focused, easy “launch a VM” workflow

Slower cold boot

UTM.app

Full GUI VM manager, supports x86 emulation on Apple Silicon

Overkill for headless dev

Full VMware Fusion / Parallels

Feature-rich

Paid

Recommendation: OrbStack’s built-in VMs if you’re already using OrbStack for Docker. Otherwise, Lima.

What you don’t need

  • Kubernetes — nope, not for C dev.

  • Docker Swarm — no.

  • Podman — fine, but Docker CLI is what every project’s README assumes; stick with the tribe.

  • Vagrant — solves a 2015 problem; OrbStack/Lima/Multipass are the 2026 answers.

What most people get wrong about containers for C

They think containers are for shipping production, not for development. Flip it: containers are your dev environment’s escape hatch. When something is behaving differently between your Mac and Linux CI, spin an Ubuntu container in 2 seconds and reproduce it. You will use this pattern hundreds of times.

Return to README.md · Next: 08_ai_assist_for_c.md