Skip to content

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

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

danielsyauqi wants to merge 2 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.

@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.

@danielsyauqi

Copy link
Copy Markdown
Author

Hi @katiewasnothere and @mareksapota! This PR adds user-specified mounts for container machines. It has been rebased and now includes matched-stack integration coverage plus manual guest read-write/read-only verification. Could you take a look when you have time? Thanks!

@conklech

Copy link
Copy Markdown

I've been using this PR for a few days without any problems. This seems like it should be an included feature.

@danielsyauqi

Copy link
Copy Markdown
Author

I've been using this PR for a few days without any problems. This seems like it should be an included feature.

Great! Hope the maintainer can approve this PR feature to be deployed 😢

@jcbl1

jcbl1 commented Sep 9, 2026

Copy link
Copy Markdown

Sorry if this isn't the right place or time to ask. Is it possible to add additional binds to an existing machine?

@danielsyauqi

Copy link
Copy Markdown
Author

Yes, this should be possible without recreating the machine, although it would require stopping and restarting it, the mounts are applied at boot rather than hot-plugged into a running machine.

I can look into adding support for updating an existing machine if the maintainers would prefer it in this PR.

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 and integration coverage.
@danielsyauqi
danielsyauqi force-pushed the feat/machine-user-mounts branch from 2332c52 to aba26f5 Compare September 14, 2026 10:36
@danielsyauqi

Copy link
Copy Markdown
Author

Hi @jglogan, could you help triage this PR or point me to the appropriate reviewer? It implements #1805 and is rebased onto current main.

The branch now complies with the signed-commit requirement, and the resulting tree is identical to the version covered by the unit, integration, and manual runtime tests described above.

I’m happy to adjust the create-time-only scope if the team prefers.

Thanks!

@danielsyauqi

Copy link
Copy Markdown
Author

@katiewasnothere @egernst, could either of you help review or route this PR when you have time? The multiple-mount capability being independently requested in #2278.

The scope remains limited to create-time, repeatable --mount configuration. The branch is current with main and mergeable, with unit, integration, and manual read-write/read-only verification documented above.

I’m happy to adjust the scope or implementation if the maintainers prefer a different direction.

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.

5 participants