Skip to content

Conversation

@radoxtech
Copy link
Contributor

Type of Change

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

Motivation and Context

Observed Problem

When mounting a volume to a container running as a non-root user (e.g., UID 1000), attempts to write files inside the container fail with permission denied errors. Application file operations report "Operation not permitted," even though the volume is mounted correctly. Forcing root execution inside the container resolves it, but breaks security guidelines.

Consequence

Non-root containers cannot use volumes for writable storage, limiting persistent data workflows. Developers must either run insecurely as root, manually adjust ownership post-mount (e.g., via chown -R), or avoid volumes entirely—disrupting OCI image compatibility and production-like testing on macOS.

Root Cause

Apple Container initializes volumes with root privileges on the host during creation. When mounted into a non-root container, the host filesystem permissions do not match the container's runtime UID/GID, preventing writes without escalation.

Fix Implemented

Apple Container now automatically adjusts volume ownership before mounting as follows:

  • Detects if the volume was already mounted in the past (via mount history); if so, skips to avoid overwriting user data. If zero prior mounts (new volume), aligns ownership to the container's runtime UID/GID.
  • Skips if running as root (no issue).
  • Sources UID/GID from container's default runtime user or CLI flags at creation.

This ensures non-root containers can write to volumes seamlessly without image modifications or manual intervention, aligning with OCI standards.

Reproduction Steps (Apple Container CLI 0.5.0)

Create a simple non-root container image (e.g., based on Alpine with user 1000):
Dockerfile

FROM alpine:latest
RUN addgroup -g 1000 appgroup && adduser -u 1000 -G appgroup -D appuser
USER appuser
CMD ["sh"]

Build the image and create a named volume:

container build -t test-nonroot .
container volume create myvol

Run with the volume:

container run -it --name test-vol -v myvol:/data test-nonroot sh

From inside the container, attempt to write a file to /data.
Observed: Fails with "Permission denied."

Testing

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

Copy link
Contributor

@jglogan jglogan left a comment

Choose a reason for hiding this comment

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

Just got started on this and wanted to post a couple comments.

}

// Skip if running as root (no fix needed, root can write anywhere)
guard uid != 0 else {
Copy link
Contributor

Choose a reason for hiding this comment

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

Would it still make sense to update if uid == 0 && gid != 0?

/// - mountCount > 0: Already been mounted/used - DO NOT change ownership (data may exist)
///
/// This function is called BEFORE the volume is mounted, so new volumes will have mountCount=0.
private func isFirstMount(volumeImagePath: SystemPackage.FilePath) -> Bool {
Copy link
Contributor

Choose a reason for hiding this comment

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

@dcantah Thinking about this in the context of storage enhancements that use the ext4 library.

Say we have a container volume create --from path command that creates and populates a filesystem in a single go. We'd need that command to know about this test and set the mount count to nonzero.

Are there other use cases where we want to setup uid/gid on a volume in instances other than this specific case (first mount, empty volume)?

For the sake of argument, what are our alternatives? Could the guest agent could perform this setup after mounting volumes and before starting the workload?

Also want to call out #729 which I don't see us doing in the immediate future but could have some interaction with this - if the volume is empty/first mount, what action(s) do we take and where?

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.

2 participants