Run Linux Containers on Your Mac With Apple's container Tool
Quick answer
Apple ships an official, open-source way to run Linux containers on your Mac — no Docker Desktop, no license conversation, and every container gets its own IP address. Install it, run your first container, build an image from a Dockerfile you already have, and learn the day-to-day commands.
- Step 1: Check Your Mac Meets the Requirements
- Step 2: Install the Signed Package
- Step 3: Start the System Service
- Step 4: Run Your First Container
- Step 5: Build Your Own Image
beginner · 25 min
Before you begin
- An Apple silicon Mac (M1 or later)
- macOS 26 (Tahoe) — the tool installs on macOS 15 but key networking features need 26
- Basic familiarity with the terminal
- A Dockerfile or two you'd like to try (optional — we build one from scratch)
Here's the thing nobody tells you about running containers on a Mac: Docker doesn't run on macOS. It never has. Every tool you've used — Docker Desktop, Colima, OrbStack — quietly runs a Linux virtual machine in the background and hides it from you. Your containers live inside that hidden VM.
Apple's container tool plays the same trick with two differences: it's built by Apple on the macOS Virtualization framework, and instead of one big shared VM, every container gets its own tiny virtual machine — its own kernel, booted fresh in under a second, thrown away when the container stops. Think of it as each container getting its own studio apartment instead of a bunk in a shared dorm: better isolation, and nobody fights over the network ports.
I wrote up the architecture, the trade-offs, and how it stacks up against Docker Desktop in a full review of the 1.0 release — this tutorial is the hands-on companion. By the end you'll have the tool installed and be building and running containers with the same Dockerfiles you use everywhere else.
What You'll Build
- Apple's
containertool installed and its system service running - A running nginx container with its own IP address — no port mapping needed
- A custom image built from a Dockerfile and running as a container
- A local DNS domain so
web.testworks instead of memorizing IPs - The everyday workflow: exec, logs, stats, copy files, set CPU/memory limits, and run
amd64images via Rosetta
Step 1: Check Your Mac Meets the Requirements
Two hard requirements: Apple silicon, and ideally macOS 26 (Tahoe). Verify both:
uname -m # must print: arm64
sw_vers # ProductVersion should be 26.xIntel Macs are not supported at all. On macOS 15 (Sequoia) the tool installs and runs single containers, but container-to-container networking and several other features need vmnet APIs that only exist in macOS 26 — and the project only prioritizes issues on 26. If you're on Sequoia, expect rough edges.
Step 2: Install the Signed Package
Grab the latest installer from the GitHub releases page — you want the container-<version>-installer-signed.pkg asset. It's a normal, Apple-signed macOS package: double-click, click through, done. (As of this writing the current release is 1.1.0, from July 2026.)
Verify from a fresh terminal:
container --versionEverything installs under /usr/local/, including two maintenance scripts you'll want to know exist: update-container.sh and uninstall-container.sh (used in Tear Down below).
One nicety worth doing now — shell completions:
container --generate-completion-script zsh > ~/.container-completion.zsh
echo 'source ~/.container-completion.zsh' >> ~/.zshrcStep 3: Start the System Service
container runs a lightweight background service that manages networking, DNS, and the API. Start it:
container system startOn first start it notices there's no Linux kernel yet and offers to fetch one:
No default kernel configured.
Install the recommended default kernel from [https://github.com/kata-containers/...]? [Y/n]:
Say yes. This downloads an optimized kernel from the Kata Containers project — the same minimal kernel used for microVMs in multi-tenant clouds. It's the kernel every one of your container VMs will boot.
Sanity check:
container lsAn empty table means the service is up and listening. (container ls and container list are the same command — most subcommands have short aliases; --help lists them.)
Step 4: Run Your First Container
Pull and run nginx, detached, with a name:
container run --detach --name web docker.io/nginx:latestBehind that one command: the image is pulled from Docker Hub, unpacked into an ext4 filesystem, a fresh VM boots a Linux kernel, and nginx starts as the VM's only process. Now look at what you got:
container ls
# NAME IMAGE OS ARCH STATE ADDR
# web docker.io/nginx:latest linux arm64 running 192.168.64.2That ADDR column is the headline feature. Your container has its own IP address. Curl it directly:
curl http://192.168.64.2No -p 8080:80, no port collisions, no "bind: address already in use" because another project's container squats on the port. Run five things that all listen on port 80 and they coexist — each is its own host as far as the network is concerned. (If some tool insists on localhost, traditional port publishing exists too: container run -p 127.0.0.1:8080:80 ... forwards host port 8080 to the container.)
Step 5: Build Your Own Image
container build consumes ordinary Dockerfiles. Make a tiny web app:
mkdir hello-container && cd hello-containerCreate a Dockerfile:
FROM docker.io/python:alpine
WORKDIR /srv
RUN echo '<h1>Built and served by Apple container</h1>' > index.html
CMD ["python3", "-m", "http.server", "80", "--bind", "0.0.0.0"]Build and run it:
container build --tag hello --file Dockerfile .
container run --detach --rm --name hello hello
container ls # note the new container's ADDR, then curl itThe first build starts a dedicated builder VM (2 GB memory, 2 CPUs by default) that sticks around for subsequent builds. Multi-stage Dockerfiles, build args, and build secrets all work as you'd expect. When you're ready to publish, it's the standard registry flow:
container registry login ghcr.io --username <you>
container image tag hello ghcr.io/<you>/hello:latest
container image push ghcr.io/<you>/hello:latestAnything OCI-compatible can pull the result — this is a normal image, nothing Apple-specific about it.
Step 6: Stop Memorizing IPs — Create a Local DNS Domain
The system service includes an embedded DNS server. Give it a domain once:
sudo container system dns create test(The sudo is for writing a resolver file under /etc/resolver so macOS knows to route *.test queries to the embedded DNS.) From now on, every named container is reachable at <name>.<domain>:
curl http://web.test # resolves to the nginx container's IP
curl http://hello.test # your custom imageNames survive container restarts even when the IP changes, which makes them the right thing to put in config files. This also works between containers — the foundation for running multi-service stacks, which is exactly what the follow-up tutorial builds.
Step 7: The Everyday Commands
Everything maps closely to the Docker CLI you know, with delete instead of rm:
1container exec -ti web sh # interactive shell inside a container
2container logs web # application logs
3container logs --boot web # the VM's kernel boot log — useful when a container dies instantly
4container cp web:/etc/nginx/nginx.conf ./nginx.conf # copy files host <-> container
5container stats web # live CPU/memory usage
6container inspect web # full JSON state (pipe to jq)
7container stop web && container delete web
8container image list # local imagesTwo logs --boot words of appreciation: because each container is a VM, when something goes wrong before your entrypoint runs, the kernel boot log tells you why. Docker has no equivalent.
Step 8: Resource Limits and amd64 Images
Each container VM gets 4 CPUs and 1 GB of memory by default. That's per container — twenty containers means twenty small VMs — so tune the ones that need more (or less):
container run --cpus 8 --memory 4g --detach --name pg docker.io/postgres:17-alpineDefaults live in ~/.config/container/config.toml if you want to change them globally:
[container]
cpus = 4
memory = "1gb"
[dns]
domain = "test"And for images that only ship x86_64 — still common for internal tooling — Rosetta 2 translates the binaries inside the Linux VM, which is dramatically faster than the QEMU emulation Docker Desktop falls back to:
container run --arch amd64 --rm docker.io/amd64/alpine uname -m
# x86_64Common Issues
container system startcan't download the kernel. Corporate proxies and VPN split-tunnel rules are the usual culprits — the kernel comes from GitHub releases. Fix connectivity and rerun; the prompt reappears until a kernel is installed.- Containers can't reach each other on macOS 15 (Sequoia). Container-to-container networking requires vmnet APIs that only exist in macOS 26. Single containers work on 15; anything multi-service needs the OS upgrade. This is a platform limitation, not a bug you can configure around.
web.testdoesn't resolve. Check the domain exists (container system dns list) and that the container was started with--name. If macOS itself won't resolve it, the/etc/resolver/testfile may be missing — delete and recreate the domain withsudo container system dns create test.- A big image takes minutes to unpack. Known weak spot: images with hundreds of thousands of small files (looking at you, ML kitchen-sink images) unpack slowly through the userspace ext4 writer. Slim images unpack fast — one more reason to keep your images small.
- Muscle memory sends you to
docker ...orcontainer rm. The verb differences are small (deleteforrm,container image listfordocker images). Aalias d=containerplus the completion script from Step 2 smooths the transition. What will not work is anything that talks to the Docker socket — see the FAQ.
Frequently Asked Questions
Does this replace Docker Desktop?
For building and running individual containers — yes, completely, and without licensing fees. What it doesn't replace is the Docker ecosystem: there's no Docker API socket, so Docker Compose, testcontainers, kind, and devcontainers don't work with it. Many developers run both side by side: container for daily builds and sandboxing, Docker Desktop or Colima kept around for compose-based projects. The 1.0 deep dive covers this trade-off honestly, and the three-way comparison with OrbStack and Colima helps you pick.
Can I use docker-compose with it?
No — Compose speaks the Docker API, which container doesn't implement. But per-container IPs plus DNS names remove much of what Compose exists to solve, and a small Makefile covers the rest. The multi-service stack tutorial shows the full pattern with Postgres, Redis, and an app container.
Why does every container get its own VM? Isn't that heavy?
Each VM boots a stripped-down Kata Containers kernel with a minimal init written in Swift — cold start is sub-second, so you don't feel it. The cost is memory: every container carries its own kernel, so the per-VM default of 1 GB matters more as container count grows. What you get in exchange is hardware-virtualization isolation per container — a container escape must beat a hypervisor, not just Linux namespaces. For running code you don't fully trust (AI-generated code, third-party tooling), that's the killer feature.
Do my existing Dockerfiles and registries work?
Yes. container builds standard OCI images from standard Dockerfiles and talks to any OCI registry — Docker Hub, GHCR, ECR, your private Harbor. Images built here run on Linux servers and vice versa. Only Docker-API-dependent tooling is affected.
Does it work on Intel Macs or older macOS?
No Intel support at all — the whole stack is built for Apple silicon. macOS 15 can install it, but the good parts (notably container-to-container networking) need macOS 26, and that's the only version the project actively supports.
Tear Down
Stop and remove what you created:
container stop web hello
container delete web hello
container image delete hello docker.io/nginx:latest
sudo container system dns delete test
container system stopTo remove the tool entirely, the installer ships an uninstall script — -d deletes your user data (images, containers), -k keeps it for a later reinstall:
/usr/local/bin/uninstall-container.sh -dOfficial References
- apple/container on GitHub — releases, issues, and the signed installer
- Get started with container — the official guided tour
- How-to guide — networking, volumes, capabilities, and every flag in context
- Command reference — the full CLI surface
- Technical overview — how the VM-per-container architecture works
Next steps: replace your docker-compose workflow with the multi-service dev stack tutorial, read why the architecture matters, or brush up container fundamentals with the Docker cheat sheet.
We built Podscape to simplify Kubernetes workflows like this — logs, events, and cluster state in one interface, without switching tools.
Struggling with this in production?
We help teams fix these exact issues. Our engineers have deployed these patterns across production environments at scale.