Diagrams¶
The homelab has two maps and one diagram for each area. Each diagram is a D2 text file in
docs/assets/diagrams/. A workflow renders every
file on each push to main, so the images show what the repo holds now.
This page tells you how to read the two maps, how to go from a box to its docs, and how to change a diagram or copy the setup into your own repo.
The two maps¶
Select an image to open it full size.
Network and service flow¶
Which parts talk to each other, and how a request, a deploy, a secret or an alert moves through the homelab.
Overview¶
Everything that exists, grouped by where it runs: the network edge, the physical hosts, the Talos cluster, storage and the smart home.
Read the network flow¶
| Mark | Meaning |
|---|---|
| Solid line | Requests or data: HTTP traffic, SQL, volumes, scrapes. |
| Dashed line | Control: deploys, secrets, DNS, certificates, alerts. |
| Box with a title | A group. A line goes to the group, not to one card in it. |
| Gold dashed card | A part that is not on the main path, for example the Tailscale VM. |
The groups go from the outside in, top to bottom: external services, GitOps, the network edge, ingress, the workloads, storage, and at the bottom the physical hosts. Every line points down. Flows that go back up, such as Kargo pull requests to GitHub or the scrapes of the hosts, are not drawn, so the lines stay readable. The area docs describe them.
The diagram does not show IP addresses, VLANs or ports. Those are in the area docs, so each value is written once. Use the table below to go from a group to its docs.
| Group | What it holds | Docs |
|---|---|---|
| Network edge | Modem, UniFi gateway, switch, access point, local.bigd.no DNS |
UniFi, DNS, Cloudflare |
| Ingress and cluster networking | Traefik gateways, Cilium VIPs, cert-manager, external-dns, KEDA interceptor | Network, Traefik, Cilium, certificates |
| GitOps and config | Argo CD, Kargo, Argo Rollouts, KEDA, External Secrets, Reloader, kro | Delivery, secrets |
| Security and policy | Authentik, Falco, Kyverno, Policy Reporter, trivy-operator | Identity, policy |
| Sites and own apps, AI, Media | The workloads | Spec, workloads, media stack, innestemme |
| Databases and object storage | CloudNativePG, the Postgres clusters, Valkey, Garage | Data |
| Observability | Prometheus, Alertmanager, Grafana, Loki, Tempo, Pyrra, the exporters | Observability |
| Storage and backups | Proxmox CSI, NFS CSI, VolSync, etcd backups | Storage, backups |
| Physical hosts | Three Proxmox nodes, the Synology NAS, the Tailscale VM | Cluster, remote access |
Follow one request¶
A public request to nordbye.it:
- The browser resolves the name in Cloudflare. Cloudflare proxies the request to the home IP address.
- The UniFi gateway forwards ports 80 and 443 to the public Traefik gateway, a Cilium VIP.
- Traefik matches the HTTPRoute and sends the request to the portfolio Service.
- Traefik sends a trace to Alloy, and Prometheus scrapes the metrics. A failed probe pages Discord through Alertmanager.
A private request to portfolio-stage.local.bigd.no starts at the UniFi gateway, which
answers the local.bigd.no name with the private Traefik VIP. Traefik sends the request to
the KEDA interceptor. When the app is at zero replicas, the interceptor holds the request
until the pod is ready (KEDA).
Area diagrams¶
Each area page has its own diagram that shows one part in more detail.
| Area | Page |
|---|---|
| Layers, from the internet to the NAS | Spec |
| Cluster layout, Talos upgrades, GPU passthrough, Terraform stacks | Talos, upgrades, GPU, Terraform |
| Traefik, Cilium L2 and policies, certificates, DNS, Cloudflare, UniFi, remote access | Network and its pages |
| Argo CD, CI, Kargo, KEDA | Delivery and its pages |
| Secrets, Authentik | Secrets, identity |
| Signals, traces, alerts, cost, SLOs, the SRE loop | Observability and its pages |
| Kyverno reports, image provenance | Policy |
| Postgres, storage classes, backups, PBS | Data, storage, backups |
| Media stack, innestemme, reelsmith, verksted, the fun room | Apps: one page per app |
How the diagrams are built¶
- The source is a
.d2file indocs/assets/diagrams/. It holds the cards, the groups and the lines as text. - D2
v0.7.1places the cards with the ELK layout engine and writes an SVG. - Each card has an icon URL: the software's own logo from dashboard-icons, the Kubernetes resource icons for Kubernetes objects, and Iconify for the rest. D2 downloads each icon during the render and puts it in the SVG. The repo stores no icon files.
- On a push to
mainthat changes a.d2file,render-assets.yamlrenders every diagram, converts each SVG to a PNG withrsvg-convert, and commits the images back tomain. - The docs site embeds the SVG. The GitHub README links the PNG, because GitHub serves a raw SVG with a content security policy that blocks the embedded icons.
Iconify can refuse the runner with HTTP 429 for some minutes. The workflow then keeps the committed SVG for that diagram and writes a warning. The next push renders it again.
Change a diagram¶
- Edit the
.d2file. To add a part, copy a sibling card in the same group and change the label and the icon. - Render locally with
make diagram. The target runs D2 in Docker, so you install nothing. - Open the SVG or PNG and check the layout.
- Open a pull request with the
.d2change. The rendered images onmaincome from the workflow.
Rules that keep the layout readable:
- Draw a line from group to group. A line into one card of a grid group stops ELK from putting the groups in layers, and the sheet goes flat.
- A line that points to a higher layer is written
upper <- lower. The formlower -> upperturns the layers upside down. - Give each style rule for lines in both forms,
(** -> **)and(** <- **). Lines without a rule get the D2 default colour. - Keep a diagram that the docs site embeds at 1000 px wide or less. The text column is 688 px, and a wider image shrinks until you cannot read it. The two maps are wider, because you open them full size.
- Use the site colours from
docs/assets/stylesheets/theme.css. Copy theclassesblock of an existing diagram.
A new app on a gateway also gets a card in both maps. The checklist for that is in
ai/skills/expose-app/SKILL.md.
Let an AI agent draft the diagram¶
An AI coding agent is good at this work. It can read every manifest, Helm values file and Terraform stack in a repo in minutes. It then lists each component and each connection between them, and writes the result as D2 text. A person needs hours to do the same audit by hand, and usually misses a part.
I use this method in my homelab and in my professional work as a platform engineer. In both places the agent finds the parts that the diagram does not show yet. The person checks the result.
- Give the agent the folders that describe the system, for example
k8s/andterraform/. Also give it one existing.d2file as an example of the style. - Ask it for a list first: the parts that run but are not in the diagram, and the parts in the diagram that no longer run. Read the list before the agent edits.
- Let the agent edit the
.d2file and render it withmake diagram. - Open the image and check it. An agent reads the code, not the live system. A part that is in the repo but switched off can still show on the diagram.
A prompt that works:
Read k8s/ and terraform/. List every service, operator and external
dependency that is not in docs/assets/diagrams/network-flow.d2, and every
card in that file that has no code behind it. Do not edit yet.
Do not give the agent secrets or a kubeconfig with write access. It needs read access to the repo only.
Use the setup in your own repo¶
You need three things:
- One or more
.d2files. - The
diagramtarget in theMakefilefor local renders. - A copy of
render-assets.yamlwithout the logo step. Change the paths and thegithub.repository_ownercheck.
A small start:
direction: down
vars: {
di: https://raw.githubusercontent.com/homarr-labs/dashboard-icons/main/svg
}
internet: Internet
edge: Network edge {
gw: Gateway {icon: ${di}/unifi.svg}
}
cluster: Cluster {
traefik: Traefik {icon: ${di}/traefik.svg}
app: My app {icon: ${di}/nginx.svg}
}
internet -> edge: HTTPS
edge -> cluster: 80, 443
Render it with:
docker run --rm -v "$PWD:/work" terrastruct/d2:v0.7.1 --layout elk /work/start.d2 /work/start.svg