Development Workflow
How work gets done at Olly: infrastructure, editor loop, git, tests.
Infrastructure
Four boxes in a single Hetzner VPC. One dev box, three runners.
flowchart LR
subgraph VPC["Hetzner VPC · 10.0.1.0/24"]
dev1["dev-1
Tilt + docker-compose
14 services + infra"]
dev2["dev-2
gh-runner"]
ci["ci
gh-runner"]
uat["uat
gh-runner"]
end
eng["Engineers"] -->|ssh/mosh| dev1
gh["GitHub Actions"] --> dev2
gh --> ci
gh --> uat
| Box | Role |
|---|---|
| dev-1 | Primary dev environment. Tilt, docker-compose, nginx for *.dev.hiolly.com |
| dev-2 · ci · uat | GitHub Actions runners. UAT-as-environment doesn't exist yet — conscious choice while the stack is still moving fast. |
The inner loop
VS Code over SSH → Claude Code in-terminal → Tilt auto-rebuilds on save. Diverging work lives in a git worktree so Tilt keeps running on the main checkout.
flowchart LR vsc["VS Code · Remote-SSH"] --> cc["Claude Code
+ skills"] cc --> wt["git worktree
(if diverging)"] wt --> tilt["Tilt on dev-1
hot reload"] tilt --> svc["Go services"] tilt --> infra["Postgres · Redpanda ·
Keycloak · Temporal · OTel"] svc --> web["*.dev.hiolly.com"]
Claude skills
- olly-debugger — OTel traces → Loki logs → Kafka → Postgres, produces a structured incident report
- service-reviewer — checks coding guidelines, 2PC outbox, OTel spans before merge
- figma-journey-validate — Playwright screenshots vs Figma designs
Git workflow
PR-based in theory; sliding scale by blast radius in practice.
flowchart LR
c["Change"] --> q{"Blast radius?"}
q -->|DB / cross-service| pr["PR → review → CI"]
q -->|one service| pr2["PR, often self-merged"]
q -->|docs / config| dir["Direct push to main"]
pr --> main["main"]
pr2 --> main
dir --> main
main --> tilt["dev-1 Tilt rebuilds"]
Honest note: the PR-for-everything ideal isn't enforced. Small team, one environment — fix is to make review cheap (fast CI, reviewer skills, smaller PRs) rather than add ceremony.
Tilt
The control plane for dev-1. One UI for services, infra, logs, and links.
| What | Why |
|---|---|
| Hot-reload Go services on save | Sub-second feedback, no docker build loop |
| Unified log stream (14 services) | One place to grep |
| Infra as first-class | Same up/down UX for app and Kafka, Postgres, Keycloak, Temporal, OTel, APISIX |
| Health gates | Postgres up → migrations → services → seeders, in order |
| Port-forwards + links | Click to any dashboard (Kafka UI, Temporal, Mailpit, OpenSearch) |
Tests
Four layers. We lean on the outer layers more than typical — the Tilt + OTel inner loop catches most drift live.
flowchart TB unit["Unit · Go per package
pure logic, no I/O"] svc["Service · Go per service
real Postgres + Kafka via testcontainers"] e2e["E2E · tests/e2e
cross-service, 2PC + trace correlation"] pw["Playwright · tests/playwright
employer (46 specs) · member (WIP)"] unit --> svc --> e2e --> pw
- No DB mocking — service tests hit real Postgres/Kafka via testcontainers.
- Cross-service E2E exercises the 2PC outbox + OTel trace propagation.
- Playwright employer covers smoke, auth, quote flow, dashboard. Member specs next.