Production Infrastructure BlueprintBlueprint Rev. 2026-08-09

Records #

ADRDecisionDateStatus
0001 One ARM node on Hetzner Cloud, running k3s
One Hetzner Cloud CAX21 — four Ampere vCPUs, 8 GB — running k3s, provisioned by Terraform and configured by Ansible.
2026-07-04 Accepted
0002 Java 21 and Spring Boot, with the footprint measured and paid down in public
Java 21 on Spring Boot, Spring MVC on virtual threads, as a Gradle multi-project with a shared platform module for observability, error handling and event contracts.
2026-07-04 Accepted
0003 Delivery is pulled by Flux, never pushed by CI
The cluster pulls.
2026-07-04 Accepted
0004 Kafka as the broker, single node, replication factor 1
Apache Kafka in KRaft mode, one broker, replication factor 1.
2026-07-16 Accepted
0005 Transactional outbox with a polling relay, not change data capture
A polling relay inside the API service, claiming rows with FOR UPDATE SKIP LOCKED.
2026-07-16 Accepted
0006 Redis holds a read model, and the two services declare its shape separately
Redis stores it as JSON under a single typed serialiser.
2026-07-16 Accepted
0007 Postgres and Redis gate API readiness; Kafka does not
The readiness group includes Postgres and Redis.
2026-07-16 Accepted
0008 Every input is pinned, every output is signed
Inputs are pinned by immutable identifier.
2026-07-22 Accepted
0009 Point-in-time recovery from archived WAL, with no operator and no derived image
Postgres' built-in continuous archiving: archive_command ships every completed WAL segment to the bucket, a nightly CronJob takes a pg_basebackup, and a restore lays down a base backup and lets the server replay archived WAL to a target time with restore_command.
2026-08-06 Accepted
0010 Observability is self-hosted, budgeted, and measured at the gateway
Prometheus and Alertmanager for metrics and alerting, Loki with Grafana Alloy for logs, Tempo for traces, and Grafana over all three, provisioned as code.
2026-07-29 Accepted

How they are written #

Every record has the same three parts. Context is the problem and the options, usually as a table with what each would cost here. Decision is one or two sentences. Consequences is the longest part, and the one that matters: what the decision gives up, what it makes true elsewhere in the repository, and when it should be revisited.

Records are amended rather than rewritten. ADR 0008 carries an amendment dated after it was accepted, explaining that a control it described had never been implemented — and what testing the implementation then found. A few were backfilled after the fact; their dates say both when the decision was made and when it was recorded.

esc
↑ ↓ to move↵ to open/ or ⌘K to search