Docker
GitHub: https://github.com/ricmatsui/home/tree/master/roles/docker
Docker runs on each node in the cluster to provide container support. The nodes are joined into a swarm for easier management of containers, networks, secrets, and other resources. The swarm also enables service communication across the entire cluster.
pi@pi:~ $ sudo docker node ls
ID HOSTNAME STATUS AVAILABILITY MANAGER STATUS ENGINE VERSION
gilz81ihgoq5th9qv67o8oub0 cannoli Ready Active Reachable 27.4.1
rpo4cymiikuh5cbc8qzvte4ym * pi Ready Active Leader 28.5.2
5q8pi3s1j7aahpky5gaxo4mbl tart Ready Active Reachable 28.5.2Nodes are labelled so that services can be placed to meet the specific constraints required. For example, the ingress node is labelled with "home.network": "ingress". Communication between nodes travels over the Wireguard overlay network 192.168.3.0/24.
pi@pi:~ $ sudo docker node inspect pi
[
{
...
"Spec": {
"Labels": {
"home.camera": "connected",
"home.instance_size": "small",
"home.instance_type": "pi",
"home.network": "ingress",
"home.storage": "external"
},
"Role": "manager",
"Availability": "active"
},
...
"Status": {
"State": "ready",
"Addr": "192.168.3.33"
},
...
}
]Networking
Swarm
Docker Swarm provides overlay networks that span the entire cluster.
pi@pi:~ $ sudo docker network ls
NETWORK ID NAME DRIVER SCOPE
7fa0320b799d bridge bridge local
ed93bc768117 docker_gwbridge bridge local
...
lvjd5edvznbk ingress overlay swarm
mvlrjbe7qx4f temporal_temporal overlay swarm
0uer3zrkkl3f traefik_traefik overlay swarmOverlay networks in the swarm are given subnets and containers are assigned addresses from the subnets.
Here the swarm is configured to use the 10.10.0.0/16 address space and networks are given /24 subnets.
pi@pi:~ $ sudo docker inspect traefik_traefik
[
{
"Name": "traefik_traefik",
"Id": "0uer3zrkkl3f4sou7gehdsq5i",
"Created": "2025-11-05T20:02:53.340102187Z",
"Scope": "swarm",
"Driver": "overlay",
"EnableIPv4": true,
"EnableIPv6": false,
"IPAM": {
"Driver": "default",
"Options": null,
"Config": [
{
"Subnet": "10.10.1.0/24",
"Gateway": "10.10.1.1"
}
]
},
...
"Containers": {
"247836f56c003fa9e5d292b6c003672210fad484fe6e4f28d6cf97a19e29eab4": {
"Name": "traefik_traefik-forward-auth.1.vviin5jl8mqf47fkt5zkybnqk",
"EndpointID": "a5efed0731a0d0b381d074e4027e4c4407d62c508f8da020310c5a877b99260c",
"MacAddress": "02:42:0a:0a:01:19",
"IPv4Address": "10.10.1.25/24",
"IPv6Address": ""
},
"8938090adbe9cb346004dbc4ecfe2ededac16a52c6e771c834f858375f165a03": {
"Name": "traefik_traefik.1.rlpz6v41b5za9dlilue3lolle",
"EndpointID": "2023751ffc88cb847e3f17ee34db2897989a3bfa4c6bccded5d6c9bb8a775019",
"MacAddress": "02:42:0a:0a:01:1d",
"IPv4Address": "10.10.1.29/24",
"IPv6Address": ""
},
...
},
...
}
]Containers can be assigned to multiple networks and are given virtual interfaces for each network.
0fdd4ee6d081:/home/ui-server# ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host
valid_lft forever preferred_lft forever
459: eth1@if460: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1450 qdisc noqueue state UP
link/ether 02:42:0a:0a:01:2c brd ff:ff:ff:ff:ff:ff
inet 10.10.1.44/24 brd 10.10.1.255 scope global eth1
valid_lft forever preferred_lft forever
465: eth2@if466: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue state UP
link/ether 02:42:ac:12:00:06 brd ff:ff:ff:ff:ff:ff
inet 172.18.0.6/16 brd 172.18.255.255 scope global eth2
valid_lft forever preferred_lft forever
473: eth0@if474: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1450 qdisc noqueue state UP
link/ether 02:42:0a:0a:03:25 brd ff:ff:ff:ff:ff:ff
inet 10.10.3.37/24 brd 10.10.3.255 scope global eth0
valid_lft forever preferred_lft foreverDocker also configures a DNS server for containers so they can resolve service names in the cluster.
0fdd4ee6d081:/home/ui-server# cat /etc/resolv.conf
# Generated by Docker Engine.
# This file can be edited; Docker Engine will not make further changes once it
# has been modified.
nameserver 127.0.0.11
options ndots:0
# Based on host file: '/etc/resolv.conf' (internal resolver)
# ExtServers: [75.75.75.75 75.75.76.76]
# Overrides: []
# Option ndots from: internal
0fdd4ee6d081:/home/ui-server# nslookup traefik_traefik
Server: 127.0.0.11
Address: 127.0.0.11:53
Non-authoritative answer:
Non-authoritative answer:
Name: traefik_traefik
Address: 10.10.1.5Local
Ports can be published on the host so that requests to the host are forwarded to the container.
pi@pi:~ $ sudo docker ps
CONTAINER ID IMAGE ... PORTS NAMES
8938090adbe9 traefik ... 0.0.0.0:80->80/tcp, [::]:80->80/tcp, 0.0.0.0:443->443/tcp, [::]:443->443/tcp, 0.0.0.0:1883->1883/tcp, [::]:1883->1883/tcp, 0.0.0.0:7233->7233/tcp, [::]:7233->7233/tcp traefik_traefik.1.rlpz6v41b5za9dlilue3lolle
...By using macvlan, containers can also be bridged directly to a host’s network interface.
pi@pi:~ $ sudo docker network ls
NETWORK ID NAME DRIVER SCOPE
...
rhic9yfokyea macvlan macvlan swarm
41b667c736df macvlan-config null local
...A pool of addresses can be set to be used for macvlan, along with the parent interface that will be bridged.
pi@pi:~ $ sudo docker inspect macvlan-config
[
{
"Name": "macvlan-config",
"Id": "41b667c736df090b7d7a01c6e4eb2bcd590885fe3897505a6615037667671cc6",
"Created": "2025-10-03T22:37:14.516512194Z",
"Scope": "local",
"Driver": "null",
"EnableIPv4": true,
"EnableIPv6": false,
"IPAM": {
"Driver": "default",
"Options": null,
"Config": [
{
"Subnet": "192.168.1.0/24",
"IPRange": "192.168.1.16/28",
"Gateway": "192.168.1.1"
}
]
},
...
"Options": {
"parent": "eth0"
},
"Labels": {}
}
]By providing a bridged network interface, the container is attached to the host’s network, allowing for more network access such as sending Wake-on-LAN packets, mDNS, etc.
ricardo@cannoli:~$ sudo docker inspect 3fc691c2eb76
[
{
"Name": "/home-assistant_home-assistant.1.j2rdwaecyb363om5n7r6xjs1r",
...
"NetworkSettings": {
...
"Networks": {
"macvlan": {
"IPAMConfig": {},
"Links": null,
"Aliases": null,
"MacAddress": "02:42:c0:a8:01:10",
"DriverOpts": null,
"NetworkID": "rhic9yfokyeat8tufwndxult1",
"EndpointID": "954747f30ed1c94a3c541c89ecca1ca113cfd5787f1a7f91d274ef9638d502a7",
"Gateway": "192.168.1.1",
"IPAddress": "192.168.1.16",
"IPPrefixLen": 24,
"IPv6Gateway": "",
"GlobalIPv6Address": "",
"GlobalIPv6PrefixLen": 0,
"DNSNames": [
"home-assistant_home-assistant.1.j2rdwaecyb363om5n7r6xjs1r",
"3fc691c2eb76"
]
},
...
}
}
}
]Stacks
Stacks are used to deploy workloads to the cluster in an organized way and can contain services, networks, volumes, and secrets.
pi@pi:~ $ sudo docker stack ls
NAME SERVICES
home-assistant 1
registry 1
traefik 2
...The nodes will continously monitor the services, restarting and updating them to match the latest stack configuration that was deployed.
pi@pi:~ $ sudo docker service ls
ID NAME MODE REPLICAS IMAGE PORTS
z9jz8v4rtanb home-assistant_home-assistant replicated 1/1 ghcr.io/home-assistant/home-assistant:2025.9.4
nggnsv5ryhsf registry_registry replicated 1/1 registry@sha256:169211e20e2f2d5d115674681eb79d21a217b296b43374b8e39f97fcf866b375
v8rwsrodm5p2 temporal_temporal-server replicated 1/1 temporalio/auto-setup:1.27.2
ivnnk4xycats traefik_traefik replicated 1/1 traefik@sha256:2f603f8d3abe1dd3a4eb28960c55506be48293b41ea2c6ed4a4297c851a57a05
...Stability
For stability, Docker is run in its own systemd slice, separate from the system slice.
pi@pi:~ $ sudo systemctl status
● pi
State: running
Jobs: 0 queued
Failed: 0 units
Since: Thu 1970-01-01 00:00:02 UTC; 55 years 10 months ago
CGroup: /
├─user.slice
│ └─user-1000.slice
│ ├─session-3113.scope
│ │ ├─3516207 sshd: pi [priv]
│ │ └─3516215 sshd: pi
│ ...
├─docker.slice
│ ├─docker-db298c412b1c85bd85e7b38a91b6dbf35b52011782a5874d9fbf5f359b8f17bf.scope …
│ │ ├─3662458 nginx: master process nginx-debug -g daemon off;
│ │ └─3663158 nginx: worker process
│ ├─containerd.service …
│ │ ├─3659355 /usr/bin/containerd
│ │ ...
│ ├─docker.service …
│ │ ├─3660126 /usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock
│ │ ├─3662911 /usr/bin/docker-proxy -proto tcp -host-ip 0.0.0.0 -host-port 80 -container-ip 172.18.0.6 -container-port 80 -use-listen-fd
│ │ ...
├─init.scope
│ └─1 /sbin/init
└─system.slice
├─cron.service
│ └─391 /usr/sbin/cron -f
├─bluetooth.service
│ └─586 /usr/libexec/bluetooth/bluetoothd
...The slice is configured to have a maximum for memory usage and to prioritize the underlying system first. cgroup features are enabled to set these limits. By tuning these limits based on system resources, containers cannot cause resource exhaustion on the host.
pi@pi:~ $ sudo systemctl status docker.slice
● docker.slice - Docker Slice
Loaded: loaded (/etc/systemd/system/docker.slice; static)
Active: active since Thu 2025-06-26 00:44:54 UTC; 4 months 20 days ago
IO: 46.0G read, 77.3M written
Tasks: 313
Memory: 398.9M (max: 1.9G)
CPU: 1d 19h 16min 46.980s
CGroup: /docker.slice
├─containerd.service
│ ├─3659355 /usr/bin/containerd
│ ...
├─docker-4b9723255f48bd1814dc5187c417f2b0819b03dfaf56969416eaaf5389497016.scope
│ ├─3662671 nginx: master process nginx -g daemon off;
│ └─3663155 nginx: worker process
...
└─docker.service
├─3660126 /usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock
├─3662911 /usr/bin/docker-proxy -proto tcp -host-ip 0.0.0.0 -host-port 80 -container-ip 172.18.0.6 -container-port 80 -use-listen-fd
...