How to Configure Read-Only Root Filesystems and tmpfs Mounts in Apple Container
Use the --read-only flag to mount the container's root filesystem as immutable and the --tmpfs <path> flag to add writable in-memory directories at specific paths.
The Apple Container CLI provides native support for hardening container security through immutable root filesystems while maintaining flexibility via temporary filesystem mounts. These features allow you to run containers with enhanced security postures without sacrificing the ability to write to specific directories like /tmp or /run. Learning how to configure read-only root filesystems and tmpfs mounts enables you to implement defense-in-depth strategies and improve I/O performance for ephemeral data.
Understanding Read-Only Root Filesystems
When you launch a container with the --read-only flag, the CLI transforms the root mount from read-write to read-only before the container runtime initializes. This prevents any modifications to the container's base image layers during execution.
How the --read-only Flag Works Internally
The flag sets ContainerConfiguration.readOnly = true in Sources/ContainerResource/Container/ContainerConfiguration.swift. During bundle creation, the Bundle.cloneContainerRootFs method (lines 43-48 in Sources/ContainerResource/Container/Bundle.swift) checks this boolean and appends the "ro" mount option:
var mutableFs = fs
if readonly && !mutableFs.options.contains("ro") {
mutableFs.options.append("ro")
}
The resulting Filesystem object is written to the bundle's rootfs configuration, causing the kernel to mount the root partition with read-only constraints. When active, kernel logs will display VFS: Mounted root (ext4 filesystem) readonly… confirming the restriction is in effect.
Implementing tmpfs Mounts
The --tmpfs flag creates ephemeral, memory-backed filesystems that disappear when the container stops. This is ideal for sensitive temporary data or high-speed cache directories that should not persist on disk.
Parsing and Creating tmpfs Filesystems
Command-line arguments pass through Parser.tmpfsMounts (lines 332-338 in Sources/Services/ContainerAPIService/Client/Parser.swift), which instantiates a Filesystem object via the Filesystem.tmpfs factory method (lines 128-131 in Sources/ContainerResource/Container/Filesystem.swift):
let fs = Filesystem.tmpfs(destination: tmpfs, options: [])
This sets type: .tmpfs and source: "tmpfs", generating a mount entry that the runtime service processes as a RAM-based volume. The mount receives default Linux tmpfs treatment, residing entirely in memory with no underlying block device.
Combining Both Options for Secure Containers
You can use both flags simultaneously to create immutable containers with designated writable areas. This pattern follows security best practices by minimizing the attack surface while preserving necessary functionality.
Run a container with a read-only root and multiple tmpfs mounts:
# Mount root as read-only and add writable memory directories
container run --read-only \
--tmpfs /tmp \
--tmpfs /run \
--name secure-demo alpine:latest \
sh -c "echo 'volatile data' > /run/status && cat /run/status"
Verify the configuration inside the container:
# Check root mount options
mount | grep ' / '
# Verify tmpfs mounts
mount | grep tmpfs
Verification and Kernel Logs
After starting a read-only container, confirm the configuration using standard Linux utilities. The mount command will show ro (read-only) in the options for the root filesystem, while tmpfs mounts display type tmpfs.
According to the project documentation in docs/how-to.md (lines 95-98), successful activation generates kernel log entries showing the root filesystem mounted as read-only, providing audit evidence of the security posture.
Summary
- Use
--read-onlyto append the"ro"option to the root filesystem viaContainerConfiguration.readOnlyandBundle.cloneContainerRootFs, preventing modifications to container layers. - Use
--tmpfs <path>to create memory-backed mounts parsed byParser.tmpfsMountsand constructed byFilesystem.tmpfs, providing ephemeral storage without disk I/O. - Combine both flags to achieve hardened container environments where the base image remains immutable but specific paths remain writable in RAM.
- Verify configuration using
mountcommands and check kernel logs forVFS: Mounted root... readonlymessages.
Frequently Asked Questions
Can I use --tmpfs without --read-only?
Yes, tmpfs mounts function independently of the root filesystem's read-only status. You can add memory-backed directories to standard read-write containers using container run --tmpfs /path without affecting the root mount's mutability. This is useful for improving performance of cache directories without enforcing full immutability.
What happens if I try to write to the root filesystem with --read-only enabled?
The kernel will reject write operations with a "Read-only file system" error. Any process attempting to modify files in the root hierarchy (such as installing packages or writing to /etc) will fail unless the specific path is covered by a writable overlay, volume mount, or tmpfs mount.
How do I specify mount options for tmpfs directories?
The current implementation in Sources/ContainerResource/Container/Filesystem.swift creates tmpfs mounts with empty options lists by default. Advanced options like size limits or uid mappings specified via the CLI are parsed through Parser.tmpfsMounts but currently result in default tmpfs configurations. Check the specific version of Sources/Services/ContainerAPIService/Client/Parser.swift for your release to verify supported option parsing.
Where are the tmpfs mounts actually created in the source code?
The Filesystem structures representing tmpfs mounts are generated in Sources/Services/ContainerAPIService/Client/Parser.swift and defined in Sources/ContainerResource/Container/Filesystem.swift. These are passed to the runtime service (RuntimeService.swift), which translates them into actual Linux mount system calls when the container starts.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →