mirror of
https://github.com/apple/container.git
synced 2026-11-06 05:53:09 +00:00
- Closes #457. - A container can now declare additional DNS names for a network attachment with a repeatable `alias` property on `--network`, including subtree aliases that match every name below a domain. - With the default domain `test`, this container answers to `gateway.test` by its name, to `api.test` by the exact alias, and to `web.gateway.test`, `a.b.gateway.test` and every other name below `gateway.test` by the subtree alias. The motivating case is a gateway container in front of a local Kubernetes cluster that answers for `*.gateway.test`, with an ingress controller routing on the `Host` header. - An alias is an exact name, or a name with a leading dot that matches every name below it. A name without a dot gets the default domain, as container names do. A subtree alias must be below a name the same attachment owns, and exact names are unique across all containers, so a container cannot claim another container's namespace. The rules for one attachment are checked when a container is created, again when a stored container starts, and again by the network helper when it registers the aliases. See `docs/architecture/dns.md` for the design, the invariants and the security considerations.
267 lines
12 KiB
Markdown
267 lines
12 KiB
Markdown
# 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.
|
|
|
|
## Give a container additional DNS names
|
|
|
|
By default a container is reachable by one DNS name, built from its name and your DNS domain. Use the `alias` option of `--network` to give a container more names that resolve to the same address. This is useful when one container, such as a reverse proxy or an ingress gateway, answers for several names, or for every name under a domain.
|
|
|
|
Aliases are served by the same DNS service as container names, so set up a DNS domain first, as described in [Set up DNS-based container names](#set-up-dns-based-container-names). The examples below assume the domain is `test`.
|
|
|
|
An alias is either an exact name, or a name with a leading dot that matches every name below it. Repeat `alias` to add more than one:
|
|
|
|
```bash
|
|
container run -d --name gateway --network default,alias=api.test,alias=.gateway.test nginx:latest
|
|
```
|
|
|
|
This container is reachable as `gateway.test` by its name, as `api.test` by the exact alias, and as `web.gateway.test`, `a.b.gateway.test` and any other name below `gateway.test` by the subtree alias:
|
|
|
|
```console
|
|
% curl http://web.gateway.test
|
|
```
|
|
|
|
A name with a leading dot matches every name below it, at one or more labels, but not the name itself. `.gateway.test` matches `web.gateway.test` and `a.b.gateway.test`, and does not match `gateway.test`, which the container already has as its own name. Matching is by whole labels, so `evilgateway.test` does not match. Names are not case sensitive.
|
|
|
|
Like container names, an alias that has no dot gets your default DNS domain. With the domain `test`, `alias=api` is `api.test` and `alias=.gateway` is `.gateway.test`, so the example above can also be written `--name gateway --network default,alias=api,alias=.gateway`. A name that contains a dot is used as written, so `alias=api.internal` is not given the domain. The names are stored with the domain added, so they stay the same if you later change the default domain.
|
|
|
|
An alias that contains `*` is rejected. Use the leading-dot form instead, which also avoids quoting in the shell.
|
|
|
|
### Rules for aliases
|
|
|
|
* A name with a leading dot must be below a name that the same network attachment owns, which is its own hostname or one of its exact aliases. `gateway.test` with `.gateway.test` is allowed. `.apps.test` on a container that has no `apps.test` name is not.
|
|
* An exact alias needs at least two labels, such as `api.test`, and the part after the leading dot of a subtree alias needs at least two labels too. A name with a single label is not under any domain, so macOS would never send queries for it to the container DNS service. When a default DNS domain is configured, a name without a dot gets the domain, as described above. When none is configured, `api` and `.test` are rejected.
|
|
* Every exact name is unique across all containers, whether they are running or stopped. A new container cannot use an alias or a hostname that another container already has, and an alias cannot match another container's name.
|
|
* A container can have at most 32 aliases for each network.
|
|
* Aliases are fixed when the container is created.
|
|
|
|
The exact name always wins over a subtree. If another container is named `db.gateway.test`, then `db.gateway.test` goes to that container, and the other names below `gateway.test` go to the gateway.
|
|
|
|
### Things to know
|
|
|
|
* A proxy that answers for every name under a domain receives the requested name in the `Host` header of each request. Treat that header as untrusted input.
|
|
* macOS sends a query to the container DNS service only for domains you registered with `sudo container system dns create`. An alias outside those domains is not reachable from your Mac. A domain created with `--localhost` is answered by a different service that does not know about aliases, so aliases under it do not resolve.
|
|
* To check an alias without changing your Mac's resolver configuration, query the DNS service directly:
|
|
|
|
```console
|
|
% dig +short @127.0.0.1 -p 2053 web.gateway.test
|
|
192.168.64.5
|
|
```
|
|
|
|
## 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.
|