> ## Documentation Index
> Fetch the complete documentation index at: https://proxy-hopper.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Config Store

> Deploying and migrating the durable config store — SQLite or Postgres — that persists admin-API-created provider/pool/target config.

## Overview

See [Config Store](/concepts/config-store) for what this is and why it's separate from `backend`/`redisUrl`. This page covers deploying it: schema migrations, Docker/Compose, and the Helm chart's SQLite-vs-Postgres handling.

By default, no config store is configured — admin-API changes don't survive a restart, same as before this feature existed. Set `server.configStoreUrl` (or the chart's `configStore.dialect`) to change that.

## Schema migrations

The config store's schema is managed with Alembic, applied via a dedicated CLI command — not automatically at process startup, so an operator (or the Helm chart, see below) controls exactly when a migration runs relative to a rollout.

```bash theme={}
proxy-hopper migrate --database-url "sqlite+aiosqlite:///./data/config.db"
proxy-hopper migrate --database-url "postgresql+asyncpg://user:pass@host/db"
```

`PROXY_HOPPER_CONFIG_STORE_URL` works in place of `--database-url` too. `migrate` requires the `proxy-hopper-sql` package, already bundled into the standard Proxy Hopper Docker images — no separate image variant to pick.

<Note>
  Run `migrate` before starting Proxy Hopper against a config store URL for the first time. Both dialects are idempotent to re-run — a `migrate` call against an already-current schema is a no-op, not an error.
</Note>

## Docker / Docker Compose

```yaml theme={}
services:
  config-store-migrate:
    image: ghcr.io/cams-data/proxy-hopper:latest
    command: ["proxy-hopper", "migrate"]
    environment:
      PROXY_HOPPER_CONFIG_STORE_URL: postgresql+asyncpg://user:pass@postgres:5432/proxy_hopper
    depends_on:
      postgres:
        condition: service_healthy

  proxy-hopper:
    image: ghcr.io/cams-data/proxy-hopper:latest
    environment:
      PROXY_HOPPER_CONFIG_STORE_URL: postgresql+asyncpg://user:pass@postgres:5432/proxy_hopper
    depends_on:
      config-store-migrate:
        condition: service_completed_successfully
```

For SQLite, mount a volume for the database file and point both the one-shot `migrate` run and the main process at the same path:

```yaml theme={}
services:
  proxy-hopper:
    image: ghcr.io/cams-data/proxy-hopper:latest
    environment:
      PROXY_HOPPER_CONFIG_STORE_URL: sqlite+aiosqlite:////data/config.db
    volumes:
      - config-store-data:/data
    entrypoint: ["sh", "-c", "proxy-hopper migrate && proxy-hopper run --config /etc/proxy-hopper/config.yaml"]

volumes:
  config-store-data:
```

## Kubernetes / Helm

The chart selects the migration mechanism from `configStore.dialect`, not from sniffing the URL — Helm can't read the contents of `configStore.existingSecret` at template-render time (unlike Proxy Hopper itself, which always detects the dialect from the URL's SQLAlchemy scheme at runtime).

### Postgres — any replica count

```yaml theme={}
configStore:
  dialect: postgres
  url: "postgresql+asyncpg://user:pass@postgres:5432/proxy_hopper"
  # or, to keep credentials out of the pod spec:
  # existingSecret: my-postgres-secret
  # existingSecretKey: url   # default
```

The chart runs migrations via a `Job` annotated `helm.sh/hook: pre-install,pre-upgrade` — it completes before the proxy/admin Deployments roll out on every install and upgrade. Since Postgres is a real multi-writer server, this works with any `replicaCount`, `autoscaling.enabled`, and alongside a separately-enabled `admin.enabled` Deployment — all can point at the same database.

### SQLite — single pod only

```yaml theme={}
configStore:
  dialect: sqlite
  sqlite:
    persistence:
      size: 256Mi
```

The chart provisions a PVC and mounts it on the main deployment, with an initContainer that runs `proxy-hopper migrate` against that pod's own local file before the main container starts — the same structural pattern as the chart's existing `wait-for-redis` initContainer, but for schema migration instead of a readiness check.

<Warning>
  SQLite is single-writer. The chart enforces this at render time: `helm template`/`helm install` **fails outright** if `configStore.dialect: sqlite` is combined with `replicaCount` greater than 1 or `autoscaling.enabled: true`, rather than silently deploying pods that get stuck in `FailedAttachVolume` trying to mount the same `ReadWriteOnce` PVC. Use `configStore.dialect: postgres` if you need more than one replica.

  For the same reason, `configStore.dialect: sqlite` is **not** wired into a separately-enabled `admin.enabled` Deployment — it would be a second `Deployment` trying to reach the same single-writer file, which the chart doesn't attempt to make safe. An admin Deployment falls back to no config store (same as `configStore.dialect` unset) when the main deployment uses SQLite. Use Postgres if the admin server needs to see the same durable config as the proxy runners.
</Warning>

### Verifying

```bash theme={}
kubectl get job -l app.kubernetes.io/component=config-store-migrate
kubectl logs job/<release-name>-proxy-hopper-config-store-migrate   # Postgres

kubectl logs deploy/<release-name>-proxy-hopper -c config-store-migrate   # SQLite
```

## See also

<CardGroup cols={2}>
  <Card title="Config Store" icon="database" href="/concepts/config-store">
    The concept guide — what's durable vs. ephemeral, and why they're separate.
  </Card>

  <Card title="Config Reference" icon="list" href="/admin/configuration/reference#server">
    `configStoreUrl` / `PROXY_HOPPER_CONFIG_STORE_URL` alongside the rest of `server:`.
  </Card>

  <Card title="High Availability" icon="server" href="/admin/deployment/kubernetes/high-availability">
    Operational-state HA via the Redis backend — a separate, orthogonal choice from config durability.
  </Card>

  <Card title="ConfigStore internals" icon="code" href="/contributors/config-store">
    The `ConfigStore` interface and how to add a new dialect, for contributors.
  </Card>
</CardGroup>
