Docker
Managed Postgres services do not load custom C extensions, so for self-hosting the repository builds two images from one compile of the extension:
| Image | What it is | Use it for |
|---|---|---|
Full image, pg-automerge:<version> (and :dev) | the official postgres:18 image plus the extension: a complete server | docker run, compose, local development, CI services, small deployments on one host, any orchestrator that runs a plain container |
CloudNativePG extension image, pg-automerge-cnpg:<version>-18-trixie | only the extension's files, FROM scratch; it cannot be run | Kubernetes with CloudNativePG, which mounts it into its own PostgreSQL 18 image |
Both hold the same pg_automerge.so, control file and SQL scripts, so a
version behaves the same in either. This page is about the full image;
CloudNativePG covers the other.
The full image is the official postgres:18 image, untouched
(entrypoint, environment variables, volume, signals), plus
pg_automerge.so, its control file and SQL scripts, and one init script.
The extension is compiled in a builder stage from the same base image,
against that image's own PGDG Postgres 18 (release build, no assertions),
with Rust and cargo-pgrx as pinned in mise.toml.
Every release publishes both, for amd64 and arm64, built and tested by the release workflow:
docker pull ghcr.io/theknarf/pg-automerge:0.1.0 # or :latest
docker pull ghcr.io/theknarf/pg-automerge-cnpg:0.1.0-18-trixieThe published ghcr.io/theknarf/pg-automerge:<version> is the same
image as a local pg-automerge:<version>; use either name below. To
build it yourself (a local build is never pushed anywhere):
mise run docker-build # both: pg-automerge:0.1.0 + :dev, and pg-automerge-cnpg:0.1.0-18-trixie
mise run docker-test # builds, then tests both images and compose.yaml (about 6 minutes once built)bash scripts/docker-build.sh --full builds only the full image (--cnpg
only the extension image).
The image is 3.5 MB larger than postgres:18. A first build takes about
8 minutes (Rust, cargo-pgrx and a fat-LTO release build); BuildKit cache
mounts keep the toolchain and compiled dependencies, so a rebuild after a
source change takes about a minute (measurements).
Without mise (from the repository root, BuildKit required; the build
checks the version argument against Cargo.toml):
docker build -f docker/Dockerfile \
--build-arg PG_AUTOMERGE_VERSION="$(sh scripts/versions.sh | sed -n 's/^CRATE_VERSION=//p')" \
-t pg-automerge:dev .Run it like postgres:18. Mount the volume at /var/lib/postgresql (the
18 images keep the data in /var/lib/postgresql/18/docker below it):
docker run -d --name pg -p 127.0.0.1:5432:5432 \
-e POSTGRES_PASSWORD=secret -e POSTGRES_DB=app \
-v pgdata:/var/lib/postgresql \
pg-automerge:0.1.0 \
postgres -c default_toast_compression=lz4 -c pg_automerge.max_load_memory=1GBFor local development, compose.yaml does the same with a named volume,
a pg_isready healthcheck, the port on 127.0.0.1 only and the suggested
settings below:
mise run docker-up # build if needed, start, wait until healthy
psql postgres://postgres:postgres@localhost:5432/app
mise run docker-down # stop; `docker compose down -v` also deletes the dataPG_AUTOMERGE_PORT, POSTGRES_PASSWORD and POSTGRES_DB override its
defaults (5432, postgres, app).
Configuration#
| Variable | Default | Effect |
|---|---|---|
PG_AUTOMERGE_CREATE_EXTENSION | 1 | 1: CREATE EXTENSION IF NOT EXISTS pg_automerge in POSTGRES_DB when the data directory is first initialized. 0: skip it (install it from your migrations, e.g. CREATE EXTENSION pg_automerge SCHEMA automerge, as a superuser). Anything else fails initialization. |
The official image's variables (POSTGRES_PASSWORD, POSTGRES_USER,
POSTGRES_DB, POSTGRES_INITDB_ARGS, ...) work as documented for
postgres, and your own files in /docker-entrypoint-initdb.d run after
ours (10-pg-automerge.sh) when their names sort after it. Like all init
scripts, it runs only on an empty data directory, never on an existing
volume.
Add init files one by one, as single-file mounts or COPY in an image
of your own; do not mount a whole directory over
/docker-entrypoint-initdb.d:
docker run ... -v ./init/20-app.sql:/docker-entrypoint-initdb.d/20-app.sql:ro pg-automerge:0.1.0A directory mounted there replaces 10-pg-automerge.sh, and the database
then silently comes up without the extension. If you do mount a
directory, put an executable script in it that runs the image's copy of
ours, /usr/local/bin/pg-automerge-initdb (it reads
PG_AUTOMERGE_CREATE_EXTENSION too):
printf '#!/bin/sh\nexec pg-automerge-initdb\n' > init/10-pg-automerge.sh
chmod 755 init/10-pg-automerge.shThe extension goes into POSTGRES_DB only, not template1: it is not
trusted (see Install), and a copy in template1 would put it
into every database a CREATEDB role creates later. Other databases get
it with CREATE EXTENSION pg_automerge by a superuser. Your application
should connect as its own non-superuser role, not as POSTGRES_USER.
The image changes no Postgres setting. Suggested, as command-line options
(as above) or in a mounted postgresql.conf:
default_toast_compression=lz4: stored documents are uncompressed Automerge saves, which TOAST compresses; the image's Postgres is built with lz4 (pg_config --configureshows--with-lz4), which compresses and decompresses much faster than the defaultpglz. It applies to columns created afterwards;ALTER TABLE .. ALTER COLUMN doc SET COMPRESSION lz4sets it on an existing one (for new values).pg_automerge.max_load_memory: size it to the container. A backend whose Rust allocation fails aborts, and Postgres then restarts every session of the cluster; with a container memory limit (--memory), the kernel's OOM killer does the same. Each session can use up to the limit at once, likework_mem, so keep it well below the container's memory divided by the sessions you expect to load large documents at once (the default is 2GB; compose uses 1GB). See Configuration and Sizing memory.