Skip to content
Macneil Shonle’s Blog

So You Want to Contain Multitudes

Two days ago I decided to try out Apple's own container tool, which enables Apple Silicon Macs to run GNU/Linux in a container. I did this because I really wanted to try Claude Code's --dangerously-skip-permissions mode, but didn't want to automatically clean out my home directory either. Things mostly went ok, but there are caveats!

Getting Started

I followed the tutorial for the 0.7.1 release. It even took me a while to figure out that I wanted to download the container-installer-signed.pkg file from Apple's GitHub releases page.

After you've downloaded it, you should do the typical MacOS developer-installing-programs things and then verify that you have it:

$ which container
/usr/local/bin/container

If you do, you can then start the service:

$ container system start
Verifying apiserver is running...
No default kernel configured.
Install the recommended default kernel from [https://github.com/kata-containers/kata-containers/releases/download/3.17.0/kata-static-3.17.0-arm64.tar.xz]? [Y/n]: 

The first time around it will ask you to install some kata kernels, so answer yes to continue.

At any time, you can check on the status of the service. If it started successfully, you'll see something like this:

$ container system status
apiserver is running
application data root: /Users/mshonle/Library/Application Support/com.apple.container/
application install root: /usr/local/
container-apiserver version: container-apiserver version 0.7.1 (build: release, commit: 420be74)
container-apiserver commit: 420be748f18afc685d11987ac5118c928e174c19

DNS Magic

Apple's tutorial covers how you can use a custom DNS name to help you access containers. Here's how to create one called test:

$ sudo container system dns create test
test

$ container system property set dns.domain test

That way, if you have a container named my-cool-django-blog you can access it via Safari as http://my-cool-django-blog.test.

Example Server

Apple gives this nice example. Create a directory and cd into it:

$ mkdir web-test
$ cd web-test

And then create a Dockerfile there so you end up with this:

$ cat Dockerfile 
FROM docker.io/python:alpine
WORKDIR /content
RUN apk add curl
RUN echo '<!DOCTYPE html><html><head><title>Hello</title></head><body><h1>Hello, world!</h1></body></html>' > index.html
CMD ["python3", "-m", "http.server", "80", "--bind", "0.0.0.0"]

This uses the Alpine Docker Official Image (DOI), where Alpine is a lightweight Linux (and that is the first time I've written "Linux" in 2026 without calling it "GNU/Linux"). Apple's container can use these Dockerfiles.

Build the Server

Even the baby server example might take several tries (read on to the troubleshooting section if this is you). Eventually, I tried this command and it built the Alpine container for me:

$ container build --tag web-test --file Dockerfile .
[+] Building 9.4s (7/7) FINISHED
 => [resolver] fetching image...docker.io/library/python:alpine                                           0.0s
 => [internal] load .dockerignore                                                                         0.0s
 => => transferring context: 2B                                                                           0.0s
 => oci-layout://docker.io/library/...
 => [linux/arm64/v8 1/4] WORKDIR /content                                                                 0.2s
 => [linux/arm64 2/4] RUN apk add curl                                                                    3.0s
 => [linux/arm64 3/4] RUN echo '<!DOCTYPE html><html><head><title>Hello</title></head><body><h1>Hello, world!</h1  0.0s
 => exporting to oci image format                                                                         0.4s 
 => => exporting layers                                                                                   0.2s 
 => => exporting manifest sha256:3680f7b7bf80274e185d6c5558d74e9dfcd207863190f50ae438cabbe8c14acf         0.0s 
 => => exporting config sha256:b5814728734c0d9b14b0f5b2c587e0bcd2ea19695dd9be9847f45cb87b13e8f5           0.0s 
 => => exporting manifest list sha256:7b9069d719ea31629c473c71e86c36362704f931237517d73c0814243d70195e    0.0s 
 => => sending tarball                                                                                    0.2s
Successfully built web-test:latest

Once a build works, you can see it listed:

$ container image list
NAME      TAG     DIGEST
python    alpine  7af51ebeb83610fb69d633d5...
web-test  latest  7b9069d719ea31629c473c71...

Start the Server

After a short while, this command that I ran next returned:

$ container run --name my-web-server --detach --rm web-test
my-web-server

And it left something around:

$ container ls
ID             IMAGE                                               OS     ARCH   STATE    ADDR          CPUS  MEMORY
my-web-server  web-test:latest                                     linux  arm64  running  192.168.64.3  4     1024 MB
buildkit       ghcr.io/apple/container-builder-shim/builder:0.7.0  linux  arm64  running  192.168.64.2  2     2048 MB

It's at this point the DNS magic can kick in:

$ open http://my-web-server.test

Never thought seeing a "Hello, world!" could be that interesting!

(Note how that sample server only has 1 GiB of memory! To do more interesting things, you'll need to increase that.)

You can poke around the server by opening a shell:

$ container exec --tty --interactive my-web-server sh
/content # whoami
root
/content # ls -l
total 4
-rw-r--r--    1 root     root            97 Jan  6 23:04 index.html
/content # uname -a
Linux my-web-server 6.12.28 #1 SMP Tue May 20 15:19:05 UTC 2025 aarch64 Linux
/content # exit

It put us into the /content directory because that's what the Dockerfile specified.

Troubeshooting

Sometimes when trying to build you will see "Dialing"... when the container service gets stuck, the first thing you should try is stopping it:

container system stop

If that still hangs, you can find and kill the processes:

$ ps ax -o "pid,comm" | grep Virtualization
99584 /System/Library/Frameworks/Virtualization.framework/Versions/A/XPCServices/com.apple.Virtualization.VirtualMachine.xpc/Contents/MacOS/com.apple.Virtualization.VirtualMachine

After you find the PID (e.g., 99584), then you can -9 it (it's like 67, I think):

kill -9 99584

If your build terminal is still hanging, you can kill the services:

$ killall container-apiserver; killall container-runtime-linux; killall container-core-images; killall container-network-vmnet

You can list your containers and remove the ones you no longer need, e.g.,:

$ container list --all
...
$ container rm 11709408-8d0d-4fab-8f52-ffad87032967
11709408-8d0d-4fab-8f52-ffad87032967

After everything is killed, you can bring it back up:

$ container system start
Verifying apiserver is running...

Putting Together All The Things

It took me a while to figure out:

I put it all up on a GitHub repo for you to use: Deeclaud. If you check it out, you can build your own Claude Code running GNU/Linux container with a script (co-authored by Microsoft Copilot, Claude Code, and me):

./manage-container.sh setup

Once that works, you can then run the server containing Claude Code with a special git worktree that gets mounted on the container side:

./deeclaud.sh ../some/path/to/my-cool-repo a-branch-name

The worktree is a copy of your Git repository, which works much better than having a single repository with multiple branches. Multiple, isolated worktrees are great for running multiple parallel tasks on the same project.

(This was the way we used to do things with SCCS/Teamware at Sun, and the same thing BitKeeper did, which is what the the GNU/Linux creator used before he decided to make his own source control system. It took ten years after that before Git had effective worktrees; which were effective in the sense that it was better than making a complete copy of all of the .git internal directories.)

So, how is it? Well, on my 16 GiB M1 MacBook Pro, there is a noticeable slowdown. I think if someone has a newer Mac with an M3+32 GiB of RAM (or better) they should definitely try it out and share their findings with me!

I think if I had this set up in place when Anthropic had the December compute gift credits I would have been able to use more of them!