# How to Use Linux Capabilities (`--cap-add` / `--cap-drop`) for Enhanced Container Security

> Enhance container security by mastering Linux capabilities with --cap-add and --cap-drop. Run containers with minimal privileges, avoiding full root access.

- Repository: [Apple/container](https://github.com/apple/container)
- Tags: how-to-guide
- Published: 2026-07-02

---

**Apple Container provides `--cap-add` and `--cap-drop` CLI flags that let you grant or remove specific Linux kernel capabilities, allowing you to run containers with minimal privileges instead of full root access.**

Linux capabilities are fine-grained permissions that replace the traditional all-or-nothing root privilege model. By leveraging the capability system implemented in the `apple/container` repository, you can significantly reduce your container's attack surface while retaining only the specific privileges your workload requires.

## How Capability Flags Are Parsed

When you execute commands like `container run --cap-add NET_ADMIN --cap-drop CHOWN`, the CLI forwards these strings to the **ContainerAPIService**. The parsing logic resides in [`Sources/Services/ContainerAPIService/Client/Parser.swift`](https://github.com/apple/container/blob/main/Sources/Services/ContainerAPIService/Client/Parser.swift).

The `capabilities()` method normalizes each capability name to upper-case and prepends the `CAP_` prefix when missing. It validates entries against the `CapabilityName` enum, which mirrors the kernel's official list, and returns two arrays—`capAdd` and `capDrop`—containing the normalized capability strings.

This validation occurs in the parser's capabilities method (lines 1023-1055 in [`Parser.swift`](https://github.com/apple/container/blob/main/Parser.swift)), ensuring that only valid kernel capabilities are passed to the runtime.

## Default Capability Set

Newly created containers start with a restricted whitelist of capabilities rather than the full root set. According to the implementation documentation, the default set includes:

- `CAP_AUDIT_WRITE`
- `CAP_CHOWN`
- `CAP_DAC_OVERRIDE`
- `CAP_FOWNER`
- `CAP_FSETID`
- `CAP_KILL`
- `CAP_MKNOD`
- `CAP_NET_BIND_SERVICE`
- `CAP_NET_RAW`
- `CAP_SETFCAP`
- `CAP_SETGID`
- `CAP_SETPCAP`
- `CAP_SETUID`
- `CAP_SYS_CHROOT`

This whitelist is defined in the how-to guide section on controlling Linux capabilities, ensuring containers start with minimal privileges.

## Order of Operations: Drops Before Adds

The runtime processes capability modifications in a specific sequence: **drops execute first, then adds**. This ordering creates predictable security boundaries.

Consider these scenarios:

- `--cap-drop ALL --cap-add ALL` results in all capabilities being granted because the add operation overwrites the drop.
- `--cap-drop ALL --cap-add SETUID --cap-add SETGID` leaves only `CAP_SETUID` and `CAP_SETGID` active, creating a least-privilege environment.

This processing order is documented in the how-to guide and enforced by the runtime implementation.

## Practical Security Patterns

Use these patterns to harden your containers based on specific workload requirements:

**Least-Privilege Containers**
Use `--cap-drop ALL` followed by explicit `--cap-add` flags for only the capabilities your application needs. This removes every default capability and grants minimal privileges.

**Granting Single Extra Privileges**
Add specific capabilities to the default set with `--cap-add NET_ADMIN` or similar flags when the default whitelist is insufficient but full privileges are unnecessary.

**Debugging and Development**
Temporarily use `--cap-drop ALL --cap-add ALL` to grant full capabilities, then progressively restrict the set using `--cap-drop` flags to identify minimum required permissions.

**Privileged Workloads**
For network appliances or system-level tools, `--cap-add ALL` provides the complete kernel capability set equivalent to Docker's `--privileged` flag.

## Code Examples

Run these commands to implement capability-based security:

```bash

# Start with default capability set

container run --rm alpine:latest uname -a

# Remove a specific capability from defaults

container run --rm --cap-drop CHOWN alpine:latest touch /tmp/file

# Grant network administration privileges

container run --rm --cap-add NET_ADMIN alpine:latest ip link set eth0 up

# Minimal privilege container with only SETUID/SETGID

container run --rm \
  --cap-drop ALL \
  --cap-add SETUID \
  --cap-add SETGID \
  alpine:latest id

# Full privileges (equivalent to --privileged)

container run --rm --cap-add ALL alpine:latest sh -c "cat /proc/1/status"

```

**Programmatic Usage (Swift)**

When building tools that interact with the Container API, construct capability arrays as follows:

```swift
let capAdd = ["NET_ADMIN", "SYS_TIME"]
let capDrop = ["MKNOD"]

let (normAdd, normDrop) = try Parser.capabilities(capAdd: capAdd, capDrop: capDrop)

// normAdd contains: ["CAP_NET_ADMIN", "CAP_SYS_TIME"]
// normDrop contains: ["CAP_MKNOD"]

```

The `Parser.capabilities()` method in [`Parser.swift`](https://github.com/apple/container/blob/main/Parser.swift) handles normalization automatically, converting shorthand names like `NET_ADMIN` to the canonical `CAP_NET_ADMIN` format required by the kernel.

## Key Implementation Files

The capability system spans these source files in the `apple/container` repository:

- **[`Sources/Services/ContainerAPIService/Client/Parser.swift`](https://github.com/apple/container/blob/main/Sources/Services/ContainerAPIService/Client/Parser.swift)** – Implements the `capabilities()` method that validates and normalizes capability names (lines 1023-1055).
- **[`Sources/Services/ContainerAPIService/Client/Flags.swift`](https://github.com/apple/container/blob/main/Sources/Services/ContainerAPIService/Client/Flags.swift)** – Declares the CLI flag definitions that map user input to the parser.
- **[`docs/how-to.md`](https://github.com/apple/container/blob/main/docs/how-to.md)** – Contains the default capability whitelist and explains ordering semantics.
- **[`docs/command-reference.md`](https://github.com/apple/container/blob/main/docs/command-reference.md)** – Documents accepted flag values including `ALL`, `CAP_NET_RAW`, and un-prefixed variants like `NET_RAW`.

## Summary

- Apple Container implements Linux capabilities through `--cap-add` and `--cap-drop` flags defined in [`Flags.swift`](https://github.com/apple/container/blob/main/Flags.swift) and processed by [`Parser.swift`](https://github.com/apple/container/blob/main/Parser.swift).
- The parser normalizes capability names (e.g., `NET_ADMIN` → `CAP_NET_ADMIN`) and validates them against the `CapabilityName` enum.
- Containers start with a restricted default set of 14 capabilities rather than full root privileges.
- The runtime applies drops before adds, allowing patterns like `--cap-drop ALL --cap-add SETUID` to create minimal privilege environments.
- Accepted values include specific capability names (with or without `CAP_` prefix) or `ALL` for the complete set.

## Frequently Asked Questions

### What happens if I specify both `--cap-add ALL` and `--cap-drop ALL`?

When both flags are present, the drop operation executes first, removing all capabilities, then the add operation restores them. Because the runtime processes drops before adds, `--cap-drop ALL --cap-add ALL` results in a container with full capabilities, while the reverse order (if explicitly forced) would differ. According to the how-to documentation, this ordering allows you to start from zero privileges and build up specific permissions.

### Can I use capability names without the CAP_ prefix?

Yes. The `capabilities()` method in [`Parser.swift`](https://github.com/apple/container/blob/main/Parser.swift) automatically prepends `CAP_` to any capability name that lacks it. You can specify either `NET_ADMIN` or `CAP_NET_ADMIN`; both normalize to the canonical `CAP_NET_ADMIN` string validated against the `CapabilityName` enum.

### Which capabilities should I drop for a production web server?

For a standard web server, start with `--cap-drop ALL` then add only `CAP_NET_BIND_SERVICE` (to bind ports below 1024) and `CAP_SETUID`/`CAP_SETGID` if the process drops privileges after binding. Remove `CAP_NET_ADMIN`, `CAP_SYS_MODULE`, and `CAP_MKNOD` to prevent network reconfiguration, kernel module loading, and device node creation if the container is compromised.

### How do capabilities differ from running as non-root?

Running as non-root (via `--user`) restricts the UID/GID, but the process may still retain Linux capabilities that allow privilege escalation. Combining non-root execution with `--cap-drop ALL` ensures the process cannot perform privileged operations even if exploited. Capabilities provide fine-grained control over what root (or any user) can do, while UID restrictions control identity.