Mastodon Skip to content
ai.rud.is
Go back

Apple's `container machine`: Persistent Linux Environments on Your Mac

hrbrmstrMD

Apple ships a container runtime for Apple Silicon Macs. It is not Docker. It is a Swift tool called container (repo at github.com/apple/container) that runs Linux containers as individual lightweight virtual machines using macOS’s Virtualization framework. Each container gets its own VM — a full virtual machine per container, with its own kernel, init, and network stack, rather than a shared Linux VM partitioned by namespace isolation.

The container machine subcommand is where the tool becomes genuinely useful, and in a way that goes beyond what Docker does.

What Is a Container Machine?

A container machine is a persistent Linux environment that runs inside one of these lightweight VMs. You create it from a standard OCI image, same as any container, but it boots with /sbin/init as PID 1, which means it runs systemd (or whatever init system the image ships). It maps your macOS username and home directory into the VM automatically. It lives until you delete it, surviving stop/start cycles with its filesystem intact.

container machine create alpine:3.22 --name dev
container machine run -n dev whoami       # your macOS username
container machine run -n dev pwd          # your macOS home directory
container machine run -n dev              # interactive shell

The container machine run command is the primary interface. It boots the machine if it is stopped, then executes the command. No command opens an interactive login shell as your macOS user. Your home directory is live-mounted — not copied, not synced — so files you edit on your Mac are immediately visible inside the VM and vice versa.

What Makes It Different

Three things distinguish container machines from ordinary containers.

Persistent system state. A container machine runs an init system. You can systemctl start postgresql and have a supervised PostgreSQL that survives crashes, writes logs to journald, and manages its own lifecycle. A standard Docker container’s entrypoint is PID 1 and there is no service supervision.

Home directory sharing. Your macOS $HOME is mounted at /Users/<you> inside the VM. Your repos, SSH keys, dotfiles, everything. Edit with Zed or any editor on the Mac side; compile and run inside the Linux VM. There is no scp-commit-redeploy loop because there is no copy step — both sides see the same files.

One VM per environment, not one VM for everything. Traditional macOS Docker runs a single Linux VM (usually in a hypervisor like HyperKit or QEMU) and uses Linux namespaces to partition it into containers. Apple’s container runs one VM per container. This means each container machine has its own kernel, its own networking, and its own init. You can run Alpine, Ubuntu, and Debian machines simultaneously, each with a different init system, and they are fully isolated from each other.

The Gotchas You Will Hit

I spent a session building and breaking things with an LLM (via OpenCode). Here is what the docs do not emphasize enough.

$HOME is not where you think. The machine mounts your macOS home at /Users/<you>, but sets $HOME to /home/<you>. Any tool that resolves ~ relative to $HOME will look in the wrong place. SSH identity file resolution is the most painful example — ssh reads ~/.ssh/id_ed25519, which becomes /home/<you>/.ssh/id_ed25519, which does not exist. The fix is a symlink:

container machine run -n sdamin --root ln -s /Users/<you>/.ssh /home/<you>/.ssh

container machine run mangles shell quoting. The command wrapper re-interprets quoting in unpredictable ways. Avoid && chains, heredocs, and pipes in commands passed to container machine run. Use single commands, or write a script on the mounted home directory and invoke it.

--root is required for privileged writes. Files created during the Docker build are owned by root. Appending to /etc/hosts or modifying system config from inside the running machine requires container machine run -n <name> --root <command>.

Go binaries install to /root/go/bin/. If you go install something in your Dockerfile, the binary lands in /root/go/bin/, which is not in the default PATH. Copy to /usr/local/bin/ explicitly.

Not everything is in apt. masscan, enum4linux, and seclists are not in Ubuntu 24.04’s repositories. The build will fail if you include them without handling the error.

The Skill I Built

Working with the LLM in OpenCode, I created an apple-container-machine skill that covers all of this — the lifecycle, the image requirements, the host integration quirks, remote-SSH setup, resource configuration, multi-distro workflows, and every gotcha listed above. It auto-triggers on phrases like “container machine”, “m run”, or “apple container machine.”

The skill is useful for:

The Two Container Files

recon-machine (Dockerfile)

A security reconnaissance workstation with Ubuntu 24.04 and systemd. Includes tools across several categories:

Purpose: a portable, disposable Linux environment for network recon and security assessment. The home directory mount means results written to ~/recon/ on the machine survive deletion, and tools installed via go install or pip inside the machine persist across stop/start cycles.

ubuntu-systemd-admin-machine (Dockerfile)

An SSH-based systemd administration workstation. Includes tmux, fzf, jq, ripgrep, multiplexed SSH (ControlMaster with ControlPersist), and a set of shell helper functions (sctl, jlog, sfollow, sfailed, sanalyze, spick) for running systemctl, journalctl, and systemd-analyze across remote Ubuntu hosts. The hosts file lists two managed servers. Purpose: a persistent terminal environment for administering a fleet of Ubuntu servers — tmux sessions survive disconnect, SSH control sockets eliminate repeat authentication, and the helper functions reduce ssh host systemctl status service to sctl host status service.

Both Dockerfiles follow the same container machine pattern: Ubuntu 24.04 base, ENV container container, systemd with appropriate services masked, empty machine-id, and SSH configured for remote access.

The Workflow That Emerges

After a few hours with this tool, a pattern emerges. The container machine becomes a persistent jump box that lives on your Mac. You SSH into it from your terminal (or container machine run -n sdamin), and from there you manage your remote infrastructure. The ControlMaster SSH multiplexing means the second, third, and hundredth connection to each host cost almost nothing. Tmux keeps your session alive when you close your laptop. Jq and ripgrep process journal output that would be unreadable raw. Fzf lets you fuzzy-select hosts from the inventory.

This is a different model than Docker or Ansible — a persistent Linux workstation with your files, your tools, and live connections to the machines you manage.



Previous Post
Bulletproof Hosting Watch: Week of June 15
Next Post
Bulletproof Hosting Watch: Week of June 8