oll.am · CI/CD & DevOps assessment · learning + reliability audit · 2026-07-03

You built a good factory. Now make it a reliable one.

An honest audit of the oll.am delivery pipeline against how the industry measures itself — the DORA keys, trunk-based delivery, GitOps, progressive delivery, supply-chain security, observability. What we do genuinely well, where the real risk hides, and the ranked road to a system you can trust while you sleep. Written to be read, not skimmed: every claim is grounded in our real files and cited to a source you can check.

The one-paragraph verdict

The build-and-ship half of DevOps is genuinely strong — a decoupled, tag-triggered, CI-green-gated, dependency-ordered, health-checked pipeline that most funded teams don't have. The run-and-observe half barely exists: no metrics, no alerting, no external uptime monitor, one un-redundant VPS, and backups that have never been restored. Today an outage is discovered by a user, not by us. Composite maturity: Level 3 of 5 — an Advanced pipeline bolted to Beginner observability. The highest-value next moves are cheap.

Part 1 · What we actually have (audit of the real files)

This section is a precise read of .github/workflows/*, the CI/CD constitution (main is the Truth), and the platform topology — not the aspiration, the code on disk.

The gate ladder — what CI verifies, per service

CI is the gate; it ships nothing. Each service climbs its own path-scoped ladder before a commit is eligible to deploy. This is the strongest part of the whole system.

Service · workflowGate ladder (in order)Job dependency
oll-core
oll-core.yml
1. DTO-drift — regen pydantic models from openapi.yaml, git diff --exit-code fails on drift → 2. ruff lint → 3. alembic migrate + pytest --cov on a real postgres:17-alpine service container (matches Neon's major) → 4. Schemathesis contract-fuzz vs the sliced spec on a booted gunicorn Core (not_a_server_error + response_schema_conformance, health paths excluded) Steps 1–4 are one test job; docker-smoke runs in parallel as its own job — builds the real image via docker-compose.core.yml, asserts host-port liveness and container Health.Status=healthy. Both jobs must pass.
products
services.yml
(foto · oll-write · oll-model)
1. ruff lint → 2. pytest (mocks Core + providers, keyless — conftest sets env) → 3. docker boot + health — builds each real image, boots keyless via mock/gateway env, container's own Dockerfile HEALTHCHECK must report healthy Matrix over the 3 services. docker-boot needs: test — the boot gate runs only after lint+test pass.
site
site.yml
design-language conformancedesign-audit.sh: token values · font stacks · dark-theme chrome · internal-link integrity · nav presence · naming canon. Fails the build on any drift. Single job.

Why the docker-boot gate is the crown jewel

Its comment names the exact incident it prevents: "the crash that took humaniz.me down — a boot-gate that demanded a key the keyless path never uses." Unit tests pass in a world of mocks; the boot gate builds the real production image and proves it comes up healthy with only the env a PaaS will give it. It catches the class of failure that unit tests structurally cannot — a missing boot env var, a $PORT/bind mismatch, a broken healthcheck — before it can reach prod. This is executable architecture governance, the pipeline analog of springular's ArchUnit rules and our own design-audit.sh: a convention that isn't mechanically enforced rots.

The deploy model — deploy is a decision, not a merge

deploy-all.yml is the CD engine, and it is textbook-correct in structure. Reading it top to bottom:

triggergit tag v*or workflow_dispatch (services = all · changed · list)
gate 1
preflightrefuse to deploy a commit with any failing check-run (gh api check-runs)
gate 2 — resolve WHAT ships
planchanged = diff paths since previous v* tag → only moved services
T0 · oll-core · oll-modelparallel · health-gated
T0 not failed
T1 · foto · writeafter foundation healthy
T1 not failed
T2 · sitelast — points at fresh backends

Three properties make this genuinely good, and worth understanding as why, not just what:

Environments & config management

The constitution defines three environments as deploy targets (production via <svc>.oll.am / tag-gated; staging via stage.<svc>.oll.am / auto-on-merge; optional PR previews) — "same service, two environments = two Coolify apps from one repo, differing only in injected config." That is 12-factor to the letter (factor III): config lives per-environment in Coolify, never in git; secrets are injected, not committed. Honest status: this is designed and documented but the staging apps are not yet stood up — today there is really one environment (production) plus the local full-stack mirror (docker-compose.local-full.yml). Two long-lived branches still exist (main + stage) with sync-stage.yml / automerge-stage.yml keeping them reconciled; the constitution's open decision is to retire the stage branch and make staging an environment.

Part 2 · How the industry measures itself (the WHY, with sources)

To judge our pipeline you need the yardstick the field actually uses. This is the learning core — read it once and the assessment in Part 3 reads itself.

The DORA keys — the only validated delivery scorecard

A decade of DORA / State of DevOps research found four (now five) metrics that predict organizational performance — and, crucially, that throughput and stability rise together in elite teams; you don't trade one for the other. The four you should track:

KeyDefinitionElite bandHow oll.am would measure it — for free
Deployment frequencyhow often you successfully release to prodon-demand / multiple per daycount successful deploy-all.yml runs (or Coolify webhook fires) per week
Lead time for changescommit → running in prod< 1 daydeploy_finish − git commit time; median it. Proxy: merge-to-main → deploy-finished
Change failure rateshare of deploys needing immediate rollback/hotfix~5% (0–15%)deploys that tripped the post-deploy health gate or were followed by a rollback-tagged deploy ÷ total
Failed-deployment recovery time
(the renamed MTTR)
time to recover from a bad deploy< 1 hourbad-deploy timestamp → next-good deploy; median

Note the 2024 report shifted from fixed global cutoffs to relative performance clusters (~Elite 19% / High 22% / Medium 35% / Low 25%) and found, for the first time, the keys not all moving together — so treat the band table as directional, not gospel (2024 State of DevOps). The point for us: because our deploy already emits every timestamp these need, an append-only CSV line per deploy turns all four keys on with zero new tooling — and measuring is the precondition for improving. Today we measure none of them.

Trunk-based development & "a branch must not decide what runs"

DORA identifies trunk-based development as a causal driver of performance, correlated specifically with < 3 active branches, branch lifetimes under a day, and no code-freeze periods. Long-lived branches (GitFlow's develop/release/*) delay integration, hide problems, and discourage refactoring (Fowler, "FeatureBranch"). The modern default is one always-releasable trunk + short-lived feat/* → PR → green CI → squash-merge. The companion principle: build one immutable artifact once and promote that same artifact across environments, varying only config — environments are deployment targets, not branches. Our constitution already reaches this conclusion independently; our residual gap is the second long-lived stage branch (which re-fuses "code line" with "environment") plus Coolify's out-of-band auto-deploy.

GitOps & 12-factor config

GitOps (a CNCF working group) formalizes what our deploy engine already does: version control holds the declarative desired state; deployment is a deliberate, auditable promotion, not a side-effect of a merge; a revert of the manifest is a rollback. Its four principles — declarative, versioned & immutable, pulled automatically, continuously reconciled — are the vocabulary for "deploy is a decision." Paired with 12-factor config (config that varies between deploys lives in the environment; the litmus test is "could this repo go public without leaking a credential?"), you get the clean separation we've designed toward.

Progressive delivery — and the honest single-VPS ceiling

The safe-rollout toolkit: blue-green (two prod environments, flip the router, instant rollback), canary (a small % of live traffic first), rolling (replace instances incrementally), health-gated (no traffic until the health check passes), and feature flags (decouple deploy from release — ship dormant code, turn it on later).

What one VPS can and cannot give you

Coolify genuinely provides: zero-downtime rolling redeploys via start-before-stop (single-Dockerfile apps, health-gated) and one-click rollback to any prior image by commit hash — no rebuild.

One node cannot provide: true blue-green redundancy (that's a rolling swap on the same host, not two environments behind a router), survival of a host failure (one kernel/disk/NIC/power domain — if the box dies, every service dies and rollback can't help), or real traffic-% canary (needs ≥2 instances). Rolling redeploy protects against a bad deploy; it does nothing against a dead host. Note: Coolify does not do rolling updates for Docker Compose deployments (static container names → stop/start). The one progressive-delivery tool that is node-count-independent — and therefore our highest-leverage one — is the feature flag. (Coolify rolling-updates docs)

Supply-chain / pipeline security — proportionate for one person

You don't need SLSA L3 to protect a solo shop; you need the four cheap controls that defend the two things most likely to actually hurt you — a leaked live Stripe key and a hijacked third-party Action.

ControlWhat it doesValue / effort (solo)
Secret scanning + push protectionblocks a push containing a detected secret before it enters git historyvery high / near-zero — directly serves the "exposed key = burned" rule (docs)
Least-privilege GITHUB_TOKENa top-level permissions: block; setting any permission drops the rest to nonehigh / ~5 mindeploy-all.yml already scopes contents:read checks:read; the CI files don't (docs)
SHA-pin third-party Actionsa @v4 tag is mutable/hijackable; a full commit SHA is immutablehigh / low — matters most for non-GitHub actions (docs)
Dependency scanning (Dependabot / pip-audit)auto-PRs for vulnerable deps; a CI audit stephigh / low — the constitution names this as a known missing gate
CodeQL static analysisfinds code-level vulnsmedium / low — free on public repos; enable if quiet
SLSA provenance / signed artifactsattestation for downstream consumers of your artifactslow value / high effort — you ship containers to your own VPS; no external consumer to verify them. Signed git tags are the sensible slice (SLSA levels)

Observability & reliability — the half we're missing

A health check answers "is it up?" (Kubernetes splits this into liveness / readiness / startup). Observability is larger: understanding internal state from outputs — logs, metrics, and traces — enough to ask novel "why?" questions (OpenTelemetry primer). The minimal starting alert set is Google SRE's four golden signals: latency (measure success and failure latency separately — a fast 500 hides otherwise), traffic, errors, saturation (alert on the rising trend, not at 100%). Around that:

Nameable references (Sam's own / public)

ionstarter — tag-release discipline + Vercel preview-per-PR: exactly the "environments-as-targets, ephemeral per-PR" model our constitution proposes, already proven in one of Sam's own projects. springular — a genuinely mature pipeline (in springular2/.github/workflows/): a single cicd.yml orchestrator that uses dorny/paths-filter for change-detection, composes reusable frontend/backend sub-pipelines via workflow_call, runs a docker-compose integration smoke, and ends in a flag-gated deploy job that GETs a Coolify webhook with Authorization: Bearer <token> — the exact pattern our deploy-all.yml deploy step is lifted from. It also governs architecture with ArchUnit-style executable rules — the same instinct as our design-audit.sh + docker-boot gates. Two ideas worth borrowing: reusable workflow_call sub-pipelines (to DRY our three CI workflows) and a central feature-flag block to toggle jobs. Where we're ahead: springular deploys on push to main (flag-gated); we deploy on a tag, CI-green-gated, dependency-ordered, and health-gated across four services.

Part 3 · Honest maturity + reliability assessment

Where oll.am sits on the maturity model

Using the Continuous Delivery Maturity Model (five categories, five levels — Base → Beginner → Intermediate → Advanced → Expert), scored per category because the profile is deliberately uneven:

1 · Base 2 · Beginner 3 · Intermediate 4 · Advanced 5 · Expert
CategoryLevelJustification (grounded in our files)
Build & DeployAdvanced (4)Automated, path-scoped CI; deploy fully decoupled from VC (tag-triggered, no push-to-branch); CI-green preflight; dependency-ordered, health-gated CD; one-click rollback. "Release decoupled from deploy" is a Level-4 marker — we're there structurally.
Test & VerificationAdvanced (4) (backend)Contract-fuzz (Schemathesis), DTO-drift gate, real-Postgres pytest, and the docker-boot gate that reproduces prod boot. Strong. Held back only by no security-scan gate and no perf/load testing.
Design & ArchitectureAdvanced (4)Loosely coupled services over HTTP, database-per-service, frozen-Core discipline, expand-contract migration policy — all DORA "loosely coupled architecture" capabilities.
Information & ReportingBase–Beginner (1–2)No metrics, no DORA measurement, no dashboards, no alerting, no external uptime monitor, no error tracking. Health checks exist but only fire during a deploy — nothing watches prod between deploys. This is the anchor dragging the composite down.
Culture & Reliability infraIntermediate (3)Excellent discipline and documentation for a solo builder, but the runtime substrate is a single VPS (SPOF) with untested backups — reliability engineering, as opposed to delivery engineering, is nascent.

Composite score

3  / 5 — an Advanced delivery pipeline bolted to Beginner observability on a single-node substrate.

The pipeline (categories that decide how well you ship) is genuinely Level 4 and better than most teams with far more resources. The composite is a 3, not a 4, because Information & Reporting is a 1–2 and reliability rests on one un-redundant box with unproven backups. DORA's own guidance is a capability mindset over a maturity one: don't chase "Level 5," measure the four keys and fix the single capability that most moves a lagging one. For us that lagging capability is unambiguous — comprehensive monitoring & observability.

What is genuinely strong — keep doing this

The real reliability risks — ranked by exposure

#RiskWhy it bites
1No observability / alerting / uptime monitorAn outage is discovered by a user (or by Sam happening to look), not by the system. Health checks run only during a deploy; nothing watches prod between deploys. You cannot fix fast what you cannot see. This single gap is the difference between a hobby and a reliable service.
2Single VPS = SPOFOne kernel / disk / NIC / power domain. If the Hetzner box dies, every service — including the money path — is down, and rollback can't help. All the pipeline's "zero-downtime" protects against bad deploys, not a dead host. Maintenance reboots = downtime with no failover.
3Backups untested + 6h PITR windowNeon Free = a 6-hour restore window; a longer incident or a delayed discovery loses data beyond it. And a restore that's never been rehearsed is a hope, not a capability — the first real test must not be during an incident.
4Flying blind on delivery (no DORA)None of the four keys is measured, so "are we getting more reliable?" is unanswerable. The data is already in our deploy logs; we just don't capture it.
5Manual tag + no live staging + fragile externalsPromotion is a manual git tag (fine, but human — easy to forget the CI-green ritual detail); the staging environment is designed but unbuilt, so integration proof happens in prod; oll-model's default provider rides the Groq free-tier rate limit (an in-process, non-durable quota — a traffic spike degrades the model path); and secret push-protection / a dependency-audit gate aren't enabled yet.

The roadmap — highest reliability-per-effort first

Ordered so each step buys the most trust per hour. Effort tags: S = under an afternoon · M = a day or two · L = a project.

#MoveEffortValue — what it buys
1External uptime monitor + alert channel — UptimeRobot HTTP + keyword check per public service (oll.am, core, model, foto, write), plus TLS-expiry + a Heartbeat per cron. Alert to a channel you can't ignore.SVery high. Closes risk #1's worst edge: you learn of an outage in minutes, from the system, not a user. Highest reliability-per-hour move available.
2Secret push-protection + least-privilege permissions: on the CI workflows + SHA-pin third-party actions + a pip-audit gate.SHigh. Under an hour total, all free/native; defends the leaked-Stripe-key and hijacked-action failure modes — the two most expensive solo mistakes.
3Sentry on every service — capture unhandled exceptions with release + suspect-commit context.SMHigh. Turns "something's wrong" into "this exception, this line, this release, N users." Pairs with #1 (black-box) as the white-box half.
4Verify + document backup/restore — actually restore a Neon branch to PITR in a rehearsal; bump the retention window on the stateful services that matter; write a one-page DR runbook (what to do when the VPS dies).SMHigh. Converts a hoped-for backup into a proven one and turns "the box died" from panic into a checklist.
5DORA scorecard — append one CSV/JSON line per deploy in deploy-all.yml (commit time, deploy-finish, changed services, health-gate result); render the four keys on the Control Room.SMedium-high. Makes improvement measurable; the data already exists, we just capture it. Measurement precedes management.
6Stand up the staging environment — the designed-but-unbuilt piece: stage.<svc>.oll.am Coolify apps on a Neon branch + test Stripe, auto-deployed on merge to main; retire the stage branch. Turn Coolify per-app git-auto-deploy OFF everywhere.MHigh. Moves integration proof out of prod, completes the environments-as-targets model, and closes the out-of-band Coolify auto-deploy hole.
7Structured logging + a golden-signals view — request-id-correlated JSON logs (we have the audit-outcome-logging pattern), latency/error/traffic surfaced somewhere queryable.MMedium. The observability step beyond error tracking; makes "why is it slow?" answerable.
8Harden the model path — give oll-model a fallback provider (or a paid tier) so the Groq free-tier rate limit isn't a single availability dependency.SMMedium. Removes a non-durable in-process quota from the critical path for the AI products.
9Redundancy for the money path — a second node + a load balancer (or a managed host for the one revenue-critical service) to survive a host failure.LHigh value, high effort. The only fix for risk #2 (SPOF). Honest "later" — justified once a stranger's dollars depend on uptime, not before. Ship-vs-build: do #1–#5 first; they buy most of the reliability at a fraction of the cost.

The one-sentence recommendation

Spend one afternoon on roadmap items #1–#3 (uptime monitor + alerts, the four cheap security controls, Sentry) and you move from "an outage is a user's problem to report" to "an outage is a page on my phone" — the biggest reliability jump available, at the lowest cost, without touching the already-strong pipeline.

Everything heavier — a second node, full metrics, blue-green — is real but premature. The pipeline is Level 4; the gap is that nothing watches the thing running. Fix seeing before you fix surviving.

oll.am · CI/CD & DevOps assessment · maturity + reliability audit · 2026-07-03 · reads with the CI/CD constitution and The Factory
Grounded in the real .github/workflows/* + constitution; best-practice claims cited inline to DORA, Fowler, GitHub, OpenGitOps, Google SRE, Neon, Coolify, SLSA.