Skip to content

Local Development Podman macOS

CYPT71 edited this page Aug 9, 2026 · 1 revision

Local Development (Podman / macOS)

scripts/local/build-macos-image.sh gives you a fast local edit-run loop on macOS without touching the repository's CI-shaped tooling, using Podman rather than Docker.

Why Podman, not Docker

Nothing in this project is Docker-specific by requirement - the root Dockerfile works with any OCI-compatible builder. Podman was chosen for the local path specifically because on macOS it runs your build inside a real Linux VM it manages for you (podman machine), which is exactly the environment this project's output actually targets (GOOS=linux binaries, Linux container semantics) - without needing Docker Desktop installed. If you already have Docker Desktop, the same commands work with docker substituted for podman throughout; nothing about the Dockerfile or the layout format is Podman-specific.

What the script does

scripts/local/build-macos-image.sh <image-name[:tag]> [amd64|arm64]
  1. Picks an architecture: your argument if given, otherwise auto-detected from uname -m (arm64 on Apple Silicon, amd64 otherwise).
  2. Builds cmd/example-service (see Scripts Reference) statically for linux/<arch> in a throwaway mktemp -d directory - nothing in the repository working tree is modified.
  3. Runs go run ./cmd/oci-builder to turn that binary into a real OCI layout, right there in the same temp directory.
  4. Copies scripts/local/Dockerfile (not the repository root one - see below) into that temp directory as the build context.
  5. podman build --platform linux/<arch> --tag <image-name> <tmpdir>.
  6. Prints the exact podman run command (with the full hardening flag set) and curl commands to try the three endpoints.

scripts/local/Dockerfile: why a second Dockerfile

This is not the repository root Dockerfile - it's a separate, simplified, local-only build file, for one specific reason: it pulls exactly one external image, Alpine, used only as a throwaway builder stage, collapsing the root Dockerfile's two Alpine-based stages (verify + unpack) into a single stage, purely to cut local iteration time (fewer layers to rebuild on every change) - it performs the exact same checks (marker files, blob count, per-blob digest verification, single-manifest/single-layer assertion) as the root Dockerfile, just in one stage instead of two. The final stage is still FROM scratch - nothing external ends up in the image you actually run, same as the root Dockerfile.

The root Dockerfile stays untouched and is what CI (ci-runtime.yml, ci-release.yml) and any real deployment should use - this one exists purely for fast local edit/build/run cycles.

cmd/example-service: the shared fixture

Both this script and MicroVM Support's CI path build the same small, stdlib-only Go HTTP API:

Path Response
GET /healthz 200 ok\n
GET /ping 200 pong\n
GET /metrics Prometheus text-exposition format: http_requests_total{path="..."} counters and process_uptime_seconds

Every request is also logged as structured JSON to stdout via log/slog (method, path, status, duration_ms, remote) - visible with podman logs <container>.

Trying it

scripts/local/build-macos-image.sh myservice:local

podman run --rm --read-only --security-opt no-new-privileges \
  --tmpfs /tmp:rw,noexec,nosuid,size=16m --publish 8080:8080 myservice:local

curl http://localhost:8080/healthz
curl http://localhost:8080/ping
curl http://localhost:8080/metrics

Relationship to the other consumers

Runs under Isolation Use case
This script Podman (container) namespaces/cgroups, shared host kernel fast local dev loop
Dockerfile any container runtime namespaces/cgroups, shared host kernel real deployment
scripts/microvm QEMU/KVM (VM) separate kernel strongest isolation, or a binary that can't be containerized

All three consume the exact same kind of OCI layout produced by cmd/oci-builder - see Architecture and OCI Layout.

Clone this wiki locally