# syntax=docker/dockerfile:1 # ============================================================================= # Multi-stage Dockerfile for pg_trickle E2E integration tests. # # Stage 1 (builder): Compiles the extension from source. # Stage 2 (runtime): Copies the compiled artifacts into a clean postgres:18.3. # # Usage: # # Fast build — uses pre-built builder image (Rust + cargo-pgrx pre-baked): # just build-builder-image # once, ~7 min; skip if already built # just build-e2e-image # ~2-3 min cold, ~30s warm # # # Self-contained build (no pre-built builder, downloads everything, ~10 min): # BUILDER_IMAGE=postgres:18.3 just build-e2e-image # # The build context must be the project root (parent of tests/). # # Speed notes: # When BUILDER_IMAGE=pg_trickle_builder:pg18 (default), Rust + cargo-pgrx # + pgrx init are already baked in. Stage 1 only runs cargo fetch and # cargo pgrx package (~2-3 min cold; ~30s warm with BuildKit cache mounts). # BuildKit --mount=type=cache persists the cargo registry and incremental # compile artefacts between builds on the same Docker host. # ============================================================================= # ── Stage 1: Build the extension ──────────────────────────────────────────── # Default to the pre-built builder image (see tests/Dockerfile.builder). # Override with BUILDER_IMAGE=postgres:18.3 for a self-contained build. ARG BUILDER_IMAGE=pg_trickle_builder:pg18 FROM ${BUILDER_IMAGE} AS builder # ── Dependency caching layer ──────────────────────────────────────────────── # Copy manifests + lockfile so dep downloads are cached separately from source. WORKDIR /build COPY Cargo.toml Cargo.lock ./ # Create minimal stubs so cargo can resolve the workspace without real source. RUN mkdir -p src/bin src/dvm/operators benches && \ echo '#![allow(warnings)] fn main() {}' > src/bin/pgrx_embed.rs && \ echo '#![allow(warnings)]' > src/lib.rs && \ echo '' > src/dvm/mod.rs && \ echo '' > src/dvm/operators/mod.rs && \ echo '' > src/ivm.rs && \ echo 'fn main() {}' > benches/refresh_bench.rs && \ echo 'fn main() {}' > benches/diff_operators.rs # Pre-fetch all crates. Cache mount keeps the registry between builds so # this becomes a no-op when Cargo.lock hasn't changed. RUN --mount=type=cache,target=/root/.cargo/registry \ --mount=type=cache,target=/root/.cargo/git \ cargo fetch # ── Build the real extension ──────────────────────────────────────────────── # Copy actual source — invalidates only the compile step below. COPY src/ src/ COPY pg_trickle.control ./ # Compile the extension. # The target/ cache mount keeps incremental artefacts between builds on the # same host, so only crates touched by the source change are recompiled. # Artefacts are copied to /artefacts/ so that Stage 2 can COPY them # (cache-mounted paths aren't directly visible to downstream COPY --from=). RUN --mount=type=cache,target=/root/.cargo/registry \ --mount=type=cache,target=/root/.cargo/git \ --mount=type=cache,target=/build/target \ cargo pgrx package --pg-config /usr/bin/pg_config && \ mkdir -p /artefacts && \ cp -r target/release/pg_trickle-pg18 /artefacts/ # Verify the artifacts were produced RUN echo "=== Package artifacts ===" && \ find /artefacts/pg_trickle-pg18 -type f && \ echo "=========================" # ── Stage 2: Runtime image with extension installed ───────────────────────── FROM postgres:18.3 LABEL org.opencontainers.image.title="pg_trickle E2E test image" LABEL org.opencontainers.image.description="PostgreSQL 18.3 with pg_trickle extension for integration testing" # Copy extension artifacts from the builder stage COPY --from=builder \ /artefacts/pg_trickle-pg18/usr/share/postgresql/18/extension/ \ /usr/share/postgresql/18/extension/ COPY --from=builder \ /artefacts/pg_trickle-pg18/usr/lib/postgresql/18/lib/ \ /usr/lib/postgresql/18/lib/ # Configure shared_preload_libraries so background worker + shared memory work. # Append to postgresql.conf.sample which is the template for new clusters. RUN echo "" >> /usr/share/postgresql/postgresql.conf.sample && \ echo "# pg_trickle E2E test configuration" >> /usr/share/postgresql/postgresql.conf.sample && \ echo "shared_preload_libraries = 'pg_trickle'" >> /usr/share/postgresql/postgresql.conf.sample && \ echo "wal_level = logical" >> /usr/share/postgresql/postgresql.conf.sample && \ echo "max_replication_slots = 10" >> /usr/share/postgresql/postgresql.conf.sample && \ echo "# Each test database spawns one pg_trickle scheduler BGW via the launcher." >> /usr/share/postgresql/postgresql.conf.sample && \ echo "# Default max_worker_processes=8 is too tight when many test databases run" >> /usr/share/postgresql/postgresql.conf.sample && \ echo "# concurrently. The full E2E suite runs ~55 test binaries in parallel, each" >> /usr/share/postgresql/postgresql.conf.sample && \ echo "# with its own scheduler BGW (e.g. 1 launcher + 55 scheduler workers + " >> /usr/share/postgresql/postgresql.conf.sample && \ echo "# autovacuum workers). 32 was too low, causing load_dynamic() failures for" >> /usr/share/postgresql/postgresql.conf.sample && \ echo "# the shared 'postgres' DB scheduler used by bgworker/cascade tests." >> /usr/share/postgresql/postgresql.conf.sample && \ echo "max_worker_processes = 128" >> /usr/share/postgresql/postgresql.conf.sample # Verify the extension files are in place RUN ls -la /usr/share/postgresql/18/extension/pg_trickle* && \ ls -la /usr/lib/postgresql/18/lib/pg_trickle* # Pre-create the extension on the `postgres` database during initdb. # The launcher background worker probes databases on startup — if the # extension isn't installed yet it waits 5 min (skip_ttl) before re-probing. # Creating the extension in initdb.d ensures the launcher's first probe # finds it and spawns a scheduler immediately. RUN echo "CREATE EXTENSION IF NOT EXISTS pg_trickle CASCADE;" \ > /docker-entrypoint-initdb.d/00-create-pg-trickle.sql EXPOSE 5432