Skip to content

Add container machine for managing persistent Linux environments - #1662

Merged
realrajaryan merged 1 commit into
apple:mainfrom
realrajaryan:container-machine
Jun 8, 2026
Merged

Add container machine for managing persistent Linux environments#1662
realrajaryan merged 1 commit into
apple:mainfrom
realrajaryan:container-machine

Conversation

@realrajaryan

Copy link
Copy Markdown
Member

Type of Change

  • Bug fix
  • New feature
  • Breaking change
  • Documentation update

Motivation and Context

container runs each workload in an ephemeral VM, so there's no built-in way to keep a persistent Linux environment you can log into and work in. container machine adds one.

A container machine is a lightweight, persistent, and integrated Linux environments that feel like an extension of your Mac, created from standard OCI images with a familiar UX. The login user matches your host account with passwordless sudo, your home directory is mounted inside the VM, and each machine keeps its filesystem and runs the image's own init system (such assystemd or openrc).

container machine create alpine:3.22 --name my-machine
container machine run -n my-machine # interactive shell
container machine set -n my-machine cpus=4 memory=8G

Subcommands: create, run, list (ls), inspect, set, set-default, logs, stop, delete (rm); m aliases machine. Docs added to docs/command-reference.md (Machine Management) and docs/how-to.md ("Use container machines").

Testing

  • Tested locally
  • Added/updated tests
  • Added/updated docs

@github-actions github-actions Bot added documentation Improvements or additions to documentation cli labels Jun 8, 2026
@realrajaryan realrajaryan removed the documentation Improvements or additions to documentation label Jun 8, 2026
`container machine` creates and manages container machines: lightweight,
persistent, and integrated Linux environments that feel like an extension
of your Mac, created from standard OCI images with a familiar UX.

Subcommands: create, run, list (ls), inspect, set, set-default, logs,
stop, delete (rm). `m` aliases machine. Adds command-reference and
how-to docs.

Co-authored-by: Jaewon Hur <jaewon_hur@apple.com>
Co-authored-by: John Logan <john_logan@apple.com>
Co-authored-by: Michael Crosby <michael_crosby@apple.com>
Co-authored-by: Eric Ernst <eric_ernst@apple.com>
Co-authored-by: Danny Canter <danny_canter@apple.com>
Signed-off-by: Raj Aryan Singh <rajaryan_singh@apple.com>
@github-actions

github-actions Bot commented Jun 8, 2026

Copy link
Copy Markdown

Code Coverage

Tier Line Coverage
Unit 33.16%
Integration 21.11%
Combined 53.47%

@realrajaryan
realrajaryan merged commit b2994ac into apple:main Jun 8, 2026
3 checks passed
@crosbymichael crosbymichael changed the title Add container machine for managing persistent Linux VMs Add container machine for managing persistent Linux environments Jun 8, 2026
@realrajaryan
realrajaryan deleted the container-machine branch June 8, 2026 19:29
bevanjkay pushed a commit to ascarter/homebrew-core that referenced this pull request Jun 10, 2026
Two related regressions in 1.0.0 prevent `container system start` from
completing:

1. The previous formula patched
   Sources/ContainerPlugin/InstallRoot.swift via `inreplace` to hard-code
   the keg prefix as the install root. Upstream PR apple/container#1558
   (May 2026) renamed the symbols the inreplace targeted
   (`CommandLine.executablePathUrl` ->`executablePath`,
   `deletingLastPathComponent` -> `removingLastComponent`), so the
   substitution silently became a no-op when 1.0.0 was bumped. The
   apiserver now resolves its install root as the lexical grandparent of
   the running executable without symlink resolution, which on
   /opt/homebrew/bin/container lands at /opt/homebrew rather than the
   keg, where no `libexec/container-plugins` directory exists. The
   apiserver crash-loops with "cannot find any plugins with type
   network" and `container system start` hangs at "Testing access to
   container-apiserver...".

   Fix this without patching upstream sources by installing the binaries
   to libexec/ and using `bin.env_script_all_files` to generate shim
   wrappers in bin/ that `exec` the real binaries via their absolute
   keg path. The kernel-reported executable path now has `opt_prefix` as
   its lexical grandparent, putting the plugin search exactly on top of
   `opt_prefix/libexec/container-plugins/`. The wrappers also export
   `CONTAINER_INSTALL_ROOT=opt_prefix` for the code paths that honor the
   environment variable.

2. The 1.0.0 release added a new `machine-apiserver` plugin
   (apple/container#1662, "container machine") under
   Sources/Plugins/MachineAPIServer that the formula never picks up.
   `container system start` fails at the "Verifying machine API server
   is running" step without it, so it must ship together with fix Homebrew#1
   for the start command to succeed at all.

Refactor the plugin installation loop to a data-driven structure so
adding the new plugin (with its config.toml plus init and
create-user.sh resources from Sources/Plugins/MachineAPIServer/Resources/)
is a one-line addition. Codesigning mirrors what the upstream Makefile
does for that binary (--prefix only, no entitlements; no
`signing/machine-apiserver.entitlements` exists upstream).

Verified locally with:

  brew install --build-from-source container
  brew test container
  brew audit --strict container
  container system start
  container list
  container machine list
subpop added a commit to subpop/applebox that referenced this pull request Jun 17, 2026
Replace the custom PID 1 sleep loop with a multi-mode init script that
execs into the distro's real /sbin/init (systemd, OpenRC, etc.). This
provides proper zombie reaping, system service support, and correct
boot semantics.

The init script operates in three modes:
- Boot mode (no flags): sets hostname, writes .containerenv, execs
  /sbin/init
- Shell mode (-s): resolves distro-appropriate shell from /etc/passwd
  with distro-aware fallbacks, execs into login shell or command
- User setup mode (-u): creates container user on first entry via
  direct /etc/passwd manipulation (distro-agnostic, replaces
  useradd/usermod)

User setup is decoupled from boot and runs once on first entry rather
than every boot. Shell resolution moves into the container (reading
/etc/os-release for distro detection) instead of relying on host-side
stamp files. Working directory resolution mirrors the host CWD only
when under the user's home directory.

Inspired by apple/container#1662 (container machine).

Assisted-By: Claude

@ramialshani250-cpu ramialshani250-cpu left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let pluginLoader = try initializePluginLoader(log: log)

try await initializePlugins(pluginLoader: pluginLoader, log: log, routes: &routes)
try await initializePlugins(pluginLoader: pluginLoader, log: log, routes: &routes, debug: debug)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.


let service = PluginsService(pluginLoader: pluginLoader, log: log)
try await service.loadAll(bootPlugins)
try await service.loadAll(bootPlugins, debug: debug)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

jianliang00 pushed a commit to jianliang00/container that referenced this pull request Aug 28, 2026
## Type of Change
- [ ] Bug fix
- [x] New feature  
- [ ] Breaking change
- [ ] Documentation update

## Motivation and Context
`container` runs each workload in an ephemeral VM, so there's no
built-in way to keep a persistent Linux environment you can log into and
work in. `container machine` adds one.

A container machine is a lightweight, persistent, and integrated Linux
environments that feel like an extension of your Mac, created from
standard OCI images with a familiar UX. The login user matches your host
account with passwordless `sudo`, your home directory is mounted inside
the VM, and each machine keeps its filesystem and runs the image's own
init system (such as`systemd` or `openrc`).

```bash
container machine create alpine:3.22 --name my-machine
container machine run -n my-machine # interactive shell
container machine set -n my-machine cpus=4 memory=8G
```

Subcommands: `create`, `run`, `list` (`ls`), `inspect`, `set`,
`set-default`, `logs`, `stop`, `delete` (`rm`); `m` aliases `machine`.
Docs added to `docs/command-reference.md` (Machine Management) and
`docs/how-to.md` ("Use container machines").

## Testing
- [x] Tested locally
- [x] Added/updated tests
- [x] Added/updated docs

Signed-off-by: Raj Aryan Singh <rajaryan_singh@apple.com>
Co-authored-by: Jaewon Hur <jaewon_hur@apple.com>
Co-authored-by: John Logan <john_logan@apple.com>
Co-authored-by: Michael Crosby <michael_crosby@apple.com>
Co-authored-by: Eric Ernst <eric_ernst@apple.com>
Co-authored-by: Danny Canter <danny_canter@apple.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants