---
title: "Apple's `container machine`: Persistent Linux Environments on Your Mac"
description: "Apple ships a container runtime for Apple Silicon Macs. It is not Docker. Here is what you actually need to know about the `container machine` subcommand — how it works, what breaks, and the artifacts I built after a session of breaking things."
pubDatetime: 2026-06-10T08:00:00Z
author: hrbrmstr
tags: ["apple", "container", "macos", "linux", "docker", "devops", "systemd", "opencode"]
---
> Original: [Apple's `container machine`: Persistent Linux Environments on Your Mac](https://ai.rud.is/posts/2026-06-10-apple-container-machine)

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](https://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.

```bash
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:

```bash
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](https://git.sr.ht/~hrbrmstr/gists/blob/main/2026/apple-container-machine-machines/apple-container-machine-skill.md) 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:

- **Any OpenCode session that involves Apple's `container` tool.** Without it, the agent -- whose training data is dominated by Docker patterns -- has no structured knowledge of the container machine subcommand and will default to Docker-style assumptions that do not apply.
- **Building custom machine images.** The Dockerfile pattern for init-system compatibility (`ENV container container`, empty `/etc/machine-id`, systemd masking) is non-obvious and error-prone. The skill embeds a working example.
- **Debugging host integration issues.** The `$HOME` mismatch, SSH key resolution, and DNS domain setup are all documented with verified fixes.

## The Two Container Files

### recon-machine ([Dockerfile](https://git.sr.ht/~hrbrmstr/gists/blob/main/2026/apple-container-machine-machines/Dockerfile.recon-machine))

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

- **Network mappers:** nmap, masscan
- **Web scanners:** nikto, whatweb, nuclei, wpscan
- **Fuzzers and brute force:** ffuf, gobuster, hydra, sqlmap
- **Subdomain and DNS recon:** subfinder, httpx, naabu, dnsrecon
- **TLS testing:** sslscan, testssl.sh
- **Service-specific:** smbclient, ldap-utils
- **General tooling:** Go for `go install` binaries

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](https://git.sr.ht/~hrbrmstr/gists/blob/main/2026/apple-container-machine-machines/Dockerfile.ubuntu-systemd-admin))

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.

