Skip to content

feat: add user-specified mounts to container machines - #1837

Open
danielsyauqi wants to merge 3 commits into
apple:mainfrom
danielsyauqi:feat/machine-user-mounts
Open

feat: add user-specified mounts to container machines#1837
danielsyauqi wants to merge 3 commits into
apple:mainfrom
danielsyauqi:feat/machine-user-mounts

Conversation

@danielsyauqi

@danielsyauqi danielsyauqi commented Jun 26, 2026

Copy link
Copy Markdown

Type of Change

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

Motivation and Context

Closes #1805.

container machine currently mounts only the user's home directory, configurable through --home-mount. There is no way to bind-mount additional arbitrary host directories into a machine, which the issue requests for parity with the container --volume workflow.

This change adds a repeatable --mount host:guest[:ro|rw] option to container machine create:

container machine create --mount /Volumes/Project:/Project --mount /tmp/data:/data:ro alpine:3.22

Summary of changes:

  • MachineConfig gains a self-contained Mount type (source, destination, readOnly) and a mounts field. Specifications are parsed and validated in with(_:mounts:): the host path must be an existing directory, the guest path must be absolute, the mode must be ro or rw (default rw), and duplicate destinations are rejected. Paths are resolved to absolute form at parse time.
  • The field is Codable with backward compatibility. Boot configurations written before this change decode to an empty list, and an empty list is omitted on encode. The field is intentionally skipped on the ConfigSnapshotDecoder path because the system-wide [machine] TOML section cannot represent arrays of structs. Mounts are a per-machine value carried in boot-config.json only.
  • MachinesService.toContainerConfig appends each configured mount as a virtiofs share at boot, alongside the existing home mount.
  • container machine inspect surfaces the configured mounts.
  • Documentation and regression tests cover configuration parsing, CLI persistence, and guest runtime behavior.

Scope is limited to create time for this revision. Mounts are fixed for the lifetime of a machine. Adding or removing mounts on an existing machine through container machine set is deferred because the current key=value last-wins semantics do not fit a repeatable list.

Testing

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

Tested on Apple M5, macOS 26.5, Swift 6.3.3, using a debug build with all CLI and service components built from the same revision.

  • make container: passed and packaged the CLI and service plugins.
  • make test: warnings-as-errors build passed, followed by all 765 non-integration tests.
  • swift test --filter MachineConfigTests: all 22 tests passed.
  • TestCLIMachineCommand/testCreateWithMounts: passed. It verifies that repeated read-write and read-only mount options survive create, persistence, and inspect.
  • TestCLIMachineRuntimeSerial/testUserMountsReadWriteAndReadOnly: passed. It boots the machine, writes from the guest through the read-write mount, verifies the file on the host, and confirms that the read-only mount rejects writes.

The same read-write propagation and read-only enforcement were also verified manually against an isolated application root.

@mareksapota

Copy link
Copy Markdown
Contributor

@danielsyauqi can you describe how to test these changes locally? You didn't check the Tested locally box so I'm not sure if these changes have been actually locally tested. I'm very interested in this feature and I have tried these changes in my local build and I have run into a couple of problems.

  1. The mounts field is always set to [] (an empty array). The values get lost somewhere in encode/decode translation. I tried
    ./bin/container machine create --name test-machine --home-mount=none --mount '/Volumes/src/:/src/' alpine
    
    and
    ./bin/container machine inspect test-machine
    
    The volume/mount is not in the inspect list, and not actually mounted in the machine container.
  2. I tried hard coding the volume, that still didn't get any mounts into the machine. Inspect shows the volume/mount if hard coded, but it does not actually mount anything.

I have also tried it with running a local ./bin/container-apiserver.
Since I'm not familiar with this code base I might be using the local binary wrong. Could you clarify how to test these changes?

@ha-ni-cc

Copy link
Copy Markdown

This feature is great and exactly what I wanted. I hope it continues to be developed to meet the requirements for merging pull requests.

Add a repeatable --mount host:guest[:ro|rw] option to container machine create. Persist validated mounts in the machine boot configuration, expose them through inspect, and add them as virtiofs shares at boot. Include backward-compatible decoding, documentation, and unit coverage.
Remove the unused generic return value so the helper compiles cleanly when warnings are treated as errors.
@danielsyauqi
danielsyauqi force-pushed the feat/machine-user-mounts branch from cee5710 to eb6be3b Compare August 10, 2026 12:58
@danielsyauqi

Copy link
Copy Markdown
Author

@mareksapota Thanks for testing this, and sorry for the slow follow-up.

The important detail is that machine operations go through the launchd-managed machine-apiserver plugin. Starting ./bin/container-apiserver by itself does not replace that plugin, so it is easy to run a newly built CLI against an older machine service.

I rebased the branch and retested it with the CLI and every service plugin built from the same revision. machine inspect reports both mounts correctly, a file written by the guest through the read-write mount appears on the host, and writing through the read-only mount fails with Read-only file system.

Here is the matched-stack procedure I used:

make container
bin/container system stop

TEST_ROOT="$(mktemp -d)"
mkdir -p "$TEST_ROOT/rw" "$TEST_ROOT/ro" "$TEST_ROOT/logs"

bin/container --debug system start \
  --app-root "$TEST_ROOT/app" \
  --install-root "$PWD" \
  --log-root "$TEST_ROOT/logs" \
  --enable-kernel-install \
  --timeout 90

bin/container machine create --no-boot \
  --name test-machine \
  --home-mount=none \
  --mount "$TEST_ROOT/rw:/audit-rw:rw" \
  --mount "$TEST_ROOT/ro:/audit-ro:ro" \
  ghcr.io/linuxcontainers/alpine:3.20

bin/container machine inspect test-machine | jq '.[0].mounts'

bin/container machine run --root -n test-machine -- touch /audit-rw/from-guest
test -f "$TEST_ROOT/rw/from-guest"

# This command should fail with "Read-only file system".
bin/container machine run --root -n test-machine -- touch /audit-ro/blocked

bin/container machine stop test-machine
bin/container machine rm test-machine
bin/container system stop

I also added integration coverage for the create and inspect path plus the actual guest read-write and read-only behavior. If you still get an empty mount list with this setup, please share the output from bin/container system version and bin/container machine inspect test-machine, and I will dig into it. Thanks again for calling out the testing gap.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Request]: User-specified mounts in machines.

4 participants