Skip to content

Running it yourself

The engine and SDK are open source, and the SDK will talk to any base URL — so you can serve patches from infrastructure you control.

This page is deliberately honest about what that gets you and what it doesn’t.

Static hosting

A bucket and a CDN. Planned, not shipped — there is no static-publish command yet. No rollouts, no targeting, no recall even once it lands.

Reference server

A small service that decides per request, so percentage rollouts, cohorts and rollback recall all work. You run it and you support it.

Patch Cloud

The hosted control plane — rollouts, targeting, analytics, audit, team accounts, and someone to call.

Most teams should use the hosted service. The self-host path exists so that choosing it isn’t a one-way door.

Static publishing is not shipped yet (see the “Static only” tab). This is what the two paths will look like when it is, so you can plan — and because the limits are structural, not temporary:

Capability Static Server
Serve the newest patch Yes Yes
Fingerprint gating Yes Yes
Rollback by republishing Yes Yes
Percentage rollouts No Yes
Cohort / version targeting No Yes
Rollback recall — pull a patch back from devices that already took it No Yes
Device-cap enforcement, adoption metrics No Yes

Rollout bucketing is computed per device, per request. A file in a bucket can’t do that. Recall is worse: it needs to know what each device is currently running, which is a query, not an object.

Wherever you host, this is the only client change:

Patch.configure(.init(
appKey: "pak_…",
apiBaseURL: URL(string: "https://patches.example.com/api/v1")))

And the CLI:

Terminal window
patchcli release --base-url https://patches.example.com
# or: export PATCH_API_URL=https://patches.example.com

Setting apiBaseURL to nil disables remote checks entirely — the app runs whatever module is bundled or already cached.

The shape is the same everywhere: object storage for the module bytes, a CDN in front of it, a small stateless service for the check endpoint, and Postgres for releases and device state. Nothing here is exotic — it fits in the smallest tier of any of these providers.

patchcli ──HTTPS──▶ Cloud Run ──▶ Cloud SQL (Postgres)
│ releases · fingerprints · check-ins
└──▶ GCS bucket (module.wasm.br)
device ◀── Cloud CDN ◀───────┘
  1. Bucket for modules.

    Terminal window
    gcloud storage buckets create gs://patch-modules-prod \
    --location=us-central1 --uniform-bucket-level-access
  2. Postgres. The smallest shared-core tier is plenty — this database holds releases and device check-ins, not user data.

    Terminal window
    gcloud sql instances create patch-db \
    --database-version=POSTGRES_16 --tier=db-f1-micro --region=us-central1
  3. Deploy the check service to Cloud Run, pointed at both.

    Terminal window
    gcloud run deploy patch-api \
    --image=<your-image> --region=us-central1 --allow-unauthenticated \
    --set-env-vars=DATABASE_URL=...,STORAGE_BUCKET=patch-modules-prod
  4. Put Cloud CDN in front of the bucket via a backend bucket on an external HTTPS load balancer. Modules are immutable and content-addressed, so cache them hard.

The check endpoint is a small read — one row lookup plus a hash. It is not the expensive part of your infrastructure.

Fleet Check service Postgres Egress
Up to ~50k devices One small instance Smallest tier Patches are KB, not MB
~500k devices Two instances behind a load balancer Smallest tier plus a read replica CDN absorbs it
Millions Autoscale on CPU Managed, connection-pooled CDN absorbs it

Modules are content-addressed and immutable, so CDN hit rates are very high — the origin only serves the first request per release per edge.

Modules are immutable. A release is content-addressed by its SHA-256. Never overwrite one in place — publish a new version and update the manifest. The SDK verifies the hash before activating, so a swapped or corrupted object fails closed rather than running.

Keep the fingerprint contract. The device reports the fingerprint of the build it’s running, and your service must only serve modules built against that same shell. Getting this wrong is the one way to ship a patch that genuinely breaks an app — see the compatibility fingerprint.

Serve the compressed artifact as-is. Modules ship brotli-compressed (.wasm.br). Don’t set Content-Encoding: br on the object — the SDK fetches the compressed bytes and decompresses them itself, so a transport-level re-encode breaks the hash check.

Retention. Device check-ins accumulate a row per device per app. Add a retention job; nothing prunes it for you.

  • No rollout dashboard, adoption graphs, or error-spike alerts
  • No team accounts, roles, or audit trail
  • No patchcli init browser onboarding — you hand-write .Patch.yml and issue your own app keys
  • No support for your deployment