Skip to content

Latest commit

 

History

History
187 lines (148 loc) · 7.78 KB

running-in-docker.md

File metadata and controls

187 lines (148 loc) · 7.78 KB

Running CuOS as a Docker container

docker-create describes a system as a Docker Compose service: the OS itself, with systemd as PID 1 and its own Docker inside, on the Docker you already have. It builds nothing and starts nothing — it writes two files, and starting, stopping and inspecting the container is plain docker compose.

It is meant for trying a configuration out locally. A container shares the host's kernel and has no disk, so it behaves like CuOS in an LXC container, not like a system on hardware.

Prerequisites

  • Docker with Compose 2.30 or newer (for post_start, see below)
  • jq
  • An x86_64 host for the default image. Elsewhere, see Choosing the image.

Quick start

From the directory the configuration is in:

./cuos-release/tool.sh docker-create my-system.json
docker compose -f CuOS-my-system.compose.yml up -d
docker compose -f CuOS-my-system.compose.yml exec cuos-my-system bash

NAME is the artefact name, as everywhere else (./cuos-release/tool.sh name my-system.json). Two files appear in the current directory:

File What it is Commit it?
NAME.compose.yml The service, its volume and its networks Yes — it holds no secrets
NAME.system.json The merged configuration, mounted as /system_init.json No — it is git-ignored for you

The configuration is the merged one, and "#include" pulls secrets into it (system_file_password, registry passwords), so docker-create adds it to .gitignore — together with ./.docker/, which it writes too. Both are regenerated by running docker-create again, which is also what a fresh clone needs before up.

Running docker-create again overwrites NAME.compose.yml. Put services of your own — a test client, a fake device — in a file that includes it:

# compose.yml
include:
  - CuOS-my-system.compose.yml
services:
  tester:
    image: alpine
    command: sleep infinity
    networks: [lan]

Two systems included this way may share a network by giving it the same name.

What the container gets

Why
entrypoint: /sbin/init The images' own entrypoint only sleeps.
privileged: true systemd, and the Docker daemon inside.
/data as a named volume State, and /var/lib/docker, which the image links to /data/docker.
NAME.system.json → /system_init.json CuOS in a container reads its configuration from there, once.
hostname From system.json.
dns Every dns-server of every network entry, each once.

The configuration is read on the first start only. CuOS copies it to /data and never looks at /system_init.json again, so after changing system.json:

./cuos-release/tool.sh docker-create my-system.json
docker compose -f CuOS-my-system.compose.yml down -v   # -v: forget /data
docker compose -f CuOS-my-system.compose.yml up -d

Without -v the old configuration stays. An update run inside the container lives in the container's own layer: down or a recreated container is back on the image the compose file names.

Choosing the image

Without --platform, lxc_image is used, and os_image if there is none. --platform P selects P_image instead — the same keys as for image:

./cuos-release/tool.sh docker-create --platform rpi-arm64 my-system.json

That is the way to a native container on an arm64 host, an Apple Silicon Mac or a Raspberry Pi: the default images are amd64, and run emulated anywhere else — slowly, if systemd starts at all. A board's image never falls back to os_image, for the same reason as in image.

The image is pinned by tag and digest (name:version@sha256:...). It is pulled while the file is written, with the configuration's registry login, so up needs no login. To pull it again later from a registry that needs one:

DOCKER_CONFIG="${PWD}/.docker" docker compose -f CuOS-my-system.compose.yml pull

The image the container runs

An update compares against /etc/image, which first-run.sh writes. A container started from the image has never run it, so the compose file passes the image in CUOS_IMAGE and CUOS_IMAGE_DIGEST, which CuOS reads on its first start.

Images released before that read nothing from the environment, and would write unknown@unknown. For them the service carries a post_start hook that runs first-run.sh with the right values — but only while /etc/image is missing or unknown@unknown, so that it never undoes an update made inside the container.

Networks

CuOS configures no network inside a container; the compose file does. Each entry of network becomes one Compose network, net<i> for network[i], attached in that order. What only Docker needs to know is in docker.nets[i] — the same pairing as proxmox.nets for Proxmox:

{
  "network": [
    { "dhcp": true },
    { "ip-address": "10.20.0.5", "network-mask": "255.255.255.0", "gateway": "10.20.0.1" },
    { "ip-address": "192.168.50.7", "network-mask": "255.255.255.0" }
  ],
  "docker": {
    "nets": [
      "lan",
      { "name": "iot", "static": true },
      { "name": "field", "driver": "macvlan", "parent": "eth1" }
    ]
  }
}
Key of docker.nets[i] Default
(a string) Shorthand for name. null keeps the element's place.
name net<i> The Compose network. Lets other services — and other systems — join it.
driver bridge bridge, macvlan or ipvlan.
parent The host interface, for macvlan and ipvlan. Required there.
static false Use ip-address, network-mask and gateway of network[i]: the network gets that subnet, the container that address. Always on for macvlan and ipvlan, which have no DHCP.

Three ways, then:

  • A bridge with an address from Docker — the default, and what works everywhere, Docker Desktop included. The addresses in system.json are ignored.
  • A bridge with the address from system.json ("static": true). The gateway becomes the host's address on that bridge, and traffic leaves through the host as usual. Do not use it for the subnet the development machine itself is in: the bridge would take that subnet over on the host. Two compose projects using the same subnet cannot both start.
  • macvlan or ipvlan, to put the container on a real network next to the host. Linux only — Docker Desktop has no host interfaces to attach to. The host itself cannot reach a macvlan container directly; that is how macvlan works.

A mac-address in network[i] is set on that interface. Interface names from network[i].name are not: inside the container they are eth0, eth1, … in the order above.

docker is a key of the tooling, like proxmox: CuOS ignores it, and it is not part of the configuration schema.

Troubleshooting

Symptom Look at
post_start is rejected Compose older than 2.30. Update it.
exec format error, or the container exits at once on an arm64 host The amd64 image without emulation. Use --platform rpi-arm64.
Pool overlaps with other one on this address space A static bridge whose subnet another network on this host already has.
up fails: the bind source path does not exist NAME.system.json is missing beside the compose file, as after a fresh clone — run docker-create again.
A changed system.json has no effect /data keeps the first configuration: down -v.

See also