cloudrop.it

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%

Rolling 30 days, measured externally.

Events / day
1.4M

Ingested, deduplicated, reconciled.

External clouds
0

In the request path. Not one.

Database
1

Cross-database joins are forbidden.

The stack

Five layers, no surprises.

  1. 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

  2. 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

  3. 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

  4. 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

  5. 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.

  1. T+0

    Nightly dump

    Postgres logical dump plus a MinIO object manifest, taken at a fixed hour and checksummed on the host.

  2. T+4m

    Encrypted, then shipped

    Encryption happens on our hardware. What leaves the machine is opaque to the storage provider by construction.

  3. T+11m

    Off-site, verified

    Object size and checksum are compared against the local manifest. A mismatch pages, a silence pages louder.

  4. 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

Worst case, one night of data. The push workers are idempotent, so a replay after a restore converges rather than duplicating.

Recovery time

Measured, not estimated — the monthly rehearsal is where the number comes from. An unrehearsed figure would be a guess with a decimal point on it.

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.