Topic/docs refactor (#2032)

Signed-off-by: Eric Ernst <eric_ernst@apple.com>
This commit is contained in:
Eric Ernst
2026-08-11 09:34:53 -07:00
committed by GitHub
parent ff5aa8a03b
commit 875d80ba07
16 changed files with 1362 additions and 824 deletions
+10
View File
@@ -31,6 +31,16 @@ Start the system service with:
container system start
```
### Run your first container
```bash
container run --rm alpine echo hello
```
This pulls the `alpine` image, runs it in a lightweight Linux VM, prints `hello`, and
removes the container when it exits. See the [tutorial](./docs/tutorials/start-here.md)
for a fuller walkthrough that builds and publishes an image of your own.
### Upgrade or downgrade
For both upgrading and downgrading, you can manually download and install the signed installer package by following the steps from [initial install](#initial-install) or use the `update-container.sh` script (installed to `/usr/local/bin`).
+6 -18
View File
@@ -619,7 +619,7 @@ container image save [--arch <arch>] [--os <os>] --output <output> [--platform <
### `container image load`
Loads images from a tar archive created by `image save`. The tar file must be specified via `--input`.
Loads images from a tar archive created by `image save`. Specify the tar file with `--input`.
**Usage**
@@ -911,22 +911,10 @@ container volume create --opt journal=journal --opt size=10g myvolume
**Anonymous Volumes**
Anonymous volumes are auto-created when using `-v /path` or `--mount type=volume,dst=/path` without specifying a source. They use UUID-based naming (`anon-{36-char-uuid}`):
```bash
# Creates anonymous volume
container run -v /data alpine
# Reuse anonymous volume by ID
VOL=$(container volume list -q | grep anon)
container run -v $VOL:/data alpine
# Manual cleanup
container volume rm $VOL
```
> [!NOTE]
> Unlike Docker, anonymous volumes do NOT auto-cleanup with `--rm`. Manual deletion is required.
Using `-v /path` or `--mount type=volume,dst=/path` without a source auto-creates a
named volume for you, tagged with the `com.apple.container.resource.anonymous` label.
See [Mounts and volumes](./volumes.md#anonymous-volumes) for how to find and clean
these up.
### `container volume delete (rm)`
@@ -1012,7 +1000,7 @@ The registry commands manage authentication and defaults for container registrie
### `container registry login`
Authenticates with a registry. Credentials can be provided interactively or via flags. The login is stored for reuse by subsequent commands.
Authenticates with a registry. You can provide credentials interactively or with flags. The login is stored for reuse by subsequent commands.
**Usage**
+72
View File
@@ -0,0 +1,72 @@
# Inspecting containers and images
Get detailed, machine-readable information about your containers and images.
## Get container or image details
`container image list` and `container list` provide basic information for all of your images and containers. You can also use `list` and `inspect` commands to print detailed machine-readable output for resources.
Use the `inspect` command and send the result to the `jq` command to get pretty-printed JSON for the images or containers that you specify:
<pre>
% container image inspect web-test | jq
[
{
"configuration": {
"name": "web-test:latest",
...
},
"variants": [
{
"platform": {
"os": "linux",
"architecture": "arm64"
},
"config": {
"created": "2025-05-08T22:27:23Z",
"architecture": "arm64",
...
% container inspect my-web-server | jq
[
{
"configuration": {
"mounts": [],
"id": "my-web-server",
"resources": {
"cpus": 4,
"memoryInBytes": 1073741824,
},
...
},
"status": {
"state": "running",
"networks": [
{
"ipv4Address": "192.168.64.3/24",
"ipv4Gateway": "192.168.64.1",
"hostname": "my-web-server.test.",
"network": "default"
}
],
...
}
}
]
</pre>
Use the `list` command with the `--format` option to display information for all images or containers. In this example, the `--all` option shows stopped as well as running containers, and `jq` selects the IP address for each running container:
<pre>
% container ls --format json --all | jq '.[] | select ( .status.state == "running" ) | [ .configuration.id, .status.networks[0].ipv4Address ]'
[
"my-web-server",
"192.168.64.3/24"
]
[
"buildkit",
"192.168.64.2/24"
]
</pre>
See [Networking](./networking.md) for how to publish ports, reach the host from a
container, set a custom MAC address, and create isolated networks.
+48 -1
View File
@@ -9,6 +9,43 @@ For a guided walk-through on setting default values, see [Container system confi
Source of truth: [`Sources/ContainerPersistence/ContainerSystemConfig.swift`](../Sources/ContainerPersistence/ContainerSystemConfig.swift).
## Viewing your configuration
Use `container system property list` (alias `ls`) to print the merged configuration
the `container` service is actually using — combining your `config.toml` with
hardcoded defaults for anything you haven't set:
```console
% container system property list
[build]
cpus = 2
memory = "2048mb"
rosetta = true
image = "ghcr.io/apple/container-builder-shim/builder:0.13.1"
[container]
cpus = 4
memory = "1gb"
[dns]
domain = "test"
[kernel]
binaryPath = "opt/kata/share/kata-containers/vmlinux-6.18.15-186"
url = "https://github.com/kata-containers/kata-containers/releases/download/3.28.0/kata-static-3.28.0-arm64.tar.zst"
digest = "sha256:f63d54507d1f18635d94475077e4c2330de4d8e05cedf25f7c38f063b0e66a91"
[network]
[registry]
domain = "docker.io"
[vminit]
image = "ghcr.io/apple/containerization/vminit:0.34.0"
```
Pass `--format json` for machine-readable output.
## Top-level schema
```toml
@@ -35,6 +72,16 @@ Resources and image used for the builder VM that runs `container build`.
| `memory` | [MemorySize](#memorysize-format) | `"2048mb"` | RAM allocation for the builder VM. |
| `image` | `String` | `ghcr.io/apple/container-builder-shim/builder:<tag>` | Reference for the builder image. The tag segment is taken from the project's bundled `container-builder-shim` version. |
To prevent the use of Rosetta translation during container builds on a Mac with Apple
silicon, set `rosetta = false`:
```toml
[build]
rosetta = false
```
This ensures builds only produce native arm64 images, with no x86_64 emulation.
## `[container]`
Defaults applied when `container run` / `container create` is invoked without `--cpus` or `--memory`.
@@ -48,7 +95,7 @@ Defaults applied when `container run` / `container create` is invoked without `-
| Key | Type | Default | Description |
|----------|-----------|---------|----------------------------------------------------------------------------|
| `domain` | `String?` | unset | Local DNS domain appended to container hostnames (e.g. `"test"` makes `my-web-server` resolvable as `my-web-server.test`). When unset, no domain is appended. |
| `domain` | `String?` | unset | Local DNS domain appended to container hostnames (e.g. `"test"` makes `my-web-server` resolvable as `my-web-server.test`). When unset, no domain is appended. See [Networking: Set up DNS-based container names](./networking.md#set-up-dns-based-container-names) for the full walkthrough. |
## `[kernel]`
+88
View File
@@ -0,0 +1,88 @@
# Host integration
Bridge your container and your Mac: forward your SSH agent in, or reach a host
service from inside a container.
## Mount your host SSH authentication socket in your container
Use the `--ssh` option to mount the macOS SSH authentication socket into your container, so that you can clone private git repositories and perform other tasks requiring passwordless SSH authentication.
When you use `--ssh`, it performs the equivalent of the options `--volume "${SSH_AUTH_SOCK}:/var/host-services/ssh-auth.sock" --env SSH_AUTH_SOCK=/var/host-services/ssh-auth.sock"`. The added benefit of `--ssh` is that when you stop your container, log out, log back in, and restart your container, the system automatically updates the target path for the socket mount to the new value of `SSH_AUTH_SOCK`, so that socket forwarding continues to function.
```console
% container run -it --rm --ssh alpine:latest sh
/ # env
SHLVL=1
HOME=/root
TERM=xterm
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
SSH_AUTH_SOCK=/var/host-services/ssh-auth.sock
PWD=/
/ # apk add openssh-client
(1/6) Installing openssh-keygen (10.0_p1-r7)
(2/6) Installing ncurses-terminfo-base (6.5_p20250503-r0)
(3/6) Installing libncursesw (6.5_p20250503-r0)
(4/6) Installing libedit (20250104.3.1-r1)
(5/6) Installing openssh-client-common (10.0_p1-r7)
(6/6) Installing openssh-client-default (10.0_p1-r7)
Executing busybox-1.37.0-r18.trigger
OK: 12 MiB in 22 packages
/ # ssh-add -l
...auth key output...
/ # apk add git
(1/12) Installing brotli-libs (1.1.0-r2)
(2/12) Installing c-ares (1.34.5-r0)
(3/12) Installing libunistring (1.3-r0)
(4/12) Installing libidn2 (2.3.7-r0)
(5/12) Installing nghttp2-libs (1.65.0-r0)
(6/12) Installing libpsl (0.21.5-r3)
(7/12) Installing zstd-libs (1.5.7-r0)
(8/12) Installing libcurl (8.14.1-r1)
(9/12) Installing libexpat (2.7.1-r0)
(10/12) Installing pcre2 (10.43-r1)
(11/12) Installing git (2.49.1-r0)
(12/12) Installing git-init-template (2.49.1-r0)
Executing busybox-1.37.0-r18.trigger
OK: 24 MiB in 34 packages
/ # git clone git@github.com:some-org/some-private-repo.git
Cloning into 'some-private-repo'...
...
```
## Access a host service from a container
> [!IMPORTANT]
> Due to macOS security constraints around packet filter rules, this feature has limited functionality:
> - Creating a localhost domain disables Private Relay.
> - The local domain packet filter rule is removed on a restart.
Create a DNS domain with `--localhost <ipv4-address>` to make a domain used by a container to access a host service. Any IPv4 address can be used as `<ipv4-address>`, which will be assigned to the domain name in container.
Choose an IP address that is least likely to conflict with any networks or reserved IP addresses in your environment. Reasonably safe address ranges include:
- The documentation ranges 192.0.2.0/24, 198.51.100.0/24, and 203.0.113.0/24.
- The 172.16.0.0/12 private range.
To connect a host HTTP server from a container, run:
```bash
mkdir -p /tmp/test; cd /tmp/test; echo "hello" > index.html
python3 -m http.server 8000 --bind 127.0.0.1
```
Create a domain for host connection:
```bash
sudo container system dns create host.container.internal --localhost 203.0.113.113
```
Test access to the host HTTP server from a container:
```console
% container run -it --rm alpine/curl curl http://host.container.internal:8000
hello
```
This uses the same underlying DNS mechanism described in [Networking: Set up DNS-based
container names](./networking.md#set-up-dns-based-container-names), just with
`--localhost` pointing the domain at a host address instead of a container.
+16 -803
View File
@@ -5,806 +5,19 @@
>
> Example: [release 0.4.1 tag](https://github.com/apple/container/tree/0.4.1)
How to use the features of `container`.
## Configure memory and CPUs for your containers
Since the containers created by `container` are lightweight virtual machines, consider the needs of your containerized application when you use `container run`. The `--memory` and `--cpus` options allow you to override the default memory and CPU limits for the virtual machine. The default values are 1 gigabyte of RAM and 4 CPUs. You can use abbreviations for memory units; for example, to run a container for image `big` with 8 CPUs and 32 GiBytes of memory, use:
```bash
container run --rm --cpus 8 --memory 32g big
```
## Configure memory and CPUs for large builds
When you first run `container build`, `container` starts a *builder*, which is a utility container that builds images from your `Dockerfile`s. As with anything you run with `container run`, the builder runs in a lightweight virtual machine, so for resource-intensive builds, you may need to increase the memory and CPU limits for the builder VM.
By default, the builder VM receives 2 GiBytes of RAM and 2 CPUs. You can change these limits by starting the builder container before running `container build`:
```bash
container builder start --cpus 8 --memory 32g
```
If your builder is already running and you need to modify the limits, just stop, delete, and restart the builder:
```bash
container builder stop
container builder delete
container builder start --cpus 8 --memory 32g
```
## Share host files with your container
With the `--volume` option of `container run`, you can share data between the host system and one or more containers, and you can persist data across multiple container runs. The volume option allows you to mount a folder on your host to a filesystem path in the container.
This example mounts a folder named `assets` on your Desktop to the directory `/content/assets` in a container:
<pre>
% ls -l ~/Desktop/assets
total 8
-rw-r--r--@ 1 fido staff 2410 May 13 18:36 link.svg
% container run --volume ${HOME}/Desktop/assets:/content/assets docker.io/python:alpine ls -l /content/assets
total 4
-rw-r--r-- 1 root root 2410 May 14 01:36 link.svg
%
</pre>
The argument to `--volume` in the example consists of the full pathname for the host folder and the full pathname for the mount point in the container, separated by a colon.
The `--mount` option uses a comma-separated `key=value` syntax to achieve the same result:
<pre>
% container run --mount source=${HOME}/Desktop/assets,target=/content/assets docker.io/python:alpine ls -l /content/assets
total 4
-rw-r--r-- 1 root root 2410 May 14 01:36 link.svg
%
</pre>
## Build and run a multiplatform image
Using the [project from the tutorial example](./tutorials/start-here.md#set-up-a-simple-project), you can create an image to use both on Apple silicon Macs and on x86-64 servers.
When building the image, just add `--arch` options that direct the builder to create an image supporting both the `arm64` and `amd64` architectures:
```bash
container build --arch arm64 --arch amd64 --tag registry.example.com/fido/web-test:latest --file Dockerfile .
```
Try running the command `uname -a` with the `arm64` variant of the image to see the system information that the virtual machine reports:
<pre>
% container run --arch arm64 --rm registry.example.com/fido/web-test:latest uname -a
Linux 7932ce5f-ec10-4fbe-a2dc-f29129a86b64 6.1.68 #1 SMP Mon Mar 31 18:27:51 UTC 2025 aarch64 GNU/Linux
%
</pre>
When you run the command with the `amd64` architecture, the x86-64 version of `uname` runs under Rosetta translation, so that you will see information for an x86-64 system:
<pre>
% container run --arch amd64 --rm registry.example.com/fido/web-test:latest uname -a
Linux c0376e0a-0bfd-4eea-9e9e-9f9a2c327051 6.1.68 #1 SMP Mon Mar 31 18:27:51 UTC 2025 x86_64 GNU/Linux
%
</pre>
The command to push your multiplatform image to a registry is no different than that for a single-platform image:
```bash
container image push registry.example.com/fido/web-test:latest
```
## Get container or image details
`container image list` and `container list` provide basic information for all of your images and containers. You can also use `list` and `inspect` commands to print detailed machine-readable output for resources.
Use the `inspect` command and send the result to the `jq` command to get pretty-printed JSON for the images or containers that you specify:
<pre>
% container image inspect web-test | jq
[
{
"name": "web-test:latest",
"variants": [
{
"platform": {
"os": "linux",
"architecture": "arm64"
},
"config": {
"created": "2025-05-08T22:27:23Z",
"architecture": "arm64",
...
% container inspect my-web-server | jq
[
{
"status": "running",
"networks": [
{
"address": "192.168.64.3/24",
"gateway": "192.168.64.1",
"hostname": "my-web-server.test.",
"network": "default"
}
],
"configuration": {
"mounts": [],
"hostname": "my-web-server",
"id": "my-web-server",
"resources": {
"cpus": 4,
"memoryInBytes": 1073741824,
},
...
</pre>
Use the `list` command with the `--format` option to display information for all images or containers. In this example, the `--all` option shows stopped as well as running containers, and `jq` selects the IP address for each running container:
<pre>
% container ls --format json --all | jq '.[] | select ( .status == "running" ) | [ .configuration.id, .networks[0].address ]'
[
"my-web-server",
"192.168.64.3/24"
]
[
"buildkit",
"192.168.64.2/24"
]
</pre>
## Forward traffic from `localhost` to your container
Use the `--publish` option to forward TCP or UDP traffic from your loopback IP to the container you run. The option value has the form `[host-ip:]host-port:container-port[/protocol]`, where protocol may be `tcp` or `udp`, case insensitive.
If your container attaches to multiple networks, the ports you publish forward to the IP address of the interface attached to the first network.
To forward requests from port 8080 on the IPv4 loopback IP to a NodeJS webserver on container port 8000, run:
```bash
container run -d --rm -p 127.0.0.1:8080:8000 node:latest npx http-server -a :: -p 8000
```
Test access using `curl`:
```console
% curl http://127.0.0.1:8080
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<title>Index of /</title>
...
<br><address>Node.js v25.2.1/ <a href="https://github.com/http-party/http-server">http-server</a> server running @ 127.0.0.1:8080</address>
</body></html>
```
To forward requests from port 8080 on the IPv6 loopback IP to a NodeJS webserver on container port 8000, run:
```bash
container run -d --rm -p '[::1]:8080:8000' node:latest npx http-server -a :: -p 8000
```
Test access using `curl`:
```console
% curl -6 'http://[::1]:8080'
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<title>Index of /</title>
...
<br><address>Node.js v25.2.1/ <a href="https://github.com/http-party/http-server">http-server</a> server running @ [::1]:8080</address>
</body></html>
```
## Access a host service from a container
> [!IMPORTANT]
> Due to macOS security constraints around packet filter rules, this feature has limited functionality:
> - Creating a localhost domain disables Private Relay.
> - The local domain packet filter rule is removed on a restart.
Create a DNS domain with `--localhost <ipv4-address>` to make a domain used by a container to access a host service. Any IPv4 address can be used as `<ipv4-address>`, which will be assigned to the domain name in container.
Choose an IP address that is least likely to conflict with any networks or reserved IP addresses in your environment. Reasonably safe address ranges include:
- The documentation ranges 192.0.2.0/24, 198.51.100.0/24, and 203.0.113.0/24.
- The 172.16.0.0/12 private range.
To connect a host HTTP server from a container, run:
```bash
mkdir -p /tmp/test; cd /tmp/test; echo "hello" > index.html
python3 -m http.server 8000 --bind 127.0.0.1
```
Create a domain for host connection:
```bash
sudo container system dns create host.container.internal --localhost 203.0.113.113
```
Test access to the host HTTP server from a container:
```console
% container run -it --rm alpine/curl curl http://host.container.internal:8000
hello
```
## Set a custom MAC address for your container
Use the `mac` option to specify a custom MAC address for your container's network interface. This is useful for:
- Network testing scenarios requiring predictable MAC addresses
- Consistent network configuration across container restarts
The MAC address must be in the format `XX:XX:XX:XX:XX:XX` (with colons or hyphens as separators). Set the two least significant bits of the first octet to `10` (locally signed, unicast address).
```bash
container run --network default,mac=02:42:ac:11:00:02 ubuntu:latest
```
To verify the MAC address is set correctly, read the interface MAC directly from sysfs inside the container:
```console
% container run --rm --network default,mac=02:42:ac:11:00:02 ubuntu:latest cat /sys/class/net/eth0/address
02:42:ac:11:00:02
```
If you don't specify a MAC address, `container` will generate one for you. The generated address has a first nibble set to hexadecimal `f` (`fX:XX:XX:XX:XX:XX`) in case you want to minimize the very small chance of conflict between your MAC address and generated addresses.
## Mount your host SSH authentication socket in your container
Use the `--ssh` option to mount the macOS SSH authentication socket into your container, so that you can clone private git repositories and perform other tasks requiring passwordless SSH authentication.
When you use `--ssh`, it performs the equivalent of the options `--volume "${SSH_AUTH_SOCK}:/run/host-services/ssh-auth.sock" --env SSH_AUTH_SOCK=/run/host-services/ssh-auth.sock"`. The added benefit of `--ssh` is that when you stop your container, log out, log back in, and restart your container, the system automatically updates the target path for the socket mount to the new value of `SSH_AUTH_SOCK`, so that socket forwarding continues to function.
```console
% container run -it --rm --ssh alpine:latest sh
/ # env
SHLVL=1
HOME=/root
TERM=xterm
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
SSH_AUTH_SOCK=/run/host-services/ssh-auth.sock
PWD=/
/ # apk add openssh-client
(1/6) Installing openssh-keygen (10.0_p1-r7)
(2/6) Installing ncurses-terminfo-base (6.5_p20250503-r0)
(3/6) Installing libncursesw (6.5_p20250503-r0)
(4/6) Installing libedit (20250104.3.1-r1)
(5/6) Installing openssh-client-common (10.0_p1-r7)
(6/6) Installing openssh-client-default (10.0_p1-r7)
Executing busybox-1.37.0-r18.trigger
OK: 12 MiB in 22 packages
/ # ssh-add -l
...auth key output...
/ # apk add git
(1/12) Installing brotli-libs (1.1.0-r2)
(2/12) Installing c-ares (1.34.5-r0)
(3/12) Installing libunistring (1.3-r0)
(4/12) Installing libidn2 (2.3.7-r0)
(5/12) Installing nghttp2-libs (1.65.0-r0)
(6/12) Installing libpsl (0.21.5-r3)
(7/12) Installing zstd-libs (1.5.7-r0)
(8/12) Installing libcurl (8.14.1-r1)
(9/12) Installing libexpat (2.7.1-r0)
(10/12) Installing pcre2 (10.43-r1)
(11/12) Installing git (2.49.1-r0)
(12/12) Installing git-init-template (2.49.1-r0)
Executing busybox-1.37.0-r18.trigger
OK: 24 MiB in 34 packages
/ # git clone git@github.com:some-org/some-private-repo.git
Cloning into 'some-private-repo'...
...
```
## Create and use a separate isolated network
> [!NOTE]
> This feature is available on macOS 26 and later.
Running `container system start` creates a vmnet network named `default` to which your containers will attach unless you specify otherwise.
You can create a separate isolated network using `container network create`.
This command creates a network named `foo`:
```bash
container network create foo
```
You can also specify custom IPv4 and IPv6 subnets when creating a network:
```bash
container network create foo --subnet 192.168.100.0/24 --subnet-v6 fd00:1234::/64
```
The `foo` network, the default network, and any other networks you create are isolated from one another. A container on one network has no connectivity to containers on other networks.
Run `container network list` to see the networks that exist:
```console
% container network list
NETWORK STATE SUBNET
default running 192.168.64.0/24
foo running 192.168.65.0/24
%
```
Run a container that is attached to that network using the `--network` flag:
```console
container run -d --name my-web-server --network foo --rm web-test
```
Use `container ls` to see that the container is on the `foo` subnet:
```console
% container ls
ID IMAGE OS ARCH STATE IP
my-web-server web-test:latest linux arm64 running 192.168.65.2
```
You can delete networks that you create once no containers are attached:
```bash
container stop my-web-server
container network delete foo
```
Networks support both IPv4 and IPv6. When creating a network without explicit subnet options, the system uses default values if configured via system properties (see below), or automatically allocates subnets. The system validates that custom subnets don't overlap with existing networks.
## Configure default network subnets
You can customize the default IPv4 and IPv6 subnets used for new networks by editing your runtime configuration file at `~/.config/container/config.toml`:
```toml
[network]
subnet = "192.168.100.1/24"
subnetv6 = "fd00:abcd::/64"
```
These settings apply to networks created without explicit `--subnet` or `--subnet-v6` options.
## View container logs
The `container logs` command displays the output from your containerized application:
<pre>
% container run -d --name my-web-server --rm registry.example.com/fido/web-test:latest
my-web-server
% curl http://my-web-server.test
&lt;!DOCTYPE html>&lt;html>&lt;head>&lt;title>Hello&lt;/title>&lt;/head>&lt;body>&lt;h1>Hello, world!&lt;/h1>&lt;/body>&lt;/html>
% container logs my-web-server
192.168.64.1 - - [15/May/2025 03:00:03] "GET / HTTP/1.1" 200 -
%
</pre>
Use the `--boot` option to see the logs for the virtual machine boot and init process:
<pre>
% container logs --boot my-web-server
[ 0.098284] cacheinfo: Unable to detect cache hierarchy for CPU 0
[ 0.098466] random: crng init done
[ 0.099657] brd: module loaded
[ 0.100707] loop: module loaded
[ 0.100838] virtio_blk virtio2: 1/0/0 default/read/poll queues
[ 0.101051] virtio_blk virtio2: [vda] 1073741824 512-byte logical blocks (550 GB/512 GiB)
...
[ 0.127467] EXT4-fs (vda): mounted filesystem without journal. Quota mode: disabled.
[ 0.127525] VFS: Mounted root (ext4 filesystem) readonly on device 254:0.
[ 0.127635] devtmpfs: mounted
[ 0.127773] Freeing unused kernel memory: 2816K
[ 0.143252] Run /sbin/vminitd as init process
2025-05-15T02:24:08+0000 info vminitd : [vminitd] vminitd booting...
2025-05-15T02:24:08+0000 info vminitd : [vminitd] serve vminitd api
2025-05-15T02:24:08+0000 debug vminitd : [vminitd] starting process supervisor
2025-05-15T02:24:08+0000 debug vminitd : port=1024 [vminitd] booting grpc server on vsock
...
2025-05-15T02:24:08+0000 debug vminitd : exits=[362: 0] pid=363 [vminitd] checking for exit of managed process
2025-05-15T02:24:08+0000 debug vminitd : [vminitd] waiting on process my-web-server
[ 1.122742] IPv6: ADDRCONF(NETDEV_CHANGE): eth0: link becomes ready
2025-05-15T02:24:39+0000 debug vminitd : sec=1747275879 usec=478412 [vminitd] setTime
%
</pre>
## Monitor container resource usage
The `container stats` command displays real-time resource usage statistics for your running containers, similar to the `top` command for processes. This is useful for:
- Monitoring CPU and memory consumption
- Tracking network and disk I/O
- Identifying resource-intensive containers
- Verifying container resource limits are appropriate
By default, `container stats` shows live statistics for all running containers in an interactive display:
```console
% container stats
Container ID Cpu % Memory Usage Net Rx/Tx Block I/O Pids
my-web-server 2.45% 45.23 MiB / 1.00 GiB 1.23 MiB / 856.00 KiB 4.50 MiB / 2.10 MiB 3
db 125.12% 512.50 MiB / 2.00 GiB 5.67 MiB / 3.21 MiB 125.00 MiB / 89.00 MiB 12
```
To monitor specific containers, provide their names or IDs:
```console
% container stats my-web-server db
```
For a single snapshot (non-interactive), use the `--no-stream` flag:
```console
% container stats --no-stream my-web-server
Container ID Cpu % Memory Usage Net Rx/Tx Block I/O Pids
my-web-server 30.45% 45.23 MiB / 1.00 GiB 1.23 MiB / 856.00 KiB 4.50 MiB / 2.10 MiB 3
```
You can also output statistics in JSON format for scripting:
```console
% container stats --format json --no-stream my-web-server | jq
[
{
"id": "my-web-server",
"memoryUsageBytes": 47431680,
"memoryLimitBytes": 1073741824,
"cpuUsageUsec": 1234567,
"networkRxBytes": 1289011,
"networkTxBytes": 876544,
"blockReadBytes": 4718592,
"blockWriteBytes": 2202009,
"numProcesses": 3
}
]
```
**Understanding the metrics:**
- **Cpu %**: Percentage of CPU usage. ~100% = one fully utilized core. A multi-core container can show > 100%.
- **Memory Usage**: Current memory usage vs. the container's memory limit.
- **Net Rx/Tx**: Network bytes received and transmitted.
- **Block I/O**: Disk bytes read and written.
- **Pids**: Number of processes running in the container.
## Control Linux capabilities
By default, containers start with a restricted set of Linux capabilities:
`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`
You can customize the capability set using `--cap-add` and `--cap-drop` with `container run` or `container create`.
Capability names can be specified with or without the `CAP_` prefix, and are case-insensitive:
These are equivalent:
```bash
container run --cap-add CAP_NET_ADMIN alpine ip link set lo down
container run --cap-add NET_ADMIN alpine ip link set lo down
container run --cap-add net_admin alpine ip link set lo down
```
To grant all capabilities:
```bash
container run --cap-add ALL alpine sh -c "ip link set lo down && echo ok"
```
To drop all capabilities and selectively re-add only what you need:
```bash
container run --cap-drop ALL --cap-add SETUID --cap-add SETGID alpine id
```
Adds are processed after drops, so `--cap-drop ALL --cap-add ALL` results in all capabilities being granted.
To grant all capabilities except specific ones:
```bash
container run --cap-add ALL --cap-drop NET_ADMIN alpine sh
```
To drop a single capability from the default set:
```console
% container run --cap-drop CHOWN alpine chown 100 /tmp
chown: /tmp: Operation not permitted
```
## Mask and protect paths inside a container
> [!NOTE]
> `--masked-path` and `--read-only-path` are experimental. The behavior described here are subject to change in a future release.
By default, containers hide a set of sensitive paths from the workload, and mark another set read-only, matching the OCI runtime spec defaults that other production runtimes apply.
Masked by default (files are replaced with `/dev/null`, directories with an empty read-only tmpfs):
`/proc/asound`, `/proc/acpi`, `/proc/kcore`, `/proc/keys`, `/proc/latency_stats`, `/proc/timer_list`, `/proc/timer_stats`, `/proc/sched_debug`, `/proc/scsi`, `/sys/firmware`, `/sys/devices/virtual/powercap`
Read-only by default:
`/proc/bus`, `/proc/fs`, `/proc/irq`, `/proc/sys`, `/proc/sysrq-trigger`
You can extend either set using `--masked-path` and `--read-only-path` with `container run` or `container create`. Both flags can be repeated, take absolute paths, and add to the defaults rather than replacing them:
```console
% container run --masked-path /etc/alpine-release alpine cat /etc/alpine-release
% container run --read-only-path /tmp alpine touch /tmp/file
touch: /tmp/file: Read-only file system
```
To opt out of the defaults entirely, pass the `NONE` sentinel. It clears every path accumulated so far for that flag, including the defaults:
```bash
container run --masked-path NONE alpine ls /sys/firmware
```
Because values are processed in order, `NONE` can be followed by a custom set that replaces the defaults:
```bash
container run --masked-path NONE --masked-path /run/secrets alpine sh
```
The two flags are independent, so clearing the masked paths leaves the read-only defaults in place. The paths that a container was created with are visible in `container inspect` under `configuration.maskedPaths` and `configuration.readonlyPaths`; when neither flag is used, both are absent and the runtime defaults apply.
## Expose virtualization capabilities to a container
> [!NOTE]
> This feature requires a M3 or newer Apple silicon machine and a Linux kernel that supports virtualization. For a kernel configuration that has all of the right features enabled, see https://github.com/apple/containerization/blob/0.5.0/kernel/config-arm64#L602.
You can enable virtualization capabilities in containers by using the `--virtualization` option of `container run` and `container create`.
If your machine does not have support for nested virtualization, you will see the following:
```console
container run --name nested-virtualization --virtualization --kernel /path/to/a/kernel/with/virtualization/support --rm ubuntu:latest sh -c "dmesg | grep kvm"
Error: unsupported: "nested virtualization is not supported on the platform"
```
When nested virtualization is enabled successfully, `dmesg` will show output like the following:
```console
container run --name nested-virtualization --virtualization --kernel /path/to/a/kernel/with/virtualization/support --rm ubuntu:latest sh -c "dmesg | grep kvm"
[ 0.017245] kvm [1]: IPA Size Limit: 40 bits
[ 0.017499] kvm [1]: GICv3: no GICV resource entry
[ 0.017501] kvm [1]: disabling GICv2 emulation
[ 0.017506] kvm [1]: GIC system register CPU interface enabled
[ 0.017685] kvm [1]: vgic interrupt IRQ9
[ 0.017893] kvm [1]: Hyp mode initialized successfully
```
## Run a container with a provided init process
By default, the command you specify in `container run` runs as PID 1 inside the container. This means it is responsible for reaping zombie processes and handling signals, which many applications are not designed to do. The `--init` flag runs a lightweight init process as PID 1 that automatically forwards signals and reaps orphaned child processes.
```bash
container run --init ubuntu:latest my-app
```
The init process is also available with `container create`:
```bash
container create --init --name my-container ubuntu:latest my-app
container start my-container
```
## Use a custom init image
The `--init-image` flag allows you to specify a custom init filesystem image for the lightweight VM that runs your container. This enables:
- Custom boot-time logic before the OCI container starts
- Running additional processes and daemons (e.g., eBPF network filters, logging agents) inside the VM
- Debugging or instrumenting the init process
### Create a custom init image
A custom init image wraps the default `vminitd` binary, allowing you to run custom logic before handing off to the standard init process.
**1. Create a wrapper binary (example in Go for easy cross-compilation):**
```go
// wrapper.go
package main
import (
"os"
"syscall"
)
func main() {
// Write a message to kernel log
kmsg, err := os.OpenFile("/dev/kmsg", os.O_WRONLY, 0)
if err == nil {
kmsg.WriteString("<6>custom-init: === CUSTOM INIT IMAGE RUNNING ===\n")
kmsg.Close()
}
// Execute the real vminitd
err = syscall.Exec("/sbin/vminitd.real", os.Args, os.Environ())
if err != nil {
os.Exit(1)
}
}
```
**2. Build the wrapper for Linux arm64:**
```bash
CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -o wrapper wrapper.go
```
**3. Create a Containerfile:**
Use the `vminit` image tag corresponding to the `scVersion` value in the project `Package.swift` file.
Or, use `vminit:latest` if you have a local `containerization` project in [edit mode](../BUILDING.md#develop-using-a-local-copy-of-containerization).
```dockerfile
FROM ghcr.io/apple/containerization/vminit:0.34.0 AS base
FROM ghcr.io/apple/containerization/vminit:0.34.0
COPY --from=base /sbin/vminitd /sbin/vminitd.real
COPY wrapper /sbin/vminitd
```
**4. Build the custom init image:**
```bash
container build -t local/custom-init:latest .
```
### Run a container with a custom init image
```bash
container run --name my-container --init-image local/custom-init:latest alpine:latest echo "hello"
```
### Verify the custom init is running
Check the VM boot logs to confirm your custom init code executed:
```console
% container logs --boot my-container | grep custom-init
[ 0.129230] custom-init: === CUSTOM INIT IMAGE RUNNING ===
```
## Use container machines
Container machines are persistent Linux environments built from OCI images — your home directory is mounted in, the user account matches your host account, and the filesystem survives stop and start. See [container-machine.md](./container-machine.md) for the full guide.
## Configure system properties
The `container system property` subcommand manages the configuration settings for the `container` CLI and services. You can customize various aspects of container behavior, including build settings, default images, and network configuration.
Use `container system property list` to show all properties that have set defaults:
```console
% bin/container system property ls
[build]
cpus = 2
memory = "2048mb"
rosetta = true
image = "ghcr.io/apple/container-builder-shim/builder:0.13.1"
[container]
cpus = 4
memory = "1gb"
[dns]
domain = "test"
[kernel]
binaryPath = "opt/kata/share/kata-containers/vmlinux-6.18.15-186"
url = "https://github.com/kata-containers/kata-containers/releases/download/3.28.0/kata-static-3.28.0-arm64.tar.zst"
digest = "sha256:f63d54507d1f18635d94475077e4c2330de4d8e05cedf25f7c38f063b0e66a91"
[network]
[registry]
domain = "docker.io"
[vminit]
image = "ghcr.io/apple/containerization/vminit:0.34.0"
```
### Example: Disable Rosetta for builds
If you want to prevent the use of Rosetta translation during container builds on Apple Silicon Macs, set the following in `~/.config/container/config.toml`:
```toml
[build]
rosetta = false
```
This is useful when you want to ensure builds only produce native arm64 images and avoid any x86_64 emulation.
## View system logs
The `container system logs` command allows you to look at the log messages that `container` writes:
<pre>
% container system logs | tail -8
2025-06-02 16:46:11.560780-0700 0xf6dc5 Info 0x0 61684 0 container-apiserver: [com.apple.container:APIServer] Registering plugin [id=com.apple.container.container-runtime-linux.my-web-server]
2025-06-02 16:46:11.699095-0700 0xf6ea8 Info 0x0 61733 0 container-runtime-linux: [com.apple.container:RuntimeLinuxHelper] starting container-runtime-linux [uuid=my-web-server]
2025-06-02 16:46:11.699125-0700 0xf6ea8 Info 0x0 61733 0 container-runtime-linux: [com.apple.container:RuntimeLinuxHelper] configuring XPC server [uuid=my-web-server]
2025-06-02 16:46:11.700908-0700 0xf6ea8 Info 0x0 61733 0 container-runtime-linux: [com.apple.container:RuntimeLinuxHelper] starting XPC server [uuid=my-web-server]
2025-06-02 16:46:11.703028-0700 0xf6ea8 Info 0x0 61733 0 container-runtime-linux: [com.apple.container:RuntimeLinuxHelper] `bootstrap` xpc handler [uuid=my-web-server]
2025-06-02 16:46:11.720836-0700 0xf6dc3 Info 0x0 61689 0 container-network-vmnet: [com.apple.container:NetworkVmnetHelper] allocated attachment [hostname=my-web-server.test.] [address=192.168.64.2/24] [gateway=192.168.64.1] [id=default]
2025-06-02 16:46:12.293193-0700 0xf6eaa Info 0x0 61733 0 container-runtime-linux: [com.apple.container:RuntimeLinuxHelper] `start` xpc handler [uuid=my-web-server]
2025-06-02 16:46:12.368723-0700 0xf6e93 Info 0x0 61684 0 container-apiserver: [com.apple.container:APIServer] Handling container my-web-server Start.
%
</pre>
## Generating and installing completion scripts
### Overview
The `container --generate-completion-script [zsh|bash|fish]` command generates completion scripts for the provided shell. Below is a detailed guide on how to install the completion scripts.
> [!NOTE]
> See the [swift-argument-parser documentation](https://apple.github.io/swift-argument-parser/documentation/argumentparser/installingcompletionscripts/#Installing-Zsh-Completions) for more information about generating and installing shell completion scripts.
### Installing `zsh` completions
If you have [oh-my-zsh](https://ohmyz.sh/) installed, you already have a directory of automatically loaded completion scripts — `.oh-my-zsh/completions`. Copy your new completion script to that directory. If the `completions` directory does not exist, simply make it.
```zsh
mkdir -p ~/.oh-my-zsh/completions
container --generate-completion-script zsh > ~/.oh-my-zsh/completions/_container
source ~/.oh-my-zsh/completions/_container
```
> [!NOTE]
> Your completion script must have the filename `_container`.
Without oh-my-zsh, youll need to add a path for completion scripts to your function path, and turn on completion script autoloading. First, add these lines to your `~/.zshrc` file:
```bash
fpath=(~/.zsh/completion $fpath)
autoload -U compinit
compinit
```
Next, create a directory at `~/.zsh/completion` and copy the completion script to the new directory.
```zsh
mkdir -p ~/.zsh/completion
container --generate-completion-script zsh > ~/.zsh/completion/_container
source ~/.zshrc
```
### Installing `bash` completions
If you have [bash-completion](https://github.com/scop/bash-completion) installed, you can just copy your new completion script to the `bash_completion.d` directory.
> [!NOTE]
> The path to the directory is dependent on how bash-completion was installed. Find the correct path and then copy the completion script there. For example, if you used homebrew to install `bash-completion`:
> ```bash
> container --generate-completion-script bash > /opt/homebrew/etc/bash_completion.d/container
> source /opt/homebrew/etc/bash_completion.d/container
> ```
Without bash-completion, youll need to source the completion script directly. Create and copy it to a directory such as `~/.bash_completions`.
```bash
mkdir -p ~/.bash_completions
container --generate-completion-script bash > ~/.bash_completions/container
source ~/.bash_completions/container
```
Furthermore, you can add the following line to `~/.bash_profile` or `~/.bashrc`, in order for every new bash session to have autocompletion ready.
```bash
source ~/.bash_completions/container
```
### Installing `fish` completions
Copy the completion script to any path listed in the environment variable `$fish_completion_path`.
```bash
container --generate-completion-script fish > ~/.config/fish/completions/container.fish
```
`container` has a lot of surface area beyond the [basic tutorial](./tutorials/start-here.md). Each topic below has its own guide — pick the one that matches what you're trying to do.
## Topics
- [Resource usage](./resource-usage.md) — CPU and memory limits for containers and builds, overcommitting resources, monitoring usage with `container stats`, and reclaiming disk space.
- [Mounts and volumes](./volumes.md) — bind-mount host directories, create named volumes, and mount temporary tmpfs storage.
- [Networking](./networking.md) — DNS-based container names, container-to-container connectivity, port forwarding, custom MAC addresses, and isolated networks.
- [Host integration](./host-integration.md) — forward your SSH agent into a container, and reach a service running on your Mac from inside a container.
- [Resource limits (ulimits)](./ulimits.md) — per-process limits like open-file and process-count limits.
- [Runtime configuration](./runtime-configuration.md) — Linux capabilities, masked and read-only paths, nested virtualization, and customizing the container's init process.
- [Multiplatform images](./multiplatform-images.md) — build, run, and publish images that support both Apple silicon and x86-64.
- [Inspecting containers and images](./container-inspection.md) — machine-readable `inspect` and `list` output for scripting.
- [Logs](./logs.md) — container output, VM boot logs, and the `container` system's own logs.
- [`config.toml` reference](./container-system-config.md) — every configuration key, its default, and how to view your merged configuration.
- Container machines — persistent Linux environments built from OCI images, with your home directory mounted in and the filesystem surviving stop/start. See [container-machine.md](./container-machine.md) for the full guide.
- [Shell completions](./shell-completions.md) — generate and install completion scripts for `zsh`, `bash`, and `fish`.
+66
View File
@@ -0,0 +1,66 @@
# Logs
View output from your containerized applications, VM boot logs, and the `container`
system's own logs.
## View container logs
The `container logs` command displays the output from your containerized application:
<pre>
% container run -d --name my-web-server --rm registry.example.com/fido/web-test:latest
my-web-server
% curl http://my-web-server.test
&lt;!DOCTYPE html>&lt;html>&lt;head>&lt;title>Hello&lt;/title>&lt;/head>&lt;body>&lt;h1>Hello, world!&lt;/h1>&lt;/body>&lt;/html>
% container logs my-web-server
192.168.64.1 - - [15/May/2025 03:00:03] "GET / HTTP/1.1" 200 -
%
</pre>
Use the `--boot` option to see the logs for the virtual machine boot and init process:
<pre>
% container logs --boot my-web-server
[ 0.098284] cacheinfo: Unable to detect cache hierarchy for CPU 0
[ 0.098466] random: crng init done
[ 0.099657] brd: module loaded
[ 0.100707] loop: module loaded
[ 0.100838] virtio_blk virtio2: 1/0/0 default/read/poll queues
[ 0.101051] virtio_blk virtio2: [vda] 1073741824 512-byte logical blocks (550 GB/512 GiB)
...
[ 0.127467] EXT4-fs (vda): mounted filesystem without journal. Quota mode: disabled.
[ 0.127525] VFS: Mounted root (ext4 filesystem) readonly on device 254:0.
[ 0.127635] devtmpfs: mounted
[ 0.127773] Freeing unused kernel memory: 2816K
[ 0.143252] Run /sbin/vminitd as init process
2025-05-15T02:24:08+0000 info vminitd : [vminitd] vminitd booting...
2025-05-15T02:24:08+0000 info vminitd : [vminitd] serve vminitd api
2025-05-15T02:24:08+0000 debug vminitd : [vminitd] starting process supervisor
2025-05-15T02:24:08+0000 debug vminitd : port=1024 [vminitd] booting grpc server on vsock
...
2025-05-15T02:24:08+0000 debug vminitd : exits=[362: 0] pid=363 [vminitd] checking for exit of managed process
2025-05-15T02:24:08+0000 debug vminitd : [vminitd] waiting on process my-web-server
[ 1.122742] IPv6: ADDRCONF(NETDEV_CHANGE): eth0: link becomes ready
2025-05-15T02:24:39+0000 debug vminitd : sec=1747275879 usec=478412 [vminitd] setTime
%
</pre>
## View system logs
The `container system logs` command allows you to look at the log messages that `container` writes:
<pre>
% container system logs | tail -8
2025-06-02 16:46:11.560780-0700 0xf6dc5 Info 0x0 61684 0 container-apiserver: [com.apple.container:APIServer] Registering plugin [id=com.apple.container.container-runtime-linux.my-web-server]
2025-06-02 16:46:11.699095-0700 0xf6ea8 Info 0x0 61733 0 container-runtime-linux: [com.apple.container:RuntimeLinuxHelper] starting container-runtime-linux [uuid=my-web-server]
2025-06-02 16:46:11.699125-0700 0xf6ea8 Info 0x0 61733 0 container-runtime-linux: [com.apple.container:RuntimeLinuxHelper] configuring XPC server [uuid=my-web-server]
2025-06-02 16:46:11.700908-0700 0xf6ea8 Info 0x0 61733 0 container-runtime-linux: [com.apple.container:RuntimeLinuxHelper] starting XPC server [uuid=my-web-server]
2025-06-02 16:46:11.703028-0700 0xf6ea8 Info 0x0 61733 0 container-runtime-linux: [com.apple.container:RuntimeLinuxHelper] `bootstrap` xpc handler [uuid=my-web-server]
2025-06-02 16:46:11.720836-0700 0xf6dc3 Info 0x0 61689 0 container-network-vmnet: [com.apple.container:NetworkVmnetHelper] allocated attachment [hostname=my-web-server.test.] [address=192.168.64.2/24] [gateway=192.168.64.1] [id=default]
2025-06-02 16:46:12.293193-0700 0xf6eaa Info 0x0 61733 0 container-runtime-linux: [com.apple.container:RuntimeLinuxHelper] `start` xpc handler [uuid=my-web-server]
2025-06-02 16:46:12.368723-0700 0xf6e93 Info 0x0 61684 0 container-apiserver: [com.apple.container:APIServer] Handling container my-web-server Start.
%
</pre>
See [bug report how-to](./bug-report-how-to.md) for gathering logs when filing an
issue.
+35
View File
@@ -0,0 +1,35 @@
# Multiplatform images
Build, run, and publish container images that support both Apple silicon and x86-64.
## Build and run a multiplatform image
Using the [project from the tutorial example](./tutorials/start-here.md#set-up-a-simple-project), you can create an image to use both on Apple silicon Macs and on x86-64 servers.
When building the image, just add `--arch` options that direct the builder to create an image supporting both the `arm64` and `amd64` architectures:
```bash
container build --arch arm64 --arch amd64 --tag registry.example.com/fido/web-test:latest --file Dockerfile .
```
Try running the command `uname -a` with the `arm64` variant of the image to see the system information that the virtual machine reports:
<pre>
% container run --arch arm64 --rm registry.example.com/fido/web-test:latest uname -a
Linux 7932ce5f-ec10-4fbe-a2dc-f29129a86b64 6.1.68 #1 SMP Mon Mar 31 18:27:51 UTC 2025 aarch64 GNU/Linux
%
</pre>
When you run the command with the `amd64` architecture, the x86-64 version of `uname` runs under Rosetta translation, so that you will see information for an x86-64 system:
<pre>
% container run --arch amd64 --rm registry.example.com/fido/web-test:latest uname -a
Linux c0376e0a-0bfd-4eea-9e9e-9f9a2c327051 6.1.68 #1 SMP Mon Mar 31 18:27:51 UTC 2025 x86_64 GNU/Linux
%
</pre>
The command to push your multiplatform image to a registry is no different than that for a single-platform image:
```bash
container image push registry.example.com/fido/web-test:latest
```
+221
View File
@@ -0,0 +1,221 @@
# Networking
Learn how `container` networks containers with one another, with the host, and with
external systems.
Running `container system start` creates a vmnet network named `default`, to which your
containers attach unless you specify otherwise. Every container gets an IP address on
its network, always reachable by that IP from the host and from other containers on the
same network (find it with `container inspect <name>`).
## Set up DNS-based container names
Reaching a container by name instead of IP goes through `container`'s embedded DNS
service. Set this up in two steps:
### Step 1: Tell the `container` service what domain to use
Edit `~/.config/container/config.toml`:
```toml
[dns]
domain = "test"
```
Restart the service so it picks up the change:
```bash
container system stop
container system start
```
From this point on, every container you run gets registered under `<name>.test` inside
`container`'s DNS service, and every container's own DNS resolver is configured to look
up `.test` names there too.
### Step 2: Tell macOS to use that domain too
Step 1 only affects the `container` service and the containers it runs — your Mac's own
DNS resolver still knows nothing about `test`. Point it at `container`'s DNS service:
```bash
sudo container system dns create test
```
Enter your administrator password when prompted. This writes a resolver file to
`/etc/resolver/` that tells macOS: for any `*.test` query, ask `127.0.0.1` instead of
your normal DNS server.
Both steps are needed. See [`[dns]` reference](./container-system-config.md#dns) for the
config-key-level detail.
With both steps done, confirm it end-to-end from your Mac:
```console
% container run -d --rm --name my-web-server python:alpine python3 -m http.server 8000
% curl http://my-web-server.test:8000
```
See [Host integration](./host-integration.md) for the reverse direction — reaching a
service running on your Mac from inside a container.
## Container-to-container networking
From one container, use another container's DNS name to reach a service it exposes.
This requires the DNS setup above ([Set up DNS-based container
names](#set-up-dns-based-container-names)):
```bash
container run --rm -d --name http-server python:alpine python3 -m http.server
container run -it --rm alpine/curl curl -v http://http-server.test:8000
container stop http-server
```
> [!WARNING]
> This works for containers on the `default` network using a domain-qualified name
> (`http-server.test`, as above). It does **not** currently work for looking up another
> container by its *bare* hostname (no domain suffix) on a custom network created with
> `container network create` — the kind of zero-configuration, Compose-style service
> discovery some users expect. That gap is tracked upstream as
> [apple/container#1809](https://github.com/apple/container/issues/1809) (open feature
> request, not yet implemented) and related broader reports in
> [apple/container#856](https://github.com/apple/container/issues/856). Until resolved,
> reach a container on a custom network by its IP address instead (`container inspect
> <name>` to find it).
## Forward traffic from `localhost` to your container
Use the `--publish` option to forward TCP or UDP traffic from your loopback IP to the container you run. The option value has the form `[host-ip:]host-port:container-port[/protocol]`, where protocol may be `tcp` or `udp`, case insensitive.
If your container attaches to multiple networks, the ports you publish forward to the IP address of the interface attached to the first network.
To forward requests from port 8080 on the IPv4 loopback IP to a NodeJS webserver on container port 8000, run:
```bash
container run -d --rm -p 127.0.0.1:8080:8000 node:latest npx http-server -a :: -p 8000
```
Test access using `curl`:
```console
% curl http://127.0.0.1:8080
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<title>Index of /</title>
...
<br><address>Node.js v25.2.1/ <a href="https://github.com/http-party/http-server">http-server</a> server running @ 127.0.0.1:8080</address>
</body></html>
```
To forward requests from port 8080 on the IPv6 loopback IP to a NodeJS webserver on container port 8000, run:
```bash
container run -d --rm -p '[::1]:8080:8000' node:latest npx http-server -a :: -p 8000
```
Test access using `curl`:
```console
% curl -6 'http://[::1]:8080'
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<title>Index of /</title>
...
<br><address>Node.js v25.2.1/ <a href="https://github.com/http-party/http-server">http-server</a> server running @ [::1]:8080</address>
</body></html>
```
## Set a custom MAC address for your container
Use the `mac` option to specify a custom MAC address for your container's network interface. This is useful for:
- Network testing scenarios requiring predictable MAC addresses
- Consistent network configuration across container restarts
The MAC address must be in the format `XX:XX:XX:XX:XX:XX` (with colons or hyphens as separators). Set the two least significant bits of the first octet to `10` (locally signed, unicast address).
```bash
container run --network default,mac=02:42:ac:11:00:02 ubuntu:latest
```
To verify the MAC address is set correctly, read the interface MAC directly from sysfs inside the container:
```console
% container run --rm --network default,mac=02:42:ac:11:00:02 ubuntu:latest cat /sys/class/net/eth0/address
02:42:ac:11:00:02
```
If you don't specify a MAC address, `container` will generate one for you. The generated address has a first nibble set to hexadecimal `f` (`fX:XX:XX:XX:XX:XX`) in case you want to minimize the very small chance of conflict between your MAC address and generated addresses.
## Create and use a separate isolated network
> [!NOTE]
> This feature is available on macOS 26 and later.
Running `container system start` creates a vmnet network named `default` to which your containers will attach unless you specify otherwise.
You can create a separate isolated network using `container network create`.
This command creates a network named `foo`:
```bash
container network create foo
```
You can also specify custom IPv4 and IPv6 subnets when creating a network:
```bash
container network create foo --subnet 192.168.100.0/24 --subnet-v6 fd00:1234::/64
```
The `foo` network, the default network, and any other networks you create are isolated from one another. A container on one network has no connectivity to containers on other networks.
Run `container network list` to see the networks that exist:
```console
% container network list
NETWORK SUBNET
default 192.168.64.0/24
foo 192.168.65.0/24
%
```
Run a container that is attached to that network using the `--network` flag:
```console
container run -d --name my-web-server --network foo --rm web-test
```
Use `container ls` to see that the container is on the `foo` subnet:
```console
% container ls
ID IMAGE OS ARCH STATE IP
my-web-server web-test:latest linux arm64 running 192.168.65.2
```
You can delete networks that you create once no containers are attached:
```bash
container stop my-web-server
container network delete foo
```
Networks support both IPv4 and IPv6. When creating a network without explicit subnet options, the system uses default values if configured in your runtime configuration file (see [Configure default network subnets](#configure-default-network-subnets)), or automatically allocates subnets. The system validates that custom subnets don't overlap with existing networks.
## Configure default network subnets
You can customize the default IPv4 and IPv6 subnets used for new networks by editing your runtime configuration file at `~/.config/container/config.toml`:
```toml
[network]
subnet = "192.168.100.1/24"
subnetv6 = "fd00:abcd::/64"
```
These settings apply to networks created without explicit `--subnet` or `--subnet-v6` options.
+160
View File
@@ -0,0 +1,160 @@
# Resource usage
Configure CPU, memory, and disk resources for your containers and builds, monitor
usage while they run, and reclaim disk space afterward.
## Configure memory and CPUs for your containers
Since the containers created by `container` are lightweight virtual machines, consider the needs of your containerized application when you use `container run`. The `--memory` and `--cpus` options allow you to override the default memory and CPU limits for the virtual machine. The default values are 1 gigabyte of RAM and 4 CPUs. You can use abbreviations for memory units; for example, to run a container for image `big` with 8 CPUs and 32 GiBytes of memory, use:
```bash
container run --rm --cpus 8 --memory 32g big
```
See [Resource limits (ulimits)](./ulimits.md) for per-process resource limits like open-file and process-count limits.
## Configure memory and CPUs for large builds
When you first run `container build`, `container` starts a *builder*, which is a utility container that builds images from your `Dockerfile`s. As with anything you run with `container run`, the builder runs in a lightweight virtual machine, so for resource-intensive builds, you may need to increase the memory and CPU limits for the builder VM.
By default, the builder VM receives 2 GiBytes of RAM and 2 CPUs. You can change these limits by starting the builder container before running `container build`:
```bash
container builder start --cpus 8 --memory 32g
```
If your builder is already running and you need to modify the limits, just stop, delete, and restart the builder:
```bash
container builder stop
container builder delete
container builder start --cpus 8 --memory 32g
```
## Overcommit memory and CPUs across containers
You can run more containers than your host has physical CPUs or memory for —
`container` does not reject a `--cpus` or `--memory` request that exceeds physical
capacity, whether for a single container or in aggregate across several. For example,
on an 8-CPU, 16 GB host you could run a builder VM with 4 CPUs/8 GB and three more
containers with 4 CPUs/2 GB each: 16 CPUs and 14 GB requested against 8 CPUs and 16 GB
physical.
This works because macOS schedules host and guest VM processes together against the
same physical resources, the same way it schedules any set of contending processes.
Throughput can't exceed what the physical CPUs provide, and CPU-bound containers slow
down as more of them compete for time. For memory, once real demand exceeds physical
RAM, macOS swaps out less-used pages — applications keep running, but performance
degrades and becomes limited by disk I/O as swapping increases. Leaving some CPU and
memory headroom for macOS and your other applications is a good practice.
## Monitor container resource usage
The `container stats` command displays real-time resource usage statistics for your running containers, similar to the `top` command for processes. This is useful for:
- Monitoring CPU and memory consumption
- Tracking network and disk I/O
- Identifying resource-intensive containers
- Verifying container resource limits are appropriate
By default, `container stats` shows live statistics for all running containers in an interactive display:
```console
% container stats
Container ID Cpu % Memory Usage Net Rx/Tx Block I/O Pids
my-web-server 2.45% 45.23 MiB / 1.00 GiB 1.23 MiB / 856.00 KiB 4.50 MiB / 2.10 MiB 3
db 125.12% 512.50 MiB / 2.00 GiB 5.67 MiB / 3.21 MiB 125.00 MiB / 89.00 MiB 12
```
To monitor specific containers, provide their names or IDs:
```console
% container stats my-web-server db
```
For a single snapshot (non-interactive), use the `--no-stream` flag:
```console
% container stats --no-stream my-web-server
Container ID Cpu % Memory Usage Net Rx/Tx Block I/O Pids
my-web-server 30.45% 45.23 MiB / 1.00 GiB 1.23 MiB / 856.00 KiB 4.50 MiB / 2.10 MiB 3
```
You can also output statistics in JSON format for scripting:
```console
% container stats --format json --no-stream my-web-server | jq
[
{
"id": "my-web-server",
"memoryUsageBytes": 47431680,
"memoryLimitBytes": 1073741824,
"cpuUsageUsec": 1234567,
"networkRxBytes": 1289011,
"networkTxBytes": 876544,
"blockReadBytes": 4718592,
"blockWriteBytes": 2202009,
"numProcesses": 3
}
]
```
**Understanding the metrics:**
- **Cpu %**: Percentage of CPU usage. ~100% = one fully utilized core. A multi-core container can show > 100%.
- **Memory Usage**: Current memory usage vs. the container's memory limit.
- **Net Rx/Tx**: Network bytes received and transmitted.
- **Block I/O**: Disk bytes read and written.
- **Pids**: Number of processes running in the container.
## Disk usage
Each container gets a macOS sparse disk image for its writable filesystem. Named
volumes get their own sparse disk image too. As your containerized application writes
data, these images grow; when a container process deletes a file, the freed blocks
aren't automatically returned to the host filesystem, so image size doesn't shrink on
its own.
Check overall usage with:
```bash
container system df
```
```console
TYPE TOTAL ACTIVE SIZE RECLAIMABLE
Images 12 4 3.2GB 1.1GB (34%)
Containers 4 2 890MB 210MB (24%)
Local Volumes 6 3 4.5GB 2.1GB (47%)
```
### Reclaim disk space
Remove stopped containers:
```bash
container prune
```
Remove images not referenced by any container (add `--all` to remove all untagged and
unused images, not just dangling ones):
```bash
container image prune
container image prune --all
```
Remove volumes with no container references:
```bash
container volume prune
```
Reclaim space used by the builder VM's layer cache by replacing the builder:
```bash
container builder stop
container builder delete
```
See [Mounts and volumes](./volumes.md) for bind mounts, named volumes, and tmpfs
mounts.
+208
View File
@@ -0,0 +1,208 @@
# Runtime configuration
Configure what runs inside and around your container's init process: Linux
capabilities, path masking, nested virtualization, and the init process itself.
## Control Linux capabilities
By default, containers start with a restricted set of Linux capabilities:
`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`
You can customize the capability set using `--cap-add` and `--cap-drop` with `container run` or `container create`.
Capability names can be specified with or without the `CAP_` prefix, and are case-insensitive:
These are equivalent:
```bash
container run --cap-add CAP_NET_ADMIN alpine ip link set lo down
container run --cap-add NET_ADMIN alpine ip link set lo down
container run --cap-add net_admin alpine ip link set lo down
```
To grant all capabilities:
```bash
container run --cap-add ALL alpine sh -c "ip link set lo down && echo ok"
```
To drop all capabilities and selectively re-add only what you need:
```bash
container run --cap-drop ALL --cap-add SETUID --cap-add SETGID alpine id
```
Adds are processed after drops, so `--cap-drop ALL --cap-add ALL` results in all capabilities being granted.
To grant all capabilities except specific ones:
```bash
container run --cap-add ALL --cap-drop NET_ADMIN alpine sh
```
To drop a single capability from the default set:
```console
% container run --cap-drop CHOWN alpine chown 100 /tmp
chown: /tmp: Operation not permitted
```
## Mask and protect paths inside a container
> [!NOTE]
> `--masked-path` and `--read-only-path` are experimental. The behavior described here is subject to change in a future release.
By default, containers hide a set of sensitive paths from the workload, and mark another set read-only, matching the OCI runtime spec defaults that other production runtimes apply.
Masked by default (files are replaced with `/dev/null`, directories with an empty read-only tmpfs):
`/proc/asound`, `/proc/acpi`, `/proc/kcore`, `/proc/keys`, `/proc/latency_stats`, `/proc/timer_list`, `/proc/timer_stats`, `/proc/sched_debug`, `/proc/scsi`, `/sys/firmware`, `/sys/devices/virtual/powercap`
Read-only by default:
`/proc/bus`, `/proc/fs`, `/proc/irq`, `/proc/sys`, `/proc/sysrq-trigger`
You can extend either set using `--masked-path` and `--read-only-path` with `container run` or `container create`. Both flags can be repeated, take absolute paths, and add to the defaults rather than replacing them:
```console
% container run --masked-path /etc/alpine-release alpine cat /etc/alpine-release
% container run --read-only-path /tmp alpine touch /tmp/file
touch: /tmp/file: Read-only file system
```
To opt out of the defaults entirely, pass the `NONE` sentinel. It clears every path accumulated so far for that flag, including the defaults:
```bash
container run --masked-path NONE alpine ls /sys/firmware
```
Because values are processed in order, `NONE` can be followed by a custom set that replaces the defaults:
```bash
container run --masked-path NONE --masked-path /run/secrets alpine sh
```
The two flags are independent, so clearing the masked paths leaves the read-only defaults in place. The paths that a container was created with are visible in `container inspect` under `configuration.maskedPaths` and `configuration.readonlyPaths`; when neither flag is used, both are absent and the runtime defaults apply.
## Expose virtualization capabilities to a container
> [!NOTE]
> This feature requires a M3 or newer Apple silicon machine and a Linux kernel that supports virtualization. For a kernel configuration that has all of the right features enabled, see https://github.com/apple/containerization/blob/0.5.0/kernel/config-arm64#L602.
You can enable virtualization capabilities in containers by using the `--virtualization` option of `container run` and `container create`.
If your machine does not have support for nested virtualization, you will see the following:
```console
container run --name nested-virtualization --virtualization --kernel /path/to/a/kernel/with/virtualization/support --rm ubuntu:latest sh -c "dmesg | grep kvm"
Error: unsupported: "nested virtualization is not supported on the platform"
```
When nested virtualization is enabled successfully, `dmesg` will show output like the following:
```console
container run --name nested-virtualization --virtualization --kernel /path/to/a/kernel/with/virtualization/support --rm ubuntu:latest sh -c "dmesg | grep kvm"
[ 0.017245] kvm [1]: IPA Size Limit: 40 bits
[ 0.017499] kvm [1]: GICv3: no GICV resource entry
[ 0.017501] kvm [1]: disabling GICv2 emulation
[ 0.017506] kvm [1]: GIC system register CPU interface enabled
[ 0.017685] kvm [1]: vgic interrupt IRQ9
[ 0.017893] kvm [1]: Hyp mode initialized successfully
```
## Run a container with a provided init process
By default, the command you specify in `container run` runs as PID 1 inside the container. This means it is responsible for reaping zombie processes and handling signals, which many applications are not designed to do. The `--init` flag runs a lightweight init process as PID 1 that automatically forwards signals and reaps orphaned child processes.
```bash
container run --init ubuntu:latest my-app
```
The init process is also available with `container create`:
```bash
container create --init --name my-container ubuntu:latest my-app
container start my-container
```
## Use a custom init image
The `--init-image` flag allows you to specify a custom init filesystem image for the lightweight VM that runs your container. This enables:
- Custom boot-time logic before the OCI container starts
- Running additional processes and daemons (e.g., eBPF network filters, logging agents) inside the VM
- Debugging or instrumenting the init process
### Create a custom init image
A custom init image wraps the default `vminitd` binary, allowing you to run custom logic before handing off to the standard init process.
**1. Create a wrapper binary (example in Go for easy cross-compilation):**
```go
// wrapper.go
package main
import (
"os"
"syscall"
)
func main() {
// Write a message to kernel log
kmsg, err := os.OpenFile("/dev/kmsg", os.O_WRONLY, 0)
if err == nil {
kmsg.WriteString("<6>custom-init: === CUSTOM INIT IMAGE RUNNING ===\n")
kmsg.Close()
}
// Execute the real vminitd
err = syscall.Exec("/sbin/vminitd.real", os.Args, os.Environ())
if err != nil {
os.Exit(1)
}
}
```
**2. Build the wrapper for Linux arm64:**
```bash
CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -o wrapper wrapper.go
```
**3. Create a Containerfile:**
Use the `vminit` image tag corresponding to the `scVersion` value in the project `Package.swift` file.
Or, use `vminit:latest` if you have a local `containerization` project in [edit mode](../BUILDING.md#develop-using-a-local-copy-of-containerization).
```dockerfile
FROM ghcr.io/apple/containerization/vminit:0.34.0 AS base
FROM ghcr.io/apple/containerization/vminit:0.34.0
COPY --from=base /sbin/vminitd /sbin/vminitd.real
COPY wrapper /sbin/vminitd
```
**4. Build the custom init image:**
```bash
container build -t local/custom-init:latest .
```
### Run a container with a custom init image
```bash
container run --name my-container --init-image local/custom-init:latest alpine:latest echo "hello"
```
### Verify the custom init is running
Check the VM boot logs to confirm your custom init code executed:
```console
% container logs --boot my-container | grep custom-init
[ 0.129230] custom-init: === CUSTOM INIT IMAGE RUNNING ===
```
See [Logs](./logs.md) for more on viewing container and VM boot logs.
+72
View File
@@ -0,0 +1,72 @@
# Shell completions
Generate and install completion scripts for `zsh`, `bash`, and `fish`.
## Overview
The `container --generate-completion-script [zsh|bash|fish]` command generates completion scripts for the provided shell. Below is a detailed guide on how to install the completion scripts.
> [!NOTE]
> See the [swift-argument-parser documentation](https://apple.github.io/swift-argument-parser/documentation/argumentparser/installingcompletionscripts/#Installing-Zsh-Completions) for more information about generating and installing shell completion scripts.
## Installing `zsh` completions
If you have [oh-my-zsh](https://ohmyz.sh/) installed, you already have a directory of automatically loaded completion scripts — `.oh-my-zsh/completions`. Copy your new completion script to that directory. If the `completions` directory does not exist, simply make it.
```zsh
mkdir -p ~/.oh-my-zsh/completions
container --generate-completion-script zsh > ~/.oh-my-zsh/completions/_container
source ~/.oh-my-zsh/completions/_container
```
> [!NOTE]
> Your completion script must have the filename `_container`.
Without oh-my-zsh, youll need to add a path for completion scripts to your function path, and turn on completion script autoloading. First, add these lines to your `~/.zshrc` file:
```bash
fpath=(~/.zsh/completion $fpath)
autoload -U compinit
compinit
```
Next, create a directory at `~/.zsh/completion` and copy the completion script to the new directory.
```zsh
mkdir -p ~/.zsh/completion
container --generate-completion-script zsh > ~/.zsh/completion/_container
source ~/.zshrc
```
## Installing `bash` completions
If you have [bash-completion](https://github.com/scop/bash-completion) installed, you can just copy your new completion script to the `bash_completion.d` directory.
> [!NOTE]
> The path to the directory is dependent on how bash-completion was installed. Find the correct path and then copy the completion script there. For example, if you used homebrew to install `bash-completion`:
> ```bash
> container --generate-completion-script bash > /opt/homebrew/etc/bash_completion.d/container
> source /opt/homebrew/etc/bash_completion.d/container
> ```
Without bash-completion, youll need to source the completion script directly. Create and copy it to a directory such as `~/.bash_completions`.
```bash
mkdir -p ~/.bash_completions
container --generate-completion-script bash > ~/.bash_completions/container
source ~/.bash_completions/container
```
Furthermore, you can add the following line to `~/.bash_profile` or `~/.bashrc`, in order for every new bash session to have autocompletion ready.
```bash
source ~/.bash_completions/container
```
## Installing `fish` completions
Copy the completion script to any path listed in the environment variable `$fish_completion_path`.
```bash
container --generate-completion-script fish > ~/.config/fish/completions/container.fish
```
@@ -33,7 +33,7 @@ touch ~/.config/container/config.toml
Open the file in the editor of your choice and add only the sections and keys you want to change.
For this tutorial, increase the default CPU and memory limits used for each new container and set a DNS domain for resolving container IP addresses from the host.
For this tutorial, increase the default CPU and memory limits used for each new container, and set a DNS domain so containers get hostnames under that domain (a container named `my-web-server` becomes `my-web-server.test`).
```toml
[container]
@@ -55,6 +55,19 @@ container system stop
container system start
```
### Route macOS DNS queries for the domain to `container`
The `[dns] domain` change above only affects the `container` service and the containers
it runs. Complete the setup by telling macOS to route `*.test` queries there too:
```bash
sudo container system dns create test
```
Enter your administrator password when prompted. See [Networking: Set up DNS-based
container names](../networking.md#set-up-dns-based-container-names) for what this step
does.
### Verify the values are loaded
Use `container system property list` (alias `ls`) to print the merged configuration that the `container` service is using.
+13 -1
View File
@@ -105,7 +105,16 @@ Use the `--help` flag to see which abbreviations exist.
### Set up a local DNS domain (optional)
`container` includes an embedded DNS service that simplifies access to your containerized applications. If you want to configure a local DNS domain named `test` for this tutorial, run:
`container` includes an embedded DNS service that simplifies access to your containerized applications. For what each step does, see [Networking: Set up DNS-based container names](../networking.md#set-up-dns-based-container-names); the short version, to set up a domain named `test`:
Set `domain = "test"` under `[dns]` in `~/.config/container/config.toml`, then restart the service:
```bash
container system stop
container system start
```
Then tell macOS to route `*.test` queries to `container`'s DNS service:
```bash
sudo container system dns create test
@@ -224,6 +233,9 @@ my-web-server 0.23% 12.45 MiB / 1.00 GiB 856.00 KiB / 1.2 KiB 2.10 MiB / 51
> [!NOTE]
> Without the `--no-stream` flag, `container stats` continuously updates the display in real-time, similar to the `top` command. Press Ctrl+C to exit the live view.
See [Resource usage](../resource-usage.md) for what each metric means, setting
CPU/memory limits, and reclaiming disk space.
### Run other commands in the container
You can run other commands in `my-web-server` by using the `container exec` command. To list the files under the content directory, run an `ls` command:
+79
View File
@@ -0,0 +1,79 @@
# Resource limits (ulimits)
Set per-process resource limits for your containers.
## Overview
The `--ulimit` option of `container run` (and `container create`) sets Linux resource
limits (`rlimit`s) for the container's init process.
## Syntax
```bash
container run --ulimit <type>=<soft>[:<hard>] ...
```
If you set a single value, it applies as both soft and hard limit:
```bash
container run --ulimit nofile=65536 -it ubuntu:24.04 bash
```
Set soft and hard limits independently:
```bash
container run --ulimit nofile=65536:131072 -it ubuntu:24.04 bash
```
Set multiple limits by repeating the flag:
```bash
container run --ulimit nofile=65536:131072 --ulimit cpu=60 -it ubuntu:24.04 bash
```
Use `unlimited` for no limit:
```bash
container run --ulimit nproc=unlimited -it ubuntu:24.04 bash
```
> [!NOTE]
> `nofile=unlimited` reliably fails to start the container
> (`NSPOSIXErrorDomain Code=1 "Operation not permitted"`). This isn't a `container`
> bug — `unlimited` sets both soft and hard limits to `UINT64_MAX`, and Linux caps
> `RLIMIT_NOFILE`'s hard limit at the guest's `/proc/sys/fs/nr_open` (`1048576` here);
> anything above that ceiling fails the same way, `unlimited` included. Use an explicit
> value at or below `nr_open` instead, e.g. `nofile=1048576`.
## Supported limit types
| Type | Maps to | Description |
|---|---|---|
| `core` | `RLIMIT_CORE` | Maximum core file size, in bytes |
| `cpu` | `RLIMIT_CPU` | Maximum CPU time, in seconds |
| `data` | `RLIMIT_DATA` | Maximum data segment size, in bytes |
| `fsize` | `RLIMIT_FSIZE` | Maximum file size, in bytes |
| `locks` | `RLIMIT_LOCKS` | Maximum number of file locks |
| `memlock` | `RLIMIT_MEMLOCK` | Maximum amount of memory that may be locked into RAM |
| `msgqueue` | `RLIMIT_MSGQUEUE` | Maximum bytes in POSIX message queues |
| `nice` | `RLIMIT_NICE` | Maximum nice priority |
| `nofile` | `RLIMIT_NOFILE` | Maximum number of open file descriptors |
| `nproc` | `RLIMIT_NPROC` | Maximum number of processes |
| `rss` | `RLIMIT_RSS` | Maximum resident set size, in bytes |
| `rtprio` | `RLIMIT_RTPRIO` | Maximum real-time priority |
| `rttime` | `RLIMIT_RTTIME` | Maximum real-time CPU time, in microseconds |
| `sigpending` | `RLIMIT_SIGPENDING` | Maximum number of pending signals |
| `stack` | `RLIMIT_STACK` | Maximum stack size, in bytes |
## Inspect limits inside a container
```console
% container run -it --rm ubuntu:24.04 bash -c "ulimit -a"
open files (-n) 1048576
cpu time (seconds, -t) unlimited
...
% container run --ulimit nofile=131072 --ulimit cpu=60 -it --rm ubuntu:24.04 bash -c "ulimit -a"
open files (-n) 131072
cpu time (seconds, -t) 60
...
```
+254
View File
@@ -0,0 +1,254 @@
# Mounts and volumes
Share data from your host with containers, create named volumes with better
performance and lifecycle guarantees than bind mounts, and mount temporary,
memory-backed storage with tmpfs.
## Share host data
With the `--volume` option of `container run`, you can share data between the host system and one or more containers, and you can persist data across multiple container runs. Use the volume option to mount a folder on your host to a filesystem path in the container.
This example mounts a folder named `assets` on your Desktop to the directory `/content/assets` in a container:
<pre>
% ls -l ~/Desktop/assets
total 8
-rw-r--r--@ 1 fido staff 2410 May 13 18:36 link.svg
% container run --volume ${HOME}/Desktop/assets:/content/assets docker.io/python:alpine ls -l /content/assets
total 4
-rw-r--r-- 1 root root 2410 May 14 01:36 link.svg
%
</pre>
The argument to `--volume` in the example consists of the full pathname for the host folder and the full pathname for the mount point in the container, separated by a colon.
The `--mount` option uses a comma-separated `key=value` syntax to achieve the same result:
<pre>
% container run --mount source=${HOME}/Desktop/assets,target=/content/assets docker.io/python:alpine ls -l /content/assets
total 4
-rw-r--r-- 1 root root 2410 May 14 01:36 link.svg
%
</pre>
## Named volumes
Named volumes offer complementary features to bind mounts. Use a named volume when you
don't need to share data with the host filesystem, and you want better I/O performance
than a bind mount provides.
Create a named volume with `container volume create`:
```bash
container volume create foo
```
By default, a volume uses a journaled `ext4` filesystem. Configure the journal mode and
size at creation time with `--opt`:
```bash
# ordered journaling (default)
container volume create --opt journal=ordered myvolume
# writeback journaling with a 64 MiB journal
container volume create --opt journal=writeback:64m myvolume
# full data journaling with an explicit volume size
container volume create --opt journal=journal --opt size=10g myvolume
```
List and remove volumes:
```bash
container volume list
container volume delete foo
```
Show a volume's configuration, including its size and the path to its backing image:
```bash
container volume inspect foo
```
```console
[
{
"configuration" : {
"creationDate" : "2026-08-10T21:39:10Z",
"driver" : "local",
"format" : "ext4",
"labels" : {
},
"name" : "foo",
"options" : {
},
"sizeInBytes" : 549755813888,
"source" : "\/Users\/fido\/Library\/Application Support\/com.apple.container\/volumes\/foo\/volume.img"
},
"id" : "foo"
}
]
```
A volume's image is sparse, so `sizeInBytes` reports the size the volume can grow to —
512 GiB by default — rather than the space it currently occupies on disk.
Remove every volume that has no container referencing it:
```bash
container volume prune
```
> [!WARNING]
> `container volume prune` deletes the volumes and their contents immediately, and the data
> can't be recovered.
Mount a named volume the same way you bind-mount a host directory, using the volume
name as the source:
```bash
container run -it --rm --volume foo:/mnt/foo alpine sh
```
Or with `--mount`:
```bash
container run -it --rm --mount type=volume,source=foo,target=/mnt/foo alpine sh
```
## Anonymous volumes
Using `-v /path` or `--mount type=volume,target=/path` without specifying a source creates
a named volume for you automatically — an anonymous volume. It's named with a bare UUID
(no prefix) and tagged with the `com.apple.container.resource.anonymous` label:
```bash
# Creates an anonymous volume
container run -v /data alpine
```
`container volume list` marks it `anonymous` in the `TYPE` column. For scripting, select it
by its label, since the JSON output has no type field:
```bash
VOL=$(container volume list --format json | jq -r '.[] | select(.configuration.labels["com.apple.container.resource.anonymous"] != null) | .id')
container run -v $VOL:/data alpine
```
> [!NOTE]
> Unlike Docker, anonymous volumes aren't deleted automatically when the container is
> removed with `--rm`. Delete them explicitly:
>
> ```bash
> container volume delete $VOL
> ```
## Tmpfs mounts
A `tmpfs` mount is temporary storage that lives only in the guest VM's memory. When the
container stops, the mount and everything written to it are gone. You can't share a
`tmpfs` mount between containers, unlike a bind mount or a named volume.
Use a `tmpfs` mount when you need high-performance storage and don't need the data to
persist after the container stops.
Use either `--tmpfs` or `--mount type=tmpfs`. Both accept the `size` and `mode` options;
see [Mount options](#mount-options) for the syntax each one takes.
Mount a `tmpfs` filesystem at `/tmpfsmount1` with `--tmpfs`:
```bash
container run --rm --tmpfs /tmpfsmount1 alpine mount -t tmpfs
```
```console
tmpfs on /tmpfsmount1 type tmpfs (rw,relatime)
tmpfs on /dev/shm type tmpfs (rw,nosuid,nodev,noexec,relatime,size=65536k)
tmpfs on /sys/firmware type tmpfs (ro,nosuid,nodev,noexec,relatime)
```
The last two entries are runtime defaults, present in every container. See
[Runtime configuration](./runtime-configuration.md#mask-and-protect-paths-inside-a-container)
for what mounts `/sys/firmware` read-only.
Mount a `tmpfs` filesystem with a 512 MiB size limit using `--mount`:
```bash
container run --rm --mount type=tmpfs,target=/tmpfsmount1,size=512M alpine stat -f /tmpfsmount1
```
```console
File: "/tmpfsmount1"
ID: 89a6eaf01fc1572c Namelen: 255 Type: tmpfs
Block size: 4096
Blocks: Total: 131072 Free: 131071 Available: 131071
Inodes: Total: 142352 Free: 142350
```
131072 blocks × 4096 bytes = 512 MiB, confirming the size limit took effect.
Set the mount's permission bits with `mode` (octal, same as `chmod`):
```bash
container run --rm --mount type=tmpfs,target=/tmpfsmount1,size=512M,mode=1777 alpine stat -c '%a' /tmpfsmount1
```
```console
1777
```
## Mount options
Mount-time options go on `container run` or `container create`, using `--mount`,
`--volume`, or `--tmpfs`. Creation-time options go on `container volume create`, using
`--opt`.
### Options for `--mount`
`--mount` takes comma-separated `key=value` pairs. An unrecognized key is an error.
| Key | Values | Applies to | Description |
|---|---|---|---|
| `type` | `bind` (alias `virtiofs`), `volume`, `tmpfs` | — | The kind of mount to create. Defaults to a bind mount. |
| `source`, `src` | host path, or volume name | bind mounts, named volumes | The host directory to share, or the name of the volume to mount. Omit it for a tmpfs mount, or to get an [anonymous volume](#anonymous-volumes). |
| `destination`, `dst`, `target` | absolute container path | all | Where the mount appears inside the container. |
| `readonly`, `ro` | key only, no value | all | Mount read-only. |
| `size` | for example `512M`, `1G` | tmpfs only | Upper bound on the guest memory the mount can consume. |
| `mode` | octal, for example `1777` | tmpfs only | Permission bits for the mount point, the same as `chmod`. |
### Options for `--volume`
`--volume` uses the colon-separated form `[source:]destination[:options]`, comma-separated
if there is more than one:
```bash
container run --rm --volume foo:/mnt/foo:ro alpine sh
```
| Key | Values | Description |
|---|---|---|
| `ro` | key only, no value | Mount read-only. |
### Options for `--tmpfs`
`--tmpfs` uses the colon-separated form `destination[:options]`, comma-separated if there
is more than one:
```bash
container run --rm --tmpfs /tmpfsmount1:size=64M,mode=1777 alpine sh
```
| Key | Values | Description |
|---|---|---|
| `size` | for example `512M`, `1G` | Upper bound on the guest memory the mount can consume. |
| `mode` | octal, for example `1777` | Permission bits for the mount point, the same as `chmod`. |
### Options for `container volume create`
| Key | Values | Description |
|---|---|---|
| `size` | for example `10g` | Size of the volume's filesystem image, fixed at creation time. |
| `journal` | `ordered` (default), `writeback`, `journal`, each optionally as `<mode>:<size>` | The `ext4` journal mode, and optionally the journal size — for example `writeback:64m`. |