The Docker Platform

The core server runs two kinds of workload side by side: containerized services in a tiered Docker layout, and a set of host-level services (DNS, VPN, IDS, the VMs, a local LLM) that sit alongside the containers.

Why tiers, not one flat compose

A single big docker-compose.yml has two problems this design avoids:

  1. There is a real order to things. This box also serves the network's DNS, including DoH, so resolution has to come up first - everything else depends on it. Then the reverse proxy (Traefik): it fronts the public site over the outbound tunnel, fronts GitLab, and is the single cert authority for the whole lab - so it has to be up before the apps it carries. Tiers let systemd sequence that dependency graph deterministically instead of leaving it to chance.
  2. Isolation. A flat network lets every container reach every other one. Tiers run on separate bridges, so a container in the apps tier cannot talk to the Git/CI tier laterally.

At a glance

platform · boot-ordered tiers (systemd)
systemctl cat docker-tier0
[Unit] Description=Docker Tier 0 - Traefik After=docker.service net-topology.service Requires=net-topology.service [Service] Type=oneshot RemainAfterExit=yes ExecStart=/usr/bin/docker compose up -d # drop-in: gate the tier before it starts ExecStartPre=/usr/local/sbin/tier-preflight.sh tier0 traefik
systemctl list-units 'docker-tier*' --no-legend
docker-tier0.service active Docker Tier 0 - Traefik docker-tier1-apps.service active Docker Tier 1 - Apps (cv-site) docker-tier1-gitlab.service active Docker Tier 1 - GitLab CE and Runner
# each tier is its own unit; Requires=/After= enforce boot order, not luck

Full mechanics - the tier layout, boot ordering, loopback aliases, host services, and the cert-store gotcha →