Infrastructure
Runs on our own steel.
One VPS in Warsaw, one database, one process boundary, and a restore we actually rehearse. Every dependency in the request path is something we can restart ourselves.
Hot path dependencies
0
Third-party services that can take the panel down. The list is empty and we intend to keep it that way.
The numbers
- Uptime
- 99.98%
- Events / day
- 1.4M
- External clouds
- 0
- Database
- 1
Rolling 30 days, measured externally.
Ingested, deduplicated, reconciled.
In the request path. Not one.
Cross-database joins are forbidden.
The stack
Five layers, no surprises.
- Edge TLS terminates here. Nothing behind it is reachable from the internet.
Traefik v3
ACME · two independent resolvers · per-host routers
Cloudflare
proxied apex · edge cache · static landing only
- Identity One identity provider. No hand-rolled auth has ever shipped here.
Authentik
OIDC · authorization code · issuer follows request host
Session
host-bound cookie · re-checked on every mutation
- Application Twenty-six modules inside one process boundary, by decision.
SvelteKit server
Node 22 LTS · TypeScript · Zod on every input
Scheduler
in-process cron · 2-minute push cadence · idempotent
AI gateway
every model call routed · budget-gated · logged
- State Snake_case in the database, camelCase in the code, one mapping layer.
PostgreSQL 17
single app database · forward-only migrations
Redis 7
Streams · at-least-once · consumer groups per module
MinIO
S3 API · presigned URLs · bind mounts, never named volumes
- Durability A backup nobody has restored is a rumour.
Backblaze B2
nightly · encrypted before it leaves the host
sops + age
secrets in git, keys emphatically not
Why
Self-hosted is a cost decision that became a product decision.
The original reason was arithmetic. A managed equivalent of this stack — database, object storage, queue, identity, edge — prices at several thousand euro a month before a single order is processed, and prices per event, which is the worst possible shape for a business whose event volume is its inventory.
The reason it stayed is different. Owning the machine means every failure is one we can reproduce, and every fix is one we can ship tonight. There is no support ticket in the loop between noticing a problem and ending it. For two operators, that is not a preference — it is the difference between running the business and waiting on it.
A dependency you cannot restart is not a dependency. It is a landlord.
The honest cost: we own the pager. Certificates, kernel updates, disk headroom, backup verification. We priced that in and wrote runbooks for it, which is the part most self-hosting stories quietly skip.
Durability
The backup story, with times in it.
- T+0
Nightly dump
Postgres logical dump plus a MinIO object manifest, taken at a fixed hour and checksummed on the host.
- T+4m
Encrypted, then shipped
Encryption happens on our hardware. What leaves the machine is opaque to the storage provider by construction.
- T+11m
Off-site, verified
Object size and checksum are compared against the local manifest. A mismatch pages, a silence pages louder.
- Monthly
Restore rehearsal
The dump is restored into a throwaway database and a fixed set of row counts is asserted. This is the only test that matters.
Recovery point
Recovery time
Discipline
Rules that outrank convenience.
- Forward-only migrations. A migration runner with a ledger table. No down-migrations, because a down-migration in production is a second untested deploy at the worst possible moment.
- Bind mounts, never named volumes. Every byte of state has a path on the host that a human can find at 3am without knowing Docker's internals.
- Zod at every boundary. Request, response, environment, config. An unvalidated input is a future incident with a longer write-up.
- Dual-stack before every cutover. Old and new hostnames served in parallel, certificates issued ahead of the flip, and a rollback that is one environment variable.
- Verified on production or not done. There is no staging tier. "Looks like it works" is not a state this project recognises.