Skip to main content

What is the config store?

Proxy Hopper’s runtime state splits into two kinds, stored in two different places:
  • Operational state — IP queues, rate-limit counters, quarantine timers, identity/cookie jars. Ephemeral by design: losing it on restart just means every IP starts back at “not yet used” — a cold cache, not a data-loss event. This lives in the backend (memory or redis).
  • Durable config — providers, IP pools, and targets created or edited through the admin API. Losing this on restart is a real regression: an operator’s changes silently disappear. This lives in the config store (memory, sqlite, or postgres).
Config defined in your YAML file is never at risk either way — it’s reloaded from disk on every restart regardless of config store. The config store only matters for changes made after startup, through the admin API (GraphQL mutations, or the admin UI).

Why this is a separate abstraction from the backend

Earlier versions of Proxy Hopper stored provider/pool/target config as JSON blobs in the same backend used for operational state — meaning “durable config” only survived a restart if you’d also opted into backend: redis with persistence enabled, purely as a side effect of a decision made for an unrelated reason (multi-replica pool sharing). That coupling is gone. Config durability and operational-state sharing are now two independent choices: A single-replica deployment with backend: memory can now durably persist admin-API config via sqlite, with zero Redis anywhere in the deployment. A multi-replica deployment can share config across an admin process and proxy runners via postgres, independent of whether pool state is shared via redis or kept private per replica via memory.

Notify-then-reconcile

When a provider, pool, or target changes, Proxy Hopper publishes a change event over the backend’s pub/sub channel — but the event itself carries no data, just which entity changed ({entity, type, name}). Every recipient reacts by re-reading the current value from the config store, not by trusting a payload embedded in the message. This matters because pub/sub messages can be missed — a replica that’s briefly disconnected when a change fires never sees that specific message again. Under the old design (the full serialized entity embedded in the event), a missed message meant permanently stale state until the next unrelated change happened to touch the same entity. Under this design, a missed message just means the next signal — or the periodic reconcile most consumers already run — catches the recipient back up, because the config store, not the pub/sub message, is the single source of truth.

SQLite vs Postgres

A local file — no external service to run. Good fit for single-replica deployments (the same class of deployment backend: memory already targets): dev environments, small single-node deployments, anywhere you want durable config without operating a database server.Single-writer by nature: one process, one file. See Config Store (Admin) for the exact topology constraints this implies under Kubernetes.
Both dialects go through the same ConfigStore interface and the same Alembic-managed schema — switching between them is a connection-string change, not a data migration you write yourself.

Enabling it

Unset by default — admin-API config does not survive a restart until you opt in:
Schema migrations are applied separately, via proxy-hopper migrate — see Config Store (Admin) for deployment details (including the Helm chart’s automatic handling of this) and Config Reference for the full field list.
Existing Redis-persisted config from before this feature existed is not migrated automatically — new deployments (and upgrades that opt into a config store for the first time) seed from YAML the same way they always have.

Going fully database-free

The config store makes durability possible — it doesn’t make it required. If you don’t want any database in your deployment at all, pair the default (unset configStoreUrl) with server.adminReadOnly: true: the admin server stays deployable for monitoring (status, targets, pools, providers, metrics all still query fine), but every mutation is rejected, so there’s no ephemeral admin-API state to lose in the first place — all config comes from the YAML file, full stop. See Admin Server — Read-only mode.