# compose/ — local-dev runtime for pgRDF
Stock `postgres:17.4-bookworm` image, no image rebuild, no entrypoint
wrapper, no init script. The locally-built extension files are placed
at the canonical Postgres paths via **per-file bind mounts**:
./extensions/lib/pgrdf.so → /usr/lib/postgresql/17/lib/pgrdf.so
./extensions/share/extension/pgrdf.control → /usr/share/postgresql/17/extension/pgrdf.control
./extensions/share/extension/pgrdf--0.3.0.sql → /usr/share/postgresql/17/extension/pgrdf--0.3.0.sql
## Layout
compose/
├── compose.yml # services definition
├── builder.Containerfile # linux/glibc-bookworm builder
├── .env.example
├── extensions/ # built artifacts (gitignored, populated by `just build-ext`)
│ ├── lib/pgrdf.so
│ └── share/extension/{pgrdf.control, pgrdf--<ver>.sql}
└── pg-data/ # PGDATA bind mount (gitignored)
## One-time setup
cp compose/.env.example compose/.env
# edit .env if you want non-default creds
## Boot sequence
From the repo root:
just build-ext # builds the linux .so + .control + .sql into compose/extensions/
just compose-up # boots Postgres
just psql # connects as pgrdf/pgrdf to the pgrdf database
pgrdf=# CREATE EXTENSION pgrdf;
pgrdf=# SELECT pgrdf.version(); -- → "0.3.0"
## Why PG 17 (not 18)
The forward path in SPEC.pgRDF.INSTALL.v0.2 §7 is to use PG 18+'s
`extension_control_path` GUC, which lets us point Postgres at a
side directory without touching `$libdir`/`$sharedir/extension`. That
is the long-term shape this compose will adopt.
Today it pins to PG 17.4-bookworm because pgrx 0.17 and 0.18 (the
versions that add PG 18 support) fail to build on current Rust
toolchains (stable 1.95, nightly 1.97) — they reference unstable
APIs without enabling the corresponding feature flags. See
[`specs/ERRATA.v0.2.md`](../specs/ERRATA.v0.2.md) item E-006. Until
pgrx publishes a fixed 0.17.x or 0.18.x, we pin pgrx 0.16 and PG 17.
## Why per-file bind mounts (no init script, no entrypoint wrapper)
On PG 17 there's no `extension_control_path` GUC, so the files must
land at canonical Postgres paths. The three supported options per
INSTALL spec are:
1. Custom-built image with pgRDF baked in — rejected by §10.
2. Init container + entrypoint wrapper that copies files at boot —
§4.3, used in K8s manifests. Out of scope for this local compose
per project direction.
3. **Per-file bind mounts targeting `$libdir` and
`$sharedir/extension` directly.**
Option 3 is what this compose does. It has the same observable
end-state as option 2 (files at canonical paths, no image rebuild,
no source compile at runtime), with one fewer moving part. The
files are produced on the host by `just build-ext` (a Linux
builder container) before `compose up`.
## Why a Linux builder container (not native cargo)
We're cross-platform (macOS host, Linux Postgres container). Native
`cargo pgrx run` works on macOS for fast iteration but produces a
`.dylib`, which the Linux postgres container cannot load. The
builder container produces a glibc-bookworm `.so` matching the
target environment exactly.
## Resetting state
just compose-down
rm -rf compose/pg-data/* # discard PGDATA
rm -rf compose/extensions/lib compose/extensions/share # discard built artifacts