From d4de32d916623454dd38bdf1a76a776c8d07a90a Mon Sep 17 00:00:00 2001 From: Dan Lynch Date: Thu, 5 Mar 2026 03:01:23 +0000 Subject: [PATCH 1/4] feat: bake shared_preload_libraries, root role, and Docker Hub push - Add pg_textsearch and pgsodium to shared_preload_libraries in postgresql.conf.sample - Create root superuser role via initdb script (fixes CI health check failures) - Push to Docker Hub (constructiveio/postgres) in addition to GHCR - Requires DOCKERHUB_USERNAME and DOCKERHUB_TOKEN repo secrets --- .github/workflows/docker.yml | 156 ++++++++++++++++++++++++++++------- Dockerfile | 14 ++++ 2 files changed, 140 insertions(+), 30 deletions(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 208515f..f368017 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -8,8 +8,9 @@ on: branches: [main] env: - REGISTRY: ghcr.io - IMAGE_NAME: constructive-io/docker/postgres-plus + GHCR_REGISTRY: ghcr.io + GHCR_IMAGE: constructive-io/docker/postgres-plus + DOCKERHUB_IMAGE: constructiveio/postgres PG_VERSION: '17' concurrency: @@ -41,19 +42,28 @@ jobs: - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - - name: Log in to Container Registry + - name: Log in to GHCR if: github.event_name != 'pull_request' uses: docker/login-action@v3 with: - registry: ${{ env.REGISTRY }} + registry: ${{ env.GHCR_REGISTRY }} username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} + - name: Log in to Docker Hub + if: github.event_name != 'pull_request' + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + - name: Extract metadata id: meta uses: docker/metadata-action@v5 with: - images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + images: | + ${{ env.GHCR_REGISTRY }}/${{ env.GHCR_IMAGE }} + ${{ env.DOCKERHUB_IMAGE }} tags: | type=raw,value=latest,enable=${{ github.ref == 'refs/heads/main' }} type=raw,value=${{ env.PG_VERSION }},enable=${{ github.ref == 'refs/heads/main' }} @@ -76,36 +86,65 @@ jobs: cache-from: type=gha cache-to: type=gha,mode=max - - name: Build & push by digest + - name: Build & push by digest (GHCR) if: github.event_name != 'pull_request' - id: build + id: build-ghcr uses: docker/build-push-action@v6 with: context: . file: ./Dockerfile platforms: ${{ matrix.platform }} labels: ${{ steps.meta.outputs.labels }} - outputs: type=image,name=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }},push-by-digest=true,push=true + outputs: type=image,name=${{ env.GHCR_REGISTRY }}/${{ env.GHCR_IMAGE }},push-by-digest=true,push=true build-args: | PG_VERSION=${{ env.PG_VERSION }} cache-from: type=gha cache-to: type=gha,mode=max - - name: Export digest + - name: Build & push by digest (Docker Hub) + if: github.event_name != 'pull_request' + id: build-dockerhub + uses: docker/build-push-action@v6 + with: + context: . + file: ./Dockerfile + platforms: ${{ matrix.platform }} + labels: ${{ steps.meta.outputs.labels }} + outputs: type=image,name=${{ env.DOCKERHUB_IMAGE }},push-by-digest=true,push=true + build-args: | + PG_VERSION=${{ env.PG_VERSION }} + cache-from: type=gha + cache-to: type=gha,mode=max + + - name: Export GHCR digest + if: github.event_name != 'pull_request' + run: | + mkdir -p "${{ runner.temp }}/digests-ghcr" + digest="${{ steps.build-ghcr.outputs.digest }}" + touch "${{ runner.temp }}/digests-ghcr/${digest#sha256:}" + + - name: Export Docker Hub digest if: github.event_name != 'pull_request' run: | - mkdir -p "${{ runner.temp }}/digests" - digest="${{ steps.build.outputs.digest }}" - touch "${{ runner.temp }}/digests/${digest#sha256:}" + mkdir -p "${{ runner.temp }}/digests-dockerhub" + digest="${{ steps.build-dockerhub.outputs.digest }}" + touch "${{ runner.temp }}/digests-dockerhub/${digest#sha256:}" - - name: Upload digest + - name: Upload GHCR digest if: github.event_name != 'pull_request' uses: actions/upload-artifact@v4 with: - name: digests-${{ matrix.arch }} - path: ${{ runner.temp }}/digests/* + name: digests-ghcr-${{ matrix.arch }} + path: ${{ runner.temp }}/digests-ghcr/* - publish-postgres-plus-manifest: + - name: Upload Docker Hub digest + if: github.event_name != 'pull_request' + uses: actions/upload-artifact@v4 + with: + name: digests-dockerhub-${{ matrix.arch }} + path: ${{ runner.temp }}/digests-dockerhub/* + + publish-manifests: if: github.event_name != 'pull_request' runs-on: ubuntu-latest needs: build-postgres-plus @@ -118,25 +157,38 @@ jobs: - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - - name: Log in to Container Registry + - name: Log in to GHCR uses: docker/login-action@v3 with: - registry: ${{ env.REGISTRY }} + registry: ${{ env.GHCR_REGISTRY }} username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - - name: Download digests + - name: Log in to Docker Hub + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Download GHCR digests uses: actions/download-artifact@v4 with: - pattern: digests-* - path: ${{ runner.temp }}/digests + pattern: digests-ghcr-* + path: ${{ runner.temp }}/digests-ghcr merge-multiple: true - - name: Extract metadata - id: meta + - name: Download Docker Hub digests + uses: actions/download-artifact@v4 + with: + pattern: digests-dockerhub-* + path: ${{ runner.temp }}/digests-dockerhub + merge-multiple: true + + - name: Extract GHCR metadata + id: meta-ghcr uses: docker/metadata-action@v5 with: - images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + images: ${{ env.GHCR_REGISTRY }}/${{ env.GHCR_IMAGE }} tags: | type=raw,value=latest,enable=${{ github.ref == 'refs/heads/main' }} type=raw,value=${{ env.PG_VERSION }},enable=${{ github.ref == 'refs/heads/main' }} @@ -144,12 +196,56 @@ jobs: type=semver,pattern={{version}},enable=${{ startsWith(github.ref, 'refs/tags/v') }} type=semver,pattern={{major}}.{{minor}},enable=${{ startsWith(github.ref, 'refs/tags/v') }} - - name: Create and push multi-arch manifests + - name: Extract Docker Hub metadata + id: meta-dockerhub + uses: docker/metadata-action@v5 + with: + images: ${{ env.DOCKERHUB_IMAGE }} + tags: | + type=raw,value=latest,enable=${{ github.ref == 'refs/heads/main' }} + type=raw,value=${{ env.PG_VERSION }},enable=${{ github.ref == 'refs/heads/main' }} + type=sha,format=short,prefix= + type=semver,pattern={{version}},enable=${{ startsWith(github.ref, 'refs/tags/v') }} + type=semver,pattern={{major}}.{{minor}},enable=${{ startsWith(github.ref, 'refs/tags/v') }} + + - name: Create and push GHCR multi-arch manifests + run: | + set -euo pipefail + + image="${{ env.GHCR_REGISTRY }}/${{ env.GHCR_IMAGE }}" + digest_dir="${{ runner.temp }}/digests-ghcr" + + if [ ! -d "$digest_dir" ]; then + echo "No digests directory found at $digest_dir" + exit 1 + fi + + digests="" + for digest_file in "$digest_dir"/*; do + digest="$(basename "$digest_file")" + digests="$digests $image@sha256:$digest" + done + + if [ -z "$digests" ]; then + echo "No digests found to create manifest" + exit 1 + fi + + echo "Creating GHCR manifests for tags:" + echo "${{ steps.meta-ghcr.outputs.tags }}" + + echo "${{ steps.meta-ghcr.outputs.tags }}" | while read -r tag; do + [ -z "$tag" ] && continue + echo "Creating multi-arch manifest for $tag" + docker buildx imagetools create -t "$tag" $digests + done + + - name: Create and push Docker Hub multi-arch manifests run: | set -euo pipefail - image="${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}" - digest_dir="${{ runner.temp }}/digests" + image="${{ env.DOCKERHUB_IMAGE }}" + digest_dir="${{ runner.temp }}/digests-dockerhub" if [ ! -d "$digest_dir" ]; then echo "No digests directory found at $digest_dir" @@ -167,10 +263,10 @@ jobs: exit 1 fi - echo "Creating manifests for tags:" - echo "${{ steps.meta.outputs.tags }}" + echo "Creating Docker Hub manifests for tags:" + echo "${{ steps.meta-dockerhub.outputs.tags }}" - echo "${{ steps.meta.outputs.tags }}" | while read -r tag; do + echo "${{ steps.meta-dockerhub.outputs.tags }}" | while read -r tag; do [ -z "$tag" ] && continue echo "Creating multi-arch manifest for $tag" docker buildx imagetools create -t "$tag" $digests diff --git a/Dockerfile b/Dockerfile index bff14b8..51296d5 100644 --- a/Dockerfile +++ b/Dockerfile @@ -88,5 +88,19 @@ RUN apk add --no-cache \ COPY --from=builder /usr/local/lib/postgresql/ /usr/local/lib/postgresql/ COPY --from=builder /usr/local/share/postgresql/ /usr/local/share/postgresql/ +# Preload pg_textsearch and pgsodium so CREATE EXTENSION works without manual config +RUN echo "shared_preload_libraries = 'pg_textsearch, pgsodium'" >> /usr/local/share/postgresql/postgresql.conf.sample + +# Create a 'root' superuser so tools running as Linux root (e.g. CI health checks) connect without errors +COPY <<'EOF' /docker-entrypoint-initdb.d/00-create-root-role.sql +DO $$ +BEGIN + IF NOT EXISTS (SELECT FROM pg_catalog.pg_roles WHERE rolname = 'root') THEN + CREATE ROLE root WITH LOGIN SUPERUSER; + END IF; +END +$$; +EOF + LABEL org.opencontainers.image.source="https://github.com/constructive-io/docker" LABEL org.opencontainers.image.description="PostgreSQL 17 with pgvector, PostGIS, pg_textsearch, and pgsodium" From 51cad64fc741f32fb463500eac63f8b927e49198 Mon Sep 17 00:00:00 2001 From: Dan Lynch Date: Thu, 5 Mar 2026 03:10:08 +0000 Subject: [PATCH 2/4] fix: remove root role creation, keep only shared_preload_libraries --- Dockerfile | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/Dockerfile b/Dockerfile index 51296d5..8df8b22 100644 --- a/Dockerfile +++ b/Dockerfile @@ -91,16 +91,5 @@ COPY --from=builder /usr/local/share/postgresql/ /usr/local/share/postgresql/ # Preload pg_textsearch and pgsodium so CREATE EXTENSION works without manual config RUN echo "shared_preload_libraries = 'pg_textsearch, pgsodium'" >> /usr/local/share/postgresql/postgresql.conf.sample -# Create a 'root' superuser so tools running as Linux root (e.g. CI health checks) connect without errors -COPY <<'EOF' /docker-entrypoint-initdb.d/00-create-root-role.sql -DO $$ -BEGIN - IF NOT EXISTS (SELECT FROM pg_catalog.pg_roles WHERE rolname = 'root') THEN - CREATE ROLE root WITH LOGIN SUPERUSER; - END IF; -END -$$; -EOF - LABEL org.opencontainers.image.source="https://github.com/constructive-io/docker" LABEL org.opencontainers.image.description="PostgreSQL 17 with pgvector, PostGIS, pg_textsearch, and pgsodium" From ff0844a47e36d286bb9f7033830c4945b1052734 Mon Sep 17 00:00:00 2001 From: Dan Lynch Date: Thu, 5 Mar 2026 03:26:34 +0000 Subject: [PATCH 3/4] revert: undo all Docker Hub push and login changes to docker.yml --- .github/workflows/docker.yml | 156 +++++++---------------------------- 1 file changed, 30 insertions(+), 126 deletions(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index f368017..208515f 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -8,9 +8,8 @@ on: branches: [main] env: - GHCR_REGISTRY: ghcr.io - GHCR_IMAGE: constructive-io/docker/postgres-plus - DOCKERHUB_IMAGE: constructiveio/postgres + REGISTRY: ghcr.io + IMAGE_NAME: constructive-io/docker/postgres-plus PG_VERSION: '17' concurrency: @@ -42,28 +41,19 @@ jobs: - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - - name: Log in to GHCR + - name: Log in to Container Registry if: github.event_name != 'pull_request' uses: docker/login-action@v3 with: - registry: ${{ env.GHCR_REGISTRY }} + registry: ${{ env.REGISTRY }} username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - - name: Log in to Docker Hub - if: github.event_name != 'pull_request' - uses: docker/login-action@v3 - with: - username: ${{ secrets.DOCKERHUB_USERNAME }} - password: ${{ secrets.DOCKERHUB_TOKEN }} - - name: Extract metadata id: meta uses: docker/metadata-action@v5 with: - images: | - ${{ env.GHCR_REGISTRY }}/${{ env.GHCR_IMAGE }} - ${{ env.DOCKERHUB_IMAGE }} + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} tags: | type=raw,value=latest,enable=${{ github.ref == 'refs/heads/main' }} type=raw,value=${{ env.PG_VERSION }},enable=${{ github.ref == 'refs/heads/main' }} @@ -86,65 +76,36 @@ jobs: cache-from: type=gha cache-to: type=gha,mode=max - - name: Build & push by digest (GHCR) + - name: Build & push by digest if: github.event_name != 'pull_request' - id: build-ghcr + id: build uses: docker/build-push-action@v6 with: context: . file: ./Dockerfile platforms: ${{ matrix.platform }} labels: ${{ steps.meta.outputs.labels }} - outputs: type=image,name=${{ env.GHCR_REGISTRY }}/${{ env.GHCR_IMAGE }},push-by-digest=true,push=true + outputs: type=image,name=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }},push-by-digest=true,push=true build-args: | PG_VERSION=${{ env.PG_VERSION }} cache-from: type=gha cache-to: type=gha,mode=max - - name: Build & push by digest (Docker Hub) - if: github.event_name != 'pull_request' - id: build-dockerhub - uses: docker/build-push-action@v6 - with: - context: . - file: ./Dockerfile - platforms: ${{ matrix.platform }} - labels: ${{ steps.meta.outputs.labels }} - outputs: type=image,name=${{ env.DOCKERHUB_IMAGE }},push-by-digest=true,push=true - build-args: | - PG_VERSION=${{ env.PG_VERSION }} - cache-from: type=gha - cache-to: type=gha,mode=max - - - name: Export GHCR digest - if: github.event_name != 'pull_request' - run: | - mkdir -p "${{ runner.temp }}/digests-ghcr" - digest="${{ steps.build-ghcr.outputs.digest }}" - touch "${{ runner.temp }}/digests-ghcr/${digest#sha256:}" - - - name: Export Docker Hub digest + - name: Export digest if: github.event_name != 'pull_request' run: | - mkdir -p "${{ runner.temp }}/digests-dockerhub" - digest="${{ steps.build-dockerhub.outputs.digest }}" - touch "${{ runner.temp }}/digests-dockerhub/${digest#sha256:}" + mkdir -p "${{ runner.temp }}/digests" + digest="${{ steps.build.outputs.digest }}" + touch "${{ runner.temp }}/digests/${digest#sha256:}" - - name: Upload GHCR digest + - name: Upload digest if: github.event_name != 'pull_request' uses: actions/upload-artifact@v4 with: - name: digests-ghcr-${{ matrix.arch }} - path: ${{ runner.temp }}/digests-ghcr/* + name: digests-${{ matrix.arch }} + path: ${{ runner.temp }}/digests/* - - name: Upload Docker Hub digest - if: github.event_name != 'pull_request' - uses: actions/upload-artifact@v4 - with: - name: digests-dockerhub-${{ matrix.arch }} - path: ${{ runner.temp }}/digests-dockerhub/* - - publish-manifests: + publish-postgres-plus-manifest: if: github.event_name != 'pull_request' runs-on: ubuntu-latest needs: build-postgres-plus @@ -157,50 +118,25 @@ jobs: - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - - name: Log in to GHCR + - name: Log in to Container Registry uses: docker/login-action@v3 with: - registry: ${{ env.GHCR_REGISTRY }} + registry: ${{ env.REGISTRY }} username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - - name: Log in to Docker Hub - uses: docker/login-action@v3 - with: - username: ${{ secrets.DOCKERHUB_USERNAME }} - password: ${{ secrets.DOCKERHUB_TOKEN }} - - - name: Download GHCR digests + - name: Download digests uses: actions/download-artifact@v4 with: - pattern: digests-ghcr-* - path: ${{ runner.temp }}/digests-ghcr + pattern: digests-* + path: ${{ runner.temp }}/digests merge-multiple: true - - name: Download Docker Hub digests - uses: actions/download-artifact@v4 - with: - pattern: digests-dockerhub-* - path: ${{ runner.temp }}/digests-dockerhub - merge-multiple: true - - - name: Extract GHCR metadata - id: meta-ghcr - uses: docker/metadata-action@v5 - with: - images: ${{ env.GHCR_REGISTRY }}/${{ env.GHCR_IMAGE }} - tags: | - type=raw,value=latest,enable=${{ github.ref == 'refs/heads/main' }} - type=raw,value=${{ env.PG_VERSION }},enable=${{ github.ref == 'refs/heads/main' }} - type=sha,format=short,prefix= - type=semver,pattern={{version}},enable=${{ startsWith(github.ref, 'refs/tags/v') }} - type=semver,pattern={{major}}.{{minor}},enable=${{ startsWith(github.ref, 'refs/tags/v') }} - - - name: Extract Docker Hub metadata - id: meta-dockerhub + - name: Extract metadata + id: meta uses: docker/metadata-action@v5 with: - images: ${{ env.DOCKERHUB_IMAGE }} + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} tags: | type=raw,value=latest,enable=${{ github.ref == 'refs/heads/main' }} type=raw,value=${{ env.PG_VERSION }},enable=${{ github.ref == 'refs/heads/main' }} @@ -208,44 +144,12 @@ jobs: type=semver,pattern={{version}},enable=${{ startsWith(github.ref, 'refs/tags/v') }} type=semver,pattern={{major}}.{{minor}},enable=${{ startsWith(github.ref, 'refs/tags/v') }} - - name: Create and push GHCR multi-arch manifests - run: | - set -euo pipefail - - image="${{ env.GHCR_REGISTRY }}/${{ env.GHCR_IMAGE }}" - digest_dir="${{ runner.temp }}/digests-ghcr" - - if [ ! -d "$digest_dir" ]; then - echo "No digests directory found at $digest_dir" - exit 1 - fi - - digests="" - for digest_file in "$digest_dir"/*; do - digest="$(basename "$digest_file")" - digests="$digests $image@sha256:$digest" - done - - if [ -z "$digests" ]; then - echo "No digests found to create manifest" - exit 1 - fi - - echo "Creating GHCR manifests for tags:" - echo "${{ steps.meta-ghcr.outputs.tags }}" - - echo "${{ steps.meta-ghcr.outputs.tags }}" | while read -r tag; do - [ -z "$tag" ] && continue - echo "Creating multi-arch manifest for $tag" - docker buildx imagetools create -t "$tag" $digests - done - - - name: Create and push Docker Hub multi-arch manifests + - name: Create and push multi-arch manifests run: | set -euo pipefail - image="${{ env.DOCKERHUB_IMAGE }}" - digest_dir="${{ runner.temp }}/digests-dockerhub" + image="${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}" + digest_dir="${{ runner.temp }}/digests" if [ ! -d "$digest_dir" ]; then echo "No digests directory found at $digest_dir" @@ -263,10 +167,10 @@ jobs: exit 1 fi - echo "Creating Docker Hub manifests for tags:" - echo "${{ steps.meta-dockerhub.outputs.tags }}" + echo "Creating manifests for tags:" + echo "${{ steps.meta.outputs.tags }}" - echo "${{ steps.meta-dockerhub.outputs.tags }}" | while read -r tag; do + echo "${{ steps.meta.outputs.tags }}" | while read -r tag; do [ -z "$tag" ] && continue echo "Creating multi-arch manifest for $tag" docker buildx imagetools create -t "$tag" $digests From 61e79b9ed4cc5ba650d214918fbc799831eb138d Mon Sep 17 00:00:00 2001 From: Dan Lynch Date: Thu, 5 Mar 2026 03:47:46 +0000 Subject: [PATCH 4/4] fix: only preload pg_textsearch, pgsodium requires getkey script --- Dockerfile | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index 8df8b22..ce7733e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -88,8 +88,9 @@ RUN apk add --no-cache \ COPY --from=builder /usr/local/lib/postgresql/ /usr/local/lib/postgresql/ COPY --from=builder /usr/local/share/postgresql/ /usr/local/share/postgresql/ -# Preload pg_textsearch and pgsodium so CREATE EXTENSION works without manual config -RUN echo "shared_preload_libraries = 'pg_textsearch, pgsodium'" >> /usr/local/share/postgresql/postgresql.conf.sample +# Preload pg_textsearch so CREATE EXTENSION works without manual config +# NOTE: pgsodium is intentionally excluded — it requires a getkey script at boot +RUN echo "shared_preload_libraries = 'pg_textsearch'" >> /usr/local/share/postgresql/postgresql.conf.sample LABEL org.opencontainers.image.source="https://github.com/constructive-io/docker" LABEL org.opencontainers.image.description="PostgreSQL 17 with pgvector, PostGIS, pg_textsearch, and pgsodium"