How to Manage Linux Capabilities in Apple Container: `--cap-add` and `--cap-drop` Explained
Use --cap-add to grant specific Linux capabilities and --cap-drop to remove them from the default set, with both flags supporting case-insensitive names, optional CAP_ prefixes, and the special ALL token to toggle the complete capability set.
The apple/container repository provides a lightweight container runtime that executes workloads inside virtual machines with a reduced Linux capability set for security hardening. Understanding how to manage Linux capabilities using --cap-add and --cap-drop commands allows you to implement precise least-privilege access controls while ensuring your applications have the specific kernel permissions they require.
Understanding the Default Capability Set
By default, containers run with a restricted subset of Linux capabilities rather than the full root privilege set. The default configuration includes capabilities such as CAP_NET_RAW and CAP_SETUID, but excludes many administrative privileges. You can view the complete default list in the project documentation at docs/how-to.md (lines 70-76).
This reduced set ensures that containers operate with minimal privileges, following the principle of least privilege. When your workload requires additional kernel permissions—such as network administration or process ownership changes—you must explicitly grant those capabilities using the command-line interface.
Command-Line Syntax and Normalization
The container runtime exposes two primary flags for capability management, defined in Sources/Services/ContainerAPIService/Client/Flags.swift (lines 31-41):
--cap-add <cap>: Adds a capability to the container's effective set--cap-drop <cap>: Removes a capability from the default set
Both flags accept capability names in a flexible format. The parser in Sources/Services/ContainerAPIService/Client/Parser.swift (lines 1009-1024) automatically normalizes input by converting strings to uppercase and prepending CAP_ if missing. For example, net_admin, NET_ADMIN, and CAP_NET_ADMIN are all valid representations of the same capability.
The parser validates each capability against the CapabilityName enum (lines 1019-1022 in Parser.swift). If you provide an invalid capability name, the command aborts immediately with an "invalid argument" error.
Processing Order and the ALL Token
Capability modifications follow a specific processing sequence that affects the final permission set. According to the documentation in docs/how-to.md (lines 98-100), the runtime processes all --cap-drop entries first, then applies all --cap-add entries. This ordering means that adds override drops when both flags target the same capability or use the ALL token.
The special token ALL represents the complete capability set:
--cap-add ALL: Grants every available Linux capability--cap-drop ALL: Removes all capabilities, starting from an empty set
Because drops are processed before adds, the combination --cap-drop ALL --cap-add ALL results in a container with full capabilities, not an empty set.
Practical Usage Examples
Granting Additional Capabilities
Add specific capabilities when your application requires elevated privileges beyond the defaults:
# Add network administration capability
container run --cap-add NET_ADMIN alpine ip link set lo down
# Add multiple capabilities for user/group management
container run --cap-add SETUID --cap-add SETGID alpine id
Removing Default Capabilities
Drop specific capabilities to further restrict container permissions:
# Remove the chown capability to prevent ownership changes
container run --cap-drop CHOWN alpine sh -c 'touch /tmp/file'
Implementing Least Privilege
Start with no capabilities and add only what is necessary:
# Drop all defaults, then add back only specific requirements
container run --cap-drop ALL --cap-add SETUID --cap-add SETGID alpine id
Using Deny-List Approach
Grant all capabilities except specific restricted ones:
# Give all capabilities except network administration
container run --cap-add ALL --cap-drop NET_ADMIN alpine sh
Understanding Order Effects
Demonstrate how processing order affects the final capability set:
# Results in ALL capabilities because add is processed after drop
container run --cap-drop ALL --cap-add ALL alpine sh
Implementation Details
The capability management system spans several key source files:
Sources/Services/ContainerAPIService/Client/Flags.swift: Defines the CLI flag structure for--cap-addand--cap-drop(lines 31-41)Sources/Services/ContainerAPIService/Client/Parser.swift: Implements the parsing logic, normalization rules, and validation against theCapabilityNameenum (lines 1009-1024)docs/how-to.md: Documents the default capability set and processing order semantics (lines 70-76, 98-100)docs/command-reference.md: Provides the command reference table for these flags (lines 47-50)
Summary
- The
apple/containerruntime runs workloads in lightweight VMs with a reduced default capability set including CAP_NET_RAW and CAP_SETUID. - Use
--cap-addto grant capabilities and--cap-dropto remove them; both support case-insensitive names with optionalCAP_prefixes. - The parser validates capabilities against the
CapabilityNameenum inParser.swiftand aborts on invalid input. - Processing order matters: All
--cap-dropentries are processed before--cap-addentries, meaning additions override removals. - The
ALLtoken represents the complete capability set and can be used with either flag to grant or remove every capability.
Frequently Asked Questions
What capabilities are included in the default set?
The default capability set includes commonly required permissions such as CAP_NET_RAW for raw socket access and CAP_SETUID for changing user IDs, but excludes administrative capabilities like CAP_NET_ADMIN or CAP_SYS_ADMIN. The complete list is documented in docs/how-to.md lines 70-76 of the apple/container repository.
Can I use lowercase when specifying capability names?
Yes. The parser automatically normalizes capability names to uppercase and adds the CAP_ prefix if you omit it. You can specify net_admin, NET_ADMIN, or CAP_NET_ADMIN interchangeably, and the parser in Sources/Services/ContainerAPIService/Client/Parser.swift will handle them identically.
What happens if I combine --cap-add and --cap-drop in the same command?
The runtime processes all --cap-drop entries first, then applies all --cap-add entries. This means that if you specify --cap-drop ALL --cap-add NET_ADMIN, the container starts with no capabilities, then gains only CAP_NET_ADMIN. Conversely, --cap-drop ALL --cap-add ALL results in a container with all capabilities because the final add operation restores the full set.
How do I run a container with absolutely no capabilities?
Use the ALL token with --cap-drop to start from an empty capability set. The command container run --cap-drop ALL alpine sh creates a container with no Linux capabilities, providing the maximum security isolation. You can then selectively add back specific capabilities using --cap-add if your workload requires them.
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 →