Cloud strategy — Hetzner-first, AWS-by-trigger
Start fully on Hetzner. Adopt AWS managed services later, one at a time, only when a concrete scale or reliability signal makes the managed version worth its premium. This is a deployment decision, not an architecture change — every cloud-specific concern already sits behind a gateway/driver, so "move it to AWS" is a swap, not a rewrite.
Where P0 actually stands
Nothing below has been reversed — the strategy holds, and the installation is still 100% Hetzner for compute. Two rows of the seam table have moved on, and both moved away from AWS rather than toward it:
- Secrets KEK is no longer a hand-written Kubernetes Secret. It lives in HashiCorp Vault on the control cluster and reaches the api through the External Secrets Operator (
Agentfy/gitops,helm/agentfy-api/values.dev.yaml→secrets.vaultKey: dev/agentfy-api). The KMS trigger at P3 is unchanged; the thing KMS would replace is now Vault. - Object storage is decided in favour of Hetzner:
terraform/storage.tfcreates one private, versioned bucket per contour, and AWS S3 is kept for Terraform state and backups only. The code side has not caught up — the only storage driver in this repository writes to a local path.
Which repository declares what: Two repositories.
Thesis: own compute, rent reliability
The whole point of Agentfy.ai 2.0 is cheap ephemeral compute — an agent is a DB row, the runtime is a k8s Job that scales to zero. That economic model only works on Hetzner-class pricing; running bursty agent Jobs on EKS + EC2 would destroy it. So:
- Compute, k8s, the
workerJobs (incl. their in-pod browser), egress → stay on Hetzner indefinitely. This is where Hetzner's ~10× price/performance advantage lives, and it's the load that scales. - AWS is adopted only for managed state, durability, keys, and deliverability — the places where AWS's reliability/compliance is genuinely worth paying for — and only when self-hosting that one piece becomes the bottleneck or the on-call liability.
We rent reliability (managed Postgres failover, KMS HSM keys, S3 durability, SES deliverability) selectively; we own the compute that defines our margin.
Why start on Hetzner
- Cost — ~10× cheaper compute + egress; pre-revenue and early-revenue margins survive.
- EU / GDPR — data stays in EU; co-located low-latency cluster.
- Simplicity — one k3s cluster, one bill, no IAM/VPC sprawl while the product is still moving.
- Already abstracted — the codebase isolates every cloud concern behind an interface, so starting Hetzner-only costs us nothing in future optionality (see next section).
The seam that makes this safe
Per CleanSlice conventions, external systems are adapters/submodules behind a gateway (infra drivers and system gateways). Each cloud-replaceable concern has exactly one swap point:
| Concern | Interface / seam | Hetzner default (MVP) | AWS option (later) |
|---|---|---|---|
| Object storage | infra/storage driver (S3 API) | Hetzner Object Storage / R2 | S3 |
| Secrets KEK | system secret gateway (wrap/unwrap) | KEK in a k8s Secret | KMS (wrap DEKs only) |
notification/email IEmailGateway | Resend | SES | |
| Relational/vector state | infra/prisma | self-host Postgres (pgvector + AGE) | RDS/Aurora (app data only) |
| LLM | system/llm gateway | Claude API (external — never AWS Bedrock) | — |
Because each is a single implementation behind a stable interface, a migration is a new driver + a data move + a config flip — never a refactor. That's why we can safely defer all of it.
Phases & triggers
Bands are rough and signal-driven — the signal is what triggers a move, the user count is just the band where that signal usually appears. Don't migrate on the number; migrate on the pain.
| Phase | Rough band | Signal that opens it | What changes |
|---|---|---|---|
| P0 — Launch | 0 → ~1k users / pre-PMF | shipping the MVP | 100% Hetzner. Self-host everything; Resend; KEK in k8s Secret; Hetzner OS. |
| P1 — Traction | ~1k → 10k / first revenue | ops toil on a single component exceeds its value | Adopt the cheap managed conveniences that remove toil: object storage → S3/R2 if you need CDN/durability SLAs; email → SES if volume/deliverability demands it. Compute stays put. |
| P2 — Scale & SLAs | ~10k → 100k / paid SLAs, on-call pain | self-hosting stateful systems becomes the bottleneck/risk | Move app Postgres → RDS/Aurora for automated HA failover + PITR + read replicas. Keep the LightRAG/AGE Postgres self-hosted (managed PG blocks the AGE extension). |
| P3 — Enterprise / compliance / global | 100k+ / SOC2, enterprise, multi-region | compliance or global latency requirements | KMS for HSM-backed KEK + audited rotation; managed vector/graph at huge scale (OpenSearch / Neptune / Qdrant Cloud); multi-region reads. |
What stays on Hetzner — permanently
These are not migration candidates; moving them to AWS would raise cost without buying reliability we need:
- k3s cluster,
corepool (api/app/admin/LightRAG). - The
workerspool + ephemeralworkerJobs (with the in-pod headless browser) — the cost-sensitive, scale-out load. - Egress / bandwidth — Hetzner's flat, generous egress vs AWS metered egress is a decisive margin lever.
- Redis (queue/pub-sub/locks) — cheap to self-host; managed only if Redis ops ever dominate.
What can migrate, and exactly when
| Component | Default | Migrate to AWS when… | One-way door? |
|---|---|---|---|
| Object storage | Hetzner OS / R2 (S3-compatible) | need CDN, multi-region, or contractual durability/compliance | No — both speak the S3 API; bucket sync + config flip. |
| Resend | per-email cost dominates at high volume, or deliverability/reputation needs SES | No — swap IEmailGateway + DNS records. | |
| App Postgres | self-host (CNPG) | you need automated failover + PITR + read-replica fan-out and DBA toil > managed bill | Soft — logical replication cutover; plan a maintenance window. Split out LightRAG/AGE PG first. |
| Secrets KEK | k8s Secret | SOC2/enterprise demands HSM-backed keys + audited rotation | No — values stay in PG; re-wrap DEKs, bump kekVersion. |
| Vector / graph (huge scale) | pgvector + AGE | a single base outgrows pgvector/AGE (benchmark first) | Soft — re-embed/rebuild indexes (derived data, not migrated). |
Caveat that shapes P2: LightRAG's graph store needs Apache AGE, which managed Postgres (RDS/Neon) does not allow. So the moment app-data moves to RDS, you already run two Postgres (app on RDS, LightRAG/AGE self-hosted) — or move the graph to Neo4j Aura. Decide this at P2, not by accident.
Migration mechanics (why each cutover is low-risk)
- Storage: Hetzner OS and S3/R2 are all S3-API →
rclone/lifecycle sync + change one driver config + env. No application code change. - Email: implement the SES
IEmailGateway, verify domain/DKIM, flip the binding. Rollback = flip back. - KEK → KMS: the secret gateway wraps/unwraps DEKs; KMS only changes how the DEK is wrapped. Encrypted values stay in Postgres untouched; re-wrap under a new
kekVersion, keep the old for decrypt during rollover. - Postgres → RDS: standard logical-replication/
pg_dumpcutover behindinfra/prisma; the schema and app are identical. Split the AGE/LightRAG database out before the move. - Vector/graph: indexes are derived — never "migrated". Stand up the new backend and re-embed from the source markdown/documents; flip retrieval when caught up.
Cost note
Hetzner keeps fixed infra cost low through P0–P1 while revenue is thin. Each AWS adoption is a deliberate trade of money for managed reliability at a point where the reliability is worth more than the cash — never a default. The bill grows with the obligations (SLAs, compliance), not ahead of them.
Guardrails (non-goals)
- Don't pre-migrate. No AWS service enters before its trigger signal is real. Optionality is already preserved by the gateway seam — buying it early just adds cost and IAM/VPC surface.
- Don't move compute to AWS. The ephemeral-agent economics depend on Hetzner-priced compute and egress. AWS is for managed state/keys/durability, not for running agent Jobs.
- Treat each migration as reversible unless proven otherwise (the table marks the soft one-way doors); keep the Hetzner path runnable for rollback through the transition.
See also
- Resources & implementation — the concrete inventory this strategy phases.
- GitOps (ArgoCD) — how the persistent state is delivered.
- Locked decisions · Open questions — storage provider, vector/graph backend, billing provider are the open inputs to P1–P3.