diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..1c257fb --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,27 @@ +version: 2 + +updates: + # Dependabot's docker file fetcher matches any filename containing "dockerfile" or + # "containerfile", case-insensitively (DOCKER_REGEXP in dependabot-core), so Dockerfile-17 + # is picked up without renaming it. + # + # Each PR here is a PostgreSQL minor release. Read the Dockerfile header before merging: + # a bump below the minor version a live data directory runs is a downgrade PostgreSQL does + # not support, and merging publishes a new image rather than deploying it. + - package-ecosystem: docker + directory: / + schedule: + interval: weekly + commit-message: + prefix: docker + labels: + - dependencies + + - package-ecosystem: github-actions + directory: / + schedule: + interval: weekly + commit-message: + prefix: ci + labels: + - dependencies diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..721ae14 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,66 @@ +name: build + +# Publishes ghcr.io/fountain-bio/postgres so Railway can consume it as a SOURCE IMAGE. +# +# The source type matters. A Railway service that builds from this repo loses its database +# recognition: no Data tab, and `railway connect` answers "No supported database found in +# service". A service pointed at a pre-built image keeps it. So this workflow publishes the +# image and each Railway service stays image-sourced, with Source Image set to +# ghcr.io/fountain-bio/postgres:17. +# +# The package has to be pullable by Railway. Make it public under the org's package settings, +# or add registry credentials on each service. + +on: + push: + branches: [main] + pull_request: + workflow_dispatch: + +# A queued build for an older commit has nothing useful to publish once a newer one lands. +concurrency: + group: build-${{ github.ref }} + cancel-in-progress: true + +jobs: + postgres-17: + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + steps: + - uses: actions/checkout@v7.0.1 + + - uses: docker/setup-buildx-action@v4.2.0 + + # Pull requests build without publishing, so a broken Dockerfile fails review rather + # than overwriting the tag Railway pulls from. + - uses: docker/login-action@v4.6.0 + if: github.event_name != 'pull_request' + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - id: meta + uses: docker/metadata-action@v6.2.0 + with: + images: ghcr.io/fountain-bio/postgres + # `17` is the moving tag Railway points at. The sha tag gives every build a stable + # identity, so a bad rollout can be pinned back to a known-good digest. + tags: | + type=raw,value=17 + type=raw,value=17-${{ github.sha }} + + - uses: docker/build-push-action@v7.3.0 + with: + context: . + file: ./Dockerfile-17 + # Railway runs amd64. Building arm64 as well would double build time for an image + # nothing pulls on that architecture. + platforms: linux/amd64 + push: ${{ github.event_name != 'pull_request' }} + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + cache-from: type=gha + cache-to: type=gha,mode=max diff --git a/Dockerfile-17 b/Dockerfile-17 index 435ec89..5f50e60 100644 --- a/Dockerfile-17 +++ b/Dockerfile-17 @@ -1,2 +1,25 @@ -FROM ghcr.io/railwayapp-templates/postgres-ssl:17.6 -RUN apt-get update && apt-get install -y --no-install-recommends postgresql-17-cron && rm -rf /var/lib/apt/lists/* +# Railway's Postgres image with pg_cron added. +# +# Railway's stock Postgres ships no extensions, so pg_cron used to be apt-installed by hand +# inside the running container. That splits the install across two lifetimes: the +# `shared_preload_libraries = pg_stat_statements,pg_cron` setting lives in postgresql.conf +# inside PGDATA, which is on the volume, while pg_cron.so lives only in the container +# filesystem. Replace the container and Postgres refuses to start with +# `FATAL: could not access file "pg_cron"`. Baking the extension into the image is what +# makes a redeploy, an image change, or a platform restart survivable. +# +# pg_cron drives the Inngest run/trace retention job (`ops/inngest-prune.sql` in the +# fountain-bio monorepo), which is the only thing keeping those trace stores bounded. +# +# The base tag is PINNED rather than floating on `:17` so Dependabot raises a visible PR for +# each Postgres minor release. Two rules when that PR arrives: +# 1. Never merge a bump that moves BELOW the minor version a live data directory runs. +# PostgreSQL guarantees minor upgrades, not minor downgrades. +# 2. Merging does not deploy. Railway pulls the new image on the next redeploy of each +# service, so prod moves when you move it. +FROM ghcr.io/railwayapp-templates/postgres-ssl:17.10 + +# The package major version has to track the base image's major version. +RUN apt-get update \ + && apt-get install -y --no-install-recommends postgresql-17-cron \ + && rm -rf /var/lib/apt/lists/*