04 — Docker, OrbStack, Colima, and Dev Containers on Apple Silicon (2026)¶
Why containers matter in your roadmap. Phase 4 (Build/Test/Tooling) and Phase 6 (Production Systems) both require a reproducible Linux C++ build environment that matches CI. macOS-native builds are useful but the industry ships Linux binaries. Containers close the gap without a second machine.
§1. The 2026 landscape on Apple Silicon (verified July 2026)¶
Tool |
Type |
License |
Native ARM64? |
RAM idle |
Speed vs Docker Desktop |
Verdict |
|---|---|---|---|---|---|---|
Docker Desktop |
GUI + engine |
Free personal / paid biz |
Yes |
~2 GB |
1.0× baseline |
Default; works but heavy |
OrbStack |
GUI + engine |
Paid (~$8/mo personal) |
Yes |
~200 MB |
~1.7× faster boot, ~1.7× more power-efficient |
Recommended if you can pay |
Colima |
CLI only |
Free, OSS |
Yes |
~500 MB |
~1.1× |
Best free alternative |
Apple |
CLI, WWDC 2025+ |
Free (Apple) |
Yes |
~100 MB |
Fast but limited |
Too new for daily use |
Rancher Desktop |
GUI |
Free |
Yes |
~1 GB |
~1.0× |
Fine, k8s-focused |
Lima |
CLI, VM-only |
Free |
Yes |
Variable |
N/A (no Docker default) |
Skip unless you’re a Lima user |
Practical decision for Raghul: if the ~₹700/month for OrbStack is justifiable, that’s the best time-saver. If not, Colima is a fully capable free path. Docker Desktop’s free personal tier also works and is the most vanilla choice.
Do NOT pick Apple’s native container yet — as of July 2026 it lacks
some docker-compose features and Dev Containers integration is
incomplete.
§2. Recommended: OrbStack install (5 min)¶
brew install --cask orbstack
Launch OrbStack from Spotlight. On first launch:
Grant it network + filesystem permissions.
It provides a
dockercommand that shadows any Docker Desktop CLI.Verify:
docker --version # expected: Docker version 27.x or newer (as shipped by OrbStack) docker run --rm hello-world # pulls hello-world image, prints greeting
Optional but useful: enable Kubernetes in OrbStack settings (adds ~10 s to launch, but you get a working local k8s for free — useful in Phase 6).
OrbStack Terms of Use (2026): free for personal non-commercial use with usage-based nagging above certain container counts; paid tier removes that and adds features. Zoho use requires the paid tier.
§3. Alternative: Colima install (free path, 5 min)¶
brew install colima docker docker-buildx docker-compose
# Start the Colima VM (Ubuntu 24.04 by default in 2026)
colima start --arch aarch64 --cpu 4 --memory 8 --disk 60
# Confirm the docker client sees Colima's VM
docker context ls
# expected: colima (current)
docker run --rm hello-world
Notes:
--memory 8allocates 8 GB to the Linux VM. On a 16 GB Mac, do NOT exceed this. On a 32 GB M-Pro/Max,--memory 16is comfortable.Colima persists across reboots via
colima start. Addalias k='colima start && docker ps'to~/.zshrcif you use it daily.Colima’s Docker plugin support: buildx works, compose works, but networking is slightly less polished than OrbStack for cross-container DNS.
§4. Minimal Dockerfile for a Linux C++20/23 build env¶
Put this at the root of any project where you want CI parity:
# Dockerfile — C++20/23 dev environment matching modern CI (July 2026 baseline)
# Ubuntu 24.04 LTS (Noble Numbat) is the current baseline; 26.04 (LTS Apr 2026)
# is also viable but had late-2025 apt-mirror quirks — stick with 24.04 through
# Phase 6 unless you have a reason.
FROM ubuntu:24.04
ENV DEBIAN_FRONTEND=noninteractive \
TZ=Asia/Kolkata \
LANG=en_US.UTF-8 \
LC_ALL=en_US.UTF-8
# LLVM apt repository (Kitware LLVM builds are more current than Ubuntu's)
RUN apt-get update && apt-get install -y --no-install-recommends \
ca-certificates curl gnupg lsb-release software-properties-common \
wget xz-utils \
&& rm -rf /var/lib/apt/lists/*
# Add LLVM apt repo (llvm.org/apt) — pins to LLVM 20 as of July 2026 baseline.
# Bump to 21 when Ubuntu 24.04 packages catch up.
RUN wget -qO- https://apt.llvm.org/llvm-snapshot.gpg.key \
| gpg --dearmor -o /usr/share/keyrings/llvm.gpg \
&& echo "deb [signed-by=/usr/share/keyrings/llvm.gpg] http://apt.llvm.org/noble/ llvm-toolchain-noble-20 main" \
> /etc/apt/sources.list.d/llvm.list
# Kitware CMake apt repo (more current than distro-cmake)
RUN wget -qO- https://apt.kitware.com/keys/kitware-archive-latest.asc \
| gpg --dearmor -o /usr/share/keyrings/kitware.gpg \
&& echo "deb [signed-by=/usr/share/keyrings/kitware.gpg] https://apt.kitware.com/ubuntu/ noble main" \
> /etc/apt/sources.list.d/kitware.list
RUN apt-get update && apt-get install -y --no-install-recommends \
clang-20 clang-tidy-20 clang-format-20 clangd-20 lld-20 lldb-20 \
libc++-20-dev libc++abi-20-dev \
cmake ninja-build \
git gh \
python3 python3-pip python3-venv pipx \
pkg-config \
gdb valgrind strace ltrace \
linux-tools-common \
ripgrep fd-find fzf jq bat \
zsh sudo \
&& rm -rf /var/lib/apt/lists/*
# Symlink LLVM-versioned binaries to unversioned names
RUN for tool in clang clang++ clang-tidy clang-format clangd lld lldb; do \
ln -sf /usr/bin/${tool}-20 /usr/local/bin/${tool}; \
done
# Install Conan 2 via pipx (isolated, upgradeable)
RUN pipx install conan --global \
&& conan --version
# Install uv (Astral's fast Python manager) — Phase 5
RUN curl -LsSf https://astral.sh/uv/install.sh | sh -s -- --no-modify-path \
&& mv /root/.local/bin/uv /usr/local/bin/uv \
&& uv --version
# Create a non-root user matching macOS UID for bind-mount friendliness
ARG USER_ID=1000
ARG GROUP_ID=1000
RUN groupadd -g ${GROUP_ID} dev \
&& useradd -m -u ${USER_ID} -g ${GROUP_ID} -s /bin/zsh dev \
&& echo "dev ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers
USER dev
WORKDIR /workspace
# Force libc++ for parity with brew LLVM on macOS
ENV CC=/usr/local/bin/clang \
CXX=/usr/local/bin/clang++ \
CXXFLAGS="-stdlib=libc++" \
LDFLAGS="-stdlib=libc++ -lc++abi"
CMD ["/bin/zsh"]
Build and run:
docker build -t cxx-dev:2026 .
docker run --rm -it -v "$(pwd):/workspace" cxx-dev:2026
# inside container:
clang++ --version
# expected: Ubuntu clang version 20.x.x
cmake --version
# expected: cmake version 3.31.x or newer (Kitware apt)
§5. VSCode Dev Containers — the reproducible-env workflow¶
Extension: ms-vscode-remote.remote-containers. Install:
code --install-extension ms-vscode-remote.remote-containers
File: .devcontainer/devcontainer.json at repo root.
{
"name": "C++20/23 Dev (Ubuntu 24.04, LLVM 20)",
"build": {
"dockerfile": "../Dockerfile",
"context": ".."
},
"workspaceFolder": "/workspace",
"workspaceMount": "source=${localWorkspaceFolder},target=/workspace,type=bind,consistency=cached",
"remoteUser": "dev",
"customizations": {
"vscode": {
"extensions": [
"llvm-vs-code-extensions.vscode-clangd",
"vadimcn.vscode-lldb",
"ms-vscode.cmake-tools",
"twxs.cmake",
"usernamehw.errorlens",
"eamodio.gitlens",
"jeff-hykin.better-cpp-syntax"
],
"settings": {
"clangd.path": "/usr/local/bin/clangd",
"cmake.generator": "Ninja",
"cmake.buildDirectory": "${workspaceFolder}/build-linux"
}
}
},
"features": {
"ghcr.io/devcontainers/features/common-utils:2": {
"installZsh": true,
"configureZshAsDefaultShell": true,
"installOhMyZsh": false,
"upgradePackages": false
}
},
"postCreateCommand": "cmake --version && clang++ --version && conan --version",
"mounts": [
"source=cxx-dev-conan-cache,target=/home/dev/.conan2,type=volume",
"source=cxx-dev-clangd-cache,target=/home/dev/.cache/clangd,type=volume"
]
}
Usage:
Open the repo in VSCode:
code ..Cmd+Shift+P→Dev Containers: Reopen in Container.VSCode builds the image (first time: ~5 min), starts the container, attaches its language servers inside.
All your VSCode extensions,
.clangd,.clang-format,.clang-tidynow run against the Linux toolchain instead of macOS.Terminal inside VSCode is now Ubuntu bash/zsh,
uname -ashows Linux.
Why this is the killer workflow. You develop C++ against the same toolchain as GitHub Actions / any Linux CI. When the CI is red and your Mac is green, you check the container. If the container is red, the fault is in your code, not your environment.
The build-linux/ output directory is separate from your macOS
build/, so both toolchains coexist without stomping each other.
The two named volumes (cxx-dev-conan-cache, cxx-dev-clangd-cache)
persist across container rebuilds — Conan package cache and the clangd
index survive. Without these you’d re-download Boost or Eigen every
rebuild.
§6. docker compose for multi-service dev (Phase 6)¶
For Phase 6 (production systems), you’ll want a Postgres or Redis alongside your C++ service:
# docker-compose.yml
version: '3.9'
services:
cxx-dev:
build: .
image: cxx-dev:2026
volumes:
- .:/workspace
- cxx-dev-conan-cache:/home/dev/.conan2
working_dir: /workspace
tty: true
stdin_open: true
depends_on: [postgres, redis]
postgres:
image: postgres:17-alpine
environment:
POSTGRES_PASSWORD: dev
POSTGRES_DB: dev
ports: ["5432:5432"]
volumes: [pgdata:/var/lib/postgresql/data]
redis:
image: redis:8-alpine
ports: ["6379:6379"]
volumes:
cxx-dev-conan-cache:
pgdata:
Usage:
docker compose up -d postgres redis
docker compose run --rm cxx-dev
# inside container: cmake -B build && cmake --build build && ./build/service
§7. Cross-platform builds (ARM64 macOS → x86_64 Linux)¶
Two paths:
Emulation (slow but simple). Docker + QEMU can build x86_64 images on an ARM host:
docker buildx build --platform linux/amd64 -t cxx-dev:2026-x86 .
This is 3–5× slower than native ARM builds. Acceptable for one-off CI parity checks. Do not use for daily dev.
Native (fast, correct). For real x86_64 testing, use a cheap Linux
VM in the cloud. Hetzner (hetzner.com/cloud) offers x86_64 dedicated
CPU boxes for €4–8/month. Provision one, ssh in, docker there.
Faster than local emulation and matches production.
For your 13-month roadmap: emulation is fine through Phase 5. If you hit performance-testing needs in Phase 6, spin up Hetzner.
§8. GitHub Actions parity — what to match¶
Your GitHub Actions matrix (documented in 05_phase_4_build_test_tooling/)
should mirror this container. Concrete parity check:
# .github/workflows/ci.yml — snippet
jobs:
linux-clang:
runs-on: ubuntu-24.04
container: cxx-dev:2026 # if you push the image to ghcr.io
steps:
- uses: actions/checkout@v4
- run: cmake -B build -G Ninja -DCMAKE_BUILD_TYPE=Release
- run: cmake --build build
- run: ctest --test-dir build --output-on-failure
Push your Dockerfile-built image to GitHub Container Registry
(ghcr.io/<user>/cxx-dev:2026) so CI pulls the exact same image you
develop against. Local + CI in perfect sync. This is the reproducibility
guarantee that eliminates 90% of “works on my machine”.
§9. India-specific notes¶
Image pulls from Docker Hub / GHCR are slow from India. ~2–5 MB/s is typical on Airtel/Jio fiber. First-time Ubuntu 24.04 + LLVM 20 pulls will take ~10–15 minutes. Do it once, cache the layers.
Docker Hub rate limits hit unauthenticated pulls (100/6 hrs from a single IP).
docker login docker.iowith a free Docker Hub account fixes this — first thing to do after installing.OrbStack / Colima do not require Docker Hub login. Once you have your image built locally, no further pulls needed for daily dev.
Corp VPN. If Zoho VPN blocks GHCR or Docker Hub, disconnect the VPN before pulling, then reconnect. Or push your image to a private Zoho registry if your work requires it.
§10. Troubleshooting¶
Symptom |
Cause |
Fix |
|---|---|---|
|
OrbStack/Colima not running |
Launch OrbStack app or |
Build super slow (~30 min for LLVM apt) |
Building x86_64 on ARM via QEMU |
Use |
|
Ubuntu clang-20 defaults to libstdc++; your Dockerfile forces libc++ but a file overrides it |
Check the file has no |
Dev Container “port already in use” |
Previous container still running |
|
Bind mount slow on macOS |
Default consistency is |
Restart container |
Container UID mismatch on files |
|
Rebuild image with |
|
LLVM or Kitware apt key expired |
Rebuild image (keys refreshed on rebuild) |
Docker Desktop uses 100% CPU idle |
Docker Desktop bug (existed since 2023) |
Switch to OrbStack or Colima |
§11. When to use what — decision matrix¶
Situation |
Tool |
|---|---|
Solo learner, cost-averse |
Colima |
Solo learner, time-averse |
OrbStack (~$8/mo worth it) |
Zoho-corporate work |
Whatever Zoho standardizes on |
GitHub Actions parity |
Dev Containers using the Dockerfile in §4 |
Multi-service integration test |
|
Testing on x86_64 explicitly |
Hetzner or Vast.ai VM, not local emulation |
Learning k8s (Phase 6 stretch) |
OrbStack’s built-in k8s or |