Quick start remote
Two containers on one Docker network:
- PostgreSQL with the postvec extension in remote mode
- postvec-server for inference, with MiniLM already loaded
SQL is the same as quick start local. The server container generates a self-signed certificate at start; the extension accepts it. The hostname postvec-server is both the container name and a SAN on that certificate.
1. Start both containers
docker network create postvec
docker run -d --name postvec-server --network postvec \
-p 127.0.0.1:22222:22222 \
-p 127.0.0.1:33333:33333 \
ghcr.io/univec-ai/postvec-server:0.5.0-2
docker run -d --name postvec --network postvec \
-e POSTGRES_PASSWORD=demo \
-e POSTGRES_USER=app \
-e POSTGRES_DB=app \
-e POSTVEC_GRPC_ENDPOINTS=postvec-server:33333 \
-e POSTVEC_HTTP_ENDPOINTS=https://postvec-server:22222 \
-p 127.0.0.1:5433:5432 \
ghcr.io/univec-ai/postvec:0.5.0-2-pg18-remoteWait until both are healthy. Start the server first: PostgreSQL reads models at boot, and MiniLM still loading leaves postvec.models empty until the next refresh (postvec.model_refresh_interval_ms, 60 s).
docker inspect --format '{{.State.Health.Status}}' postvec-server
docker inspect --format '{{.State.Health.Status}}' postvecExpected
postvec-server is healthy once MiniLM can answer (GET /ready). postvec is healthy once PostgreSQL is up and the worker heartbeat advances. The remote image creates the extension in POSTGRES_DB on first initialization only.
Optional
docker exec postvec-server postvec-server status
docker exec postvec postvec-healthcheck
curl -sk https://127.0.0.1:22222/readystatus lists the loaded model. /ready is 200 once it can serve. The dashboard is https://127.0.0.1:22222 (self-signed).
2. Enable a column and insert
docker exec -i postvec psql -U app -d app <<'SQL'
CREATE TABLE docs (
id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
body text,
category text
);
SELECT postvec.enable(
'public.docs', 'body',
model => 'sentence-transformers-all-minilm-l6-v2',
create_fts_index => true
);
INSERT INTO docs (body, category) VALUES
('quarterly revenue guidance was raised after strong subscription growth',
'finance'),
('migrating embedding models normally requires re-embedding source text',
'engineering'),
('the office plants need watering twice a week',
'office');
SELECT relation, pending_jobs, dead_jobs
FROM postvec.status();
SQLVectors fill asynchronously on postvec-server. After INSERT commits, wait until pending_jobs = 0 and every body_semantic is non-NULL:
docker exec -it postvec psql -U app -d appthen (in psql):
SELECT count(*) FILTER (WHERE body_semantic IS NOT NULL) AS filled,
count(*) AS total
FROM docs;Expected
filled = total, pending_jobs = 0, dead_jobs = 0. MiniLM on the server is usually a second or two.
3. Index and search
Index (one-time):
SELECT postvec.create_vector_index('public.docs', 'body');Search:
SELECT d.id, d.body,
round(s.rrf_score::numeric, 5) AS score,
s.semantic_rank, s.fts_rank
FROM postvec.search(
'public.docs', 'body',
'switching AI models without redoing the work'
) AS s
JOIN public.docs AS d ON d.id = s.pk_value::bigint
ORDER BY s.rrf_score DESC;Expected
The engineering row ranks highly despite almost no keyword overlap. status().has_vector_index is true. postvec.models lists MiniLM from the server's /config.
4. Remove the containers
docker rm -f postvec postvec-server
docker network rm postvecA lasting install uses Docker or packages for postvec-server, then connect PostgreSQL. RDS, Aurora, Cloud SQL, Azure, Supabase and Neon use managed PostgreSQL.