diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..16b19b5 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,4 @@ +target +**/node_modules +.git +packer/staging diff --git a/.github/workflows/release-image.yml b/.github/workflows/release-image.yml new file mode 100644 index 0000000..5a1e60e --- /dev/null +++ b/.github/workflows/release-image.yml @@ -0,0 +1,76 @@ +name: Release image +on: + push: + tags: + - image/v* +permissions: + contents: read + packages: write +env: + IMAGE: ghcr.io/beyondoss/beyond-postgres +jobs: + build: + strategy: + fail-fast: false + matrix: + include: + - runs-on: ubuntu-latest + arch: amd64 + - runs-on: ubuntu-24.04-arm + arch: arm64 + runs-on: ${{ matrix.runs-on }} + steps: + - uses: actions/checkout@v7 + - uses: docker/setup-buildx-action@v4 + - uses: docker/login-action@v4 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + - id: build + uses: docker/build-push-action@v7 + with: + context: . + file: docker/Dockerfile + platforms: linux/${{ matrix.arch }} + outputs: type=image,name=${{ env.IMAGE }},push-by-digest=true,name-canonical=true,push=true + cache-from: type=gha,scope=image-${{ matrix.arch }} + cache-to: type=gha,mode=max,scope=image-${{ matrix.arch }} + - name: Export digest + run: | + mkdir -p /tmp/digests + digest="${{ steps.build.outputs.digest }}" + touch "/tmp/digests/${digest#sha256:}" + - uses: actions/upload-artifact@v7 + with: + name: digest-${{ matrix.arch }} + path: /tmp/digests/* + if-no-files-found: error + retention-days: 1 + merge: + runs-on: ubuntu-latest + needs: [build] + steps: + - uses: actions/download-artifact@v8 + with: + path: /tmp/digests + pattern: digest-* + merge-multiple: true + - uses: docker/setup-buildx-action@v4 + - uses: docker/login-action@v4 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + - name: Resolve version + id: meta + run: echo "version=${GITHUB_REF_NAME#image/v}" >> "$GITHUB_OUTPUT" + - name: Create manifest list + working-directory: /tmp/digests + run: | + docker buildx imagetools create \ + -t ${{ env.IMAGE }}:${{ steps.meta.outputs.version }} \ + -t ${{ env.IMAGE }}:latest \ + $(printf '${{ env.IMAGE }}@sha256:%s ' *) + - name: Inspect + run: docker buildx imagetools inspect ${{ env.IMAGE }}:${{ steps.meta.outputs.version }} diff --git a/docker/Dockerfile b/docker/Dockerfile new file mode 100644 index 0000000..656542c --- /dev/null +++ b/docker/Dockerfile @@ -0,0 +1,43 @@ +# syntax=docker/dockerfile:1 +# Postgres 18 bundled with the beyond_auth extension + the queue schema, for +# local-dev / docker-compose (ghcr.io/beyondoss/beyond-postgres). +# +# This is the Docker analogue of the production rootfs: prod builds the same +# beyond_auth/beyond_queue extensions (packer/scripts/build-beyond-extensions.sh, +# pinned in extensions.toml) into an ubuntu-noble Firecracker image. Here we layer +# them onto the official postgres:18 image so a generated app can `docker pull` a +# working database. Extension sources are cloned at AUTH_REF / QUEUE_REF. +ARG AUTH_REF=main +ARG QUEUE_REF=main + +# ---- build the beyond_auth pgrx extension against pg18 ----------------------- +FROM postgres:18-bookworm AS extbuilder +ARG AUTH_REF +RUN apt-get update && apt-get install -y --no-install-recommends \ + curl build-essential clang libclang-dev pkg-config libssl-dev \ + postgresql-server-dev-18 ca-certificates git \ + && rm -rf /var/lib/apt/lists/* +RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs \ + | sh -s -- -y --default-toolchain 1.92.0 --profile minimal +ENV PATH="/root/.cargo/bin:${PATH}" +# cargo-pgrx must match the extension's pgrx dependency (=0.18.0). +RUN cargo install cargo-pgrx --version 0.18.0 --locked +# Register pg18 (system install) so PGRX_HOME exists; no postgres is downloaded. +RUN cargo pgrx init --pg18 /usr/bin/pg_config +RUN git clone --depth 1 --branch "${AUTH_REF}" https://github.com/beyondoss/auth /src/auth +WORKDIR /src/auth/beyond-auth-extension +# Installs control + SQL + .so into this image's postgres dirs. +RUN cargo pgrx install --release --features pg18 --no-default-features --pg-config /usr/bin/pg_config + +# ---- fetch the queue schema (PL/pgSQL hot paths; no pgrx ext needed) --------- +FROM alpine/git AS queuesrc +ARG QUEUE_REF +RUN git clone --depth 1 --branch "${QUEUE_REF}" https://github.com/beyondoss/queue /src/queue + +# ---- final image ------------------------------------------------------------ +FROM postgres:18-bookworm +COPY --from=extbuilder /usr/share/postgresql/18/extension/beyond_auth* /usr/share/postgresql/18/extension/ +COPY --from=extbuilder /usr/lib/postgresql/18/lib/beyond_auth.so /usr/lib/postgresql/18/lib/ +COPY --from=queuesrc /src/queue/beyond-queue-extension/sql/schema.sql /opt/queue/schema.sql +COPY --from=queuesrc /src/queue/tests/fixtures/hot_paths.sql /opt/queue/hot_paths.sql +COPY docker/initdb.sh /docker-entrypoint-initdb.d/10-beyond-init.sh diff --git a/docker/initdb.sh b/docker/initdb.sh new file mode 100755 index 0000000..bce58b1 --- /dev/null +++ b/docker/initdb.sh @@ -0,0 +1,23 @@ +#!/usr/bin/env bash +# Runs once on first DB init (against POSTGRES_DB). Sets up the schemas the +# primitives expect in the shared app database: +# - auth : auth server auto-migrates + CREATE EXTENSION beyond_auth (installed in the image) +# - queue : schema + PL/pgSQL hot paths +# - public : the app's own migrations +set -euo pipefail + +# beyond_auth owns (creates) the `auth` schema, so let the extension make it +# rather than pre-creating it (pre-creating triggers "schema auth is not a member +# of extension"). The auth server's CREATE EXTENSION IF NOT EXISTS is then a no-op. +psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" <<'SQL' +CREATE EXTENSION IF NOT EXISTS beyond_auth; +CREATE SCHEMA IF NOT EXISTS queue; +SQL + +# Queue: base schema then the PL/pgSQL hot-path overrides (search_path=queue). +psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" \ + -c 'SET search_path = queue, public;' -f /opt/queue/schema.sql +psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" \ + -c 'SET search_path = queue, public;' -f /opt/queue/hot_paths.sql + +echo "[beyond-init] auth/queue schemas ready in ${POSTGRES_DB}"