Skip to main content

PostgreSQL deployment modes

SQLite on local disk is the default. Use PostgreSQL when your storage topology needs it or you already operate an instance. The UI still stays at one replica. For everyday backups, start with Database and backups.

A separate PostgreSQL service​

Download docker-compose.postgres.yml from the same release as the base file. It adds a postgres:16 service pinned by digest, its persistent volume and the app database URL. The app waits for PostgreSQL's health check before starting. For a new installation, set in .env:

POSTGRES_PASSWORD=a-long-random-password
COMPOSE_FILE=docker-compose.yml:docker-compose.postgres.yml

Then run docker compose up -d. The password is used in the database URL: use a URL-safe random value, or percent-encode reserved characters when configuring your own URL. Keep both files in COMPOSE_FILE for later updates. For an existing SQLite installation, follow the copy-first procedure before enabling the overlay's app URL. On Kubernetes, deploy/kubernetes/overlays/postgres does the equivalent: it is not referenced by base/kustomization.yaml, so applying base alone keeps SQLite, and applying the overlay points the Deployment at a database-secret.yaml you fill in yourself; it does not run PostgreSQL for you. Terraform: set database_url (empty by default, which keeps SQLite) and, only if you share the instance, database_schema (default immich_memories).

IMMICH_MEMORIES_DATABASE_URL=postgresql+psycopg://immich_memories:change-me@postgres:5432/immich_memories

Plain PostgreSQL 14+. No extensions, no vchord, no pgvector: every vector comparison in this app is exact numpy over a small candidate set (one film, one story, a 14-day window), so there is no extension to install and no extension to keep in step with an upstream image.

A separate database on your existing PostgreSQL instance​

The same URL, pointed at a database on an instance you already run for something else (including Immich's own PostgreSQL container, if you want to reuse it without touching Immich's schema):

IMMICH_MEMORIES_DATABASE_URL=postgresql+psycopg://immich_memories:change-me@database:5432/immich_memories

database is the service name in Immich's own compose file, so the host resolves when this app runs in that file (next to your Immich stack). From anywhere else, use the address that reaches your PostgreSQL.

Create the database and a role scoped to it first. Execute each SQL statement separately; CREATE DATABASE cannot run inside a transaction:

CREATE ROLE immich_memories WITH LOGIN PASSWORD 'change-me';
CREATE DATABASE immich_memories OWNER immich_memories;

Nothing here reads or writes Immich's own database. This mode exists for hosts that already pay for one PostgreSQL instance and want a second logical database on it rather than a second container.

A dedicated schema in Immich's own database​

Point the URL at Immich's database, and set the schema so the store's tables land somewhere that is not public:

IMMICH_MEMORIES_DATABASE_URL=postgresql+psycopg://immich_memories:change-me@database:5432/immich
IMMICH_MEMORIES_DATABASE_SCHEMA=immich_memories

IMMICH_MEMORIES_DATABASE_SCHEMA is not in the shipped compose file: add it to the service's environment: block next to the URL.

Create the role and schema while connected to Immich's database (immich), with no grants on anything Immich owns:

CREATE ROLE immich_memories WITH LOGIN PASSWORD 'change-me';
CREATE SCHEMA immich_memories AUTHORIZATION immich_memories;
GRANT CREATE ON DATABASE immich TO immich_memories;

Execute those statements separately. The database CREATE privilege lets restore recreate the store schema after dropping it; it also permits creating other schemas in this shared database. Restore checks this privilege before changing the existing store and refuses if it is missing. Use mode 3 if that grant is too broad for your deployment. There is no GRANT on public, no grant on any Immich table, no cross-schema foreign key, no search-path change on Immich's own role. Alembic's version table (alembic_version) lives inside immich_memories, and autogenerate is scoped to this app's own MetaData, so a migration here never touches Immich's tables and an Immich migration never touches this schema. No extensions are needed for either side, so there is nothing to reconcile between the two.

The one thing this mode does not protect against: a whole-database restore of Immich (pg_dump -d immich or a snapshot restore) restores every schema in that database, this one included. If you restore Immich from a backup taken before a memory run, that run's decisions and banked answers go back with it. immich-memories store backup dumps only this schema, independent of Immich's own backup schedule, if you want the two to have separate retention.