Files
ServerKit/backend/templates/postgresql-pgvector.yaml
T
Juan DenisandClaude Opus 5 679082bf16 feat(databases): engine extensions, without the silent failure
The third deferred item. An extension installs INTO a running engine rather than
getting its own container, so it lives in its own namespace:
`backend/templates/extensions/<id>.yaml` with a top-level `extension:` block,
discovered by the same operator-wins two-directory scan the app templates use.
Dropping a YAML in makes it available with no code change, and a test pins that.

Not nested inside the engine template, because pgvector applies to any
PostgreSQL-speaking engine rather than to `postgresql.yaml` — nesting would force
duplication per host. Not a third `kind:` in the app catalog either: an
extension has no compose, port or volume and produces no Application, so every
consumer of `list_all_templates` would have had to learn to skip it.

The hard part is not the SQL. `CREATE EXTENSION vector` is valid and fails on
stock `postgres:16` with "could not open extension control file", because that
image does not ship the library. Two layers, and the statement is never sent
speculatively: an extension declares which images provide it, so a listing marks
an instance unsupported BEFORE anything is clicked; then at install
`pg_available_extensions` is queried first and a negative answer is a 409 with a
remedy, not an attempt. The live probe overrules the image name in both
directions — a private `registry.internal/ops/pg-with-vector` is allowed, and a
`pgvector/pgvector` tag that does not actually carry the library is still
refused.

ServerKit does not recreate a container behind the operator's back. The remedy
instead names an engine template that IS capable, found by matching the
extension's images against each engine template's compose — hence
`postgresql-pgvector.yaml`. TimescaleDB deliberately ships no bundled host and
says so rather than implying one exists.

Compatibility also needed correcting mid-build: matching on wire protocol alone
claimed CockroachDB could host pgvector. It answers on pgwire and cannot load a
single extension. Engines opt in with `engine.extensions: true`, which excludes
it without any Python naming it.

Two fixes fell out. `DatabaseService` had no psql twin of `docker_mysql_execute`
— `managed_db_user_service` carried a private copy with a comment saying as
much; there is now one implementation and it delegates. And that twin sets
`ON_ERROR_STOP=1`, without which psql reports SUCCESS for a statement the server
rejected.

57 tests, covering the zero-code-change property, compatibility, the image
problem in both probe directions, admin gating, and refusals that must not
execute anything.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-25 23:44:34 -04:00

89 lines
2.4 KiB
YAML

id: postgresql-pgvector
name: PostgreSQL + pgvector
version: "16"
description: PostgreSQL on an image that ships the pgvector shared library, so vector search can be enabled without rebuilding the container
icon: https://serverkit.ai/imgs/template-icons/postgresql.svg
categories:
- database
- sql
- ai
website: https://github.com/pgvector/pgvector
documentation: https://github.com/pgvector/pgvector#readme
# A normal engine template. The only thing that distinguishes it from
# `postgresql.yaml` is the image: `pgvector/pgvector` carries the extension's
# control file, so `CREATE EXTENSION vector` succeeds here and cannot on stock
# `postgres:16`. Nothing in Python knows that -- the extension declares which
# image repositories provide it, and this template is discovered by matching.
engine:
family: Relational
protocol: postgresql
default_port: 5434
admin_user: postgres
admin_password_var: DB_PASSWORD
database_var: DB_NAME
port_var: PORT
bind_var: BIND_ADDRESS
unit: tables
client: psql
data_path: /var/lib/postgresql/data
extensions: true
version_var: IMAGE_TAG
versions:
- "16"
- "15"
- "14"
variables:
- name: IMAGE_TAG
type: string
default: "16"
hidden: true
description: PostgreSQL major version; constrained to engine.versions by the installer
- name: PORT
type: port
# 5432 is usually the host's own PostgreSQL and 5433 the plain engine template.
default: "5434"
hidden: false
description: Host port to publish PostgreSQL on
- name: BIND_ADDRESS
type: string
default: 127.0.0.1
hidden: true
description: Address the port binds to. 127.0.0.1 keeps the engine private.
- name: DB_PASSWORD
type: password
length: 28
description: Password for the postgres superuser
- name: DB_NAME
type: string
default: postgres
description: Initial database to create
compose:
services:
app:
image: pgvector/pgvector:pg${IMAGE_TAG}
container_name: ${APP_NAME}
restart: unless-stopped
ports:
- "${BIND_ADDRESS}:${PORT}:5432"
environment:
POSTGRES_PASSWORD: "${DB_PASSWORD}"
POSTGRES_USER: postgres
POSTGRES_DB: "${DB_NAME}"
volumes:
- postgresql-pgvector-data:/var/lib/postgresql/data
volumes:
postgresql-pgvector-data:
ports:
- port: "${PORT}"
protocol: postgresql
description: PostgreSQL wire protocol
requirements:
memory: 512MB
storage: 5GB