From fa96023c0ea4c6e9514c4214890bab83b6c114a0 Mon Sep 17 00:00:00 2001 From: Francesco Canovai Date: Fri, 3 Jan 2025 18:28:27 +0100 Subject: [PATCH] ci: build minimal and standard images Build images without barman-cloud, to be used with backup plugins. Closes #132 Signed-off-by: Francesco Canovai --- .github/workflows/bake.yaml | 152 ++++++++++++++++++++++++++++++++++++ Dockerfile | 37 +++++++++ README.md | 67 +++++++++++++--- docker-bake.hcl | 107 +++++++++++++++++++++++++ 4 files changed, 352 insertions(+), 11 deletions(-) create mode 100644 .github/workflows/bake.yaml create mode 100644 Dockerfile create mode 100644 docker-bake.hcl diff --git a/.github/workflows/bake.yaml b/.github/workflows/bake.yaml new file mode 100644 index 00000000..aef88b45 --- /dev/null +++ b/.github/workflows/bake.yaml @@ -0,0 +1,152 @@ +name: Bake images + +on: + schedule: + - cron: 0 8 * * 1 + workflow_dispatch: + inputs: + environment: + type: choice + options: + - testing + - production + default: testing + description: "Choose the environment to bake the images for" + # TODO: remove this, it is used to test a workflow that is not on main + push: + +jobs: + # Start by building images for testing. We want to run security checks before pushing those to production. + testbuild: + name: Build for testing + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + security-events: write + outputs: + metadata: ${{ steps.build.outputs.metadata }} + images: ${{ steps.images.outputs.images }} + steps: + - name: Checkout Code + uses: actions/checkout@v4 + + - name: Log in to the GitHub Container registry + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + # TODO: review this when GitHub has linux/arm64 runners available (Q1 2025?) + # https://github.com/github/roadmap/issues/970 + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + with: + platforms: 'arm64' + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + with: + driver-opts: network=host + + - name: Build and push + uses: docker/bake-action@v6 + id: build + env: + environment: testing + registry: ghcr.io/${{ github.repository_owner }} + revision: ${{ github.sha }} + with: + push: true + + # Get a list of the images that were built and pushed. We only care about a single tag for each image. + - name: Generated images + id: images + run: | + echo "images=$(echo '${{steps.build.outputs.metadata}}' | jq -c '[ .[]."image.name" | sub(",.*";"" )]')" >> "$GITHUB_OUTPUT" + + security: + name: Security checks + runs-on: ubuntu-latest + needs: + - testbuild + strategy: + matrix: + image: ${{fromJson(needs.testbuild.outputs.images)}} + steps: + - name: Checkout Code + uses: actions/checkout@v4 + + - name: Log in to the GitHub Container registry + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Dockle + uses: erzz/dockle-action@v1 + with: + image: ${{ matrix.image }} + exit-code: '1' + + - name: Snyk + uses: snyk/actions/docker@master + continue-on-error: true + env: + SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }} + with: + image: "${{ matrix.image }}" + args: --severity-threshold=high --file=Dockerfile + + - name: Upload result to GitHub Code Scanning + uses: github/codeql-action/upload-sarif@v3 + continue-on-error: true + with: + sarif_file: snyk.sarif + + # Build the image for production. + # + # TODO: no need to rebuild everything, just copy the testing images we have generated to the production registry + # if we get here and we are building for production. + prodbuild: + if: github.event.inputs.environment == 'production' || github.event_name == 'schedule' + name: Build for production + runs-on: ubuntu-latest + needs: + - security + permissions: + contents: read + packages: write + security-events: write + steps: + - name: Checkout Code + uses: actions/checkout@v4 + + - name: Log in to the GitHub Container registry + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + with: + platforms: 'arm64' + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + with: + driver-opts: network=host + + - name: Build and push + uses: docker/bake-action@v6 + id: build + env: + environment: production + registry: ghcr.io/${{ github.repository_owner }} + revision: ${{ github.sha }} + with: + push: true diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..afd93934 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,37 @@ +ARG BASE=debian:bookworm-slim +FROM $BASE AS minimal + +ARG PG_VERSION +ARG PG_MAJOR=${PG_VERSION%%.*} + +ENV PATH=$PATH:/usr/lib/postgresql/$PG_MAJOR/bin + +RUN apt-get update && \ + apt-get install -y --no-install-recommends postgresql-common ca-certificates gnupg && \ + /usr/share/postgresql-common/pgdg/apt.postgresql.org.sh -y && \ + apt-get install --no-install-recommends -o Dpkg::::="--force-confdef" -o Dpkg::::="--force-confold" postgresql-common -y && \ + sed -ri 's/#(create_main_cluster) .*$/\1 = false/' /etc/postgresql-common/createcluster.conf && \ + apt-get install --no-install-recommends \ + -o Dpkg::::="--force-confdef" -o Dpkg::::="--force-confold" "postgresql-${PG_MAJOR}=${PG_VERSION}*" -y && \ + apt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false && \ + rm -rf /var/lib/apt/lists/* /var/cache/* /var/log/* + + +RUN usermod -u 26 postgres +USER 26 + +FROM minimal AS standard + +LABEL org.opencontainers.image.title="CloudNativePG PostgreSQL $PG_VERSION standard" +LABEL org.opencontainers.image.description="A standard PostgreSQL $PG_VERSION container image, with a minimal set of extensions and all the locales" + +USER root +RUN apt-get update && \ + apt-get install -y --no-install-recommends locales-all \ + "postgresql-${PG_MAJOR}-pgaudit" \ + "postgresql-${PG_MAJOR}-pgvector" \ + "postgresql-${PG_MAJOR}-pg-failover-slots" && \ + apt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false && \ + rm -rf /var/lib/apt/lists/* /var/cache/* /var/log/* + +USER 26 diff --git a/README.md b/README.md index f23835e4..ea876aac 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,26 @@ for all available PostgreSQL versions (13 to 17) to be used as operands with the [CloudNativePG operator](https://cloudnative-pg.io) for Kubernetes. +## Images + +We build three types of images: +* [system](#system) +* [minimal](#minimal) +* [standard](#standard) + +Switching from system images to minimal or standard images on an existing +cluster is not currently supported. + +Minimal and standard images are supposed to be used alongside a backup plugin +like [Barman Cloud](https://github.com/cloudnative-pg/plugin-barman-cloud). + +Images are available via +[GitHub Container Registry](https://github.com/cloudnative-pg/postgres-containers/pkgs/container/postgresql). + +Currently, images are automatically rebuilt once a week (Monday). + +### System + These images are built on top of the [Official Postgres image](https://hub.docker.com/_/postgres) maintained by the [PostgreSQL Docker Community](https://github.com/docker-library/postgres), by adding the following software: @@ -14,7 +34,42 @@ by adding the following software: - Postgres Failover Slots - pgvector -Currently, images are automatically rebuilt once a week (Monday). +### Minimal + +These images are build on top of [official Debian images](https://hub.docker.com/_/debian) +by installing PostgreSQL. + +Minimal images include `minimal` in the tag name, e.g. +`17.2-standard-bookworm`. + +### Standard + +These images are build on top of the minimal images by adding the following +software: + +- PGAudit +- Postgres Failover Slots +- pgvector + +and all the locales. + +Standard images include `standard` in the tag name, e.g. +`17.2-standard-bookworm`. + +## SBOMs + +Software Bills of Materials (SBOMs) are available for minimal and standard +images. The SBOM for an image can be retrieved with the following command: + +```shell +docker buildx imagetools inspect --format "{{ json .SBOM.SPDX}}" +``` + +## License and copyright + +This software is available under [Apache License 2.0](LICENSE). + +Copyright The CloudNativePG Contributors. Barman Cloud is distributed by EnterpriseDB under the [GNU GPL 3 License](https://github.com/EnterpriseDB/barman/blob/master/LICENSE). @@ -28,18 +83,8 @@ Postgres Failover Slots is distributed by EnterpriseDB under the pgvector is distributed under the [PostgreSQL License](https://github.com/pgvector/pgvector/blob/master/LICENSE). -Images are available via -[GitHub Container Registry](https://github.com/cloudnative-pg/postgres-containers/pkgs/container/postgresql). - -## License and copyright - -This software is available under [Apache License 2.0](LICENSE). - -Copyright The CloudNativePG Contributors. - ## Trademarks *[Postgres, PostgreSQL and the Slonik Logo](https://www.postgresql.org/about/policies/trademarks/) are trademarks or registered trademarks of the PostgreSQL Community Association of Canada, and used with their permission.* - diff --git a/docker-bake.hcl b/docker-bake.hcl new file mode 100644 index 00000000..f941fc56 --- /dev/null +++ b/docker-bake.hcl @@ -0,0 +1,107 @@ +variable "environment" { + default = "testing" + validation { + condition = contains(["testing", "production"], environment) + error_message = "environment must be either testing or production" + } +} + +variable "registry" { + default = "localhost:5000" +} + +// Use the revision variable to identify the commit that generated the image +variable "revision" { + default = "" +} + +fullname = ( environment == "testing") ? "${registry}/postgresql-testing" : "{registry}/postgresql" +now = timestamp() + +target "default" { + matrix = { + tgt = [ + "minimal", +// "standard" + ] + pgVersion = [ +// "13.18", +// "14.15", +// "15.10", + "16.6", + "17.2" + ] + base = [ + // renovate: datasource=docker versioning=loose +// "debian:bookworm-slim@sha256:d365f4920711a9074c4bcd178e8f457ee59250426441ab2a5f8106ed8fe948eb", + // renovate: datasource=docker versioning=loose + "debian:bullseye-slim@sha256:b0c91cc181796d34c53f7ea106fbcddaf87f3e601cc371af6a24a019a489c980" + ] + } + dockerfile = "Dockerfile" + name = "postgresql-${index(split(".",pgVersion),0)}-${tgt}-${distroVersion(base)}" + tags = [ + "${fullname}:${index(split(".",pgVersion),0)}-${tgt}-${distroVersion(base)}", + "${fullname}:${pgVersion}-${tgt}-${distroVersion(base)}", + "${fullname}:${pgVersion}-${formatdate("YYYYMMDDhhmm", now)}-${tgt}-${distroVersion(base)}" + ] + context = "." + target = "${tgt}" + args = { + PG_VERSION = "${pgVersion}" + BASE = "${base}" + } + attest = [ + "type=provenance,mode=max", + "type=sbom" + ] + annotations = [ + "index,manifest:org.opencontainers.image.created=${now}", + "index,manifest:org.opencontainers.image.url=https://github.com/cloudnative-pg/postgres-containers", + "index,manifest:org.opencontainers.image.source=https://github.com/cloudnative-pg/postgres-containers", + "index,manifest:org.opencontainers.image.version=${pgVersion}", + "index,manifest:org.opencontainers.image.revision=${revision}", + "index,manifest:org.opencontainers.image.vendor=The CloudNativePG Contributors", + "index,manifest:org.opencontainers.image.title=CloudNativePG PostgreSQL ${pgVersion} ${tgt}", + "index,manifest:org.opencontainers.image.description=A ${tgt} PostgreSQL ${pgVersion} container image", + "index,manifest:org.opencontainers.image.documentation=https://github.com/cloudnative-pg/postgres-containers", + "index,manifest:org.opencontainers.image.authors=The CloudNativePG Contributors", + "index,manifest:org.opencontainers.image.licenses=Apache-2.0", + "index,manifest:org.opencontainers.image.base.name=docker.io/library/${tag(base)}", + "index,manifest:org.opencontainers.image.base.digest=${digest(base)}" + ] + labels = { + "org.opencontainers.image.created" = "${now}", + "org.opencontainers.image.url" = "https://github.com/cloudnative-pg/postgres-containers", + "org.opencontainers.image.source" = "https://github.com/cloudnative-pg/postgres-containers", + "org.opencontainers.image.version" = "${pgVersion}", + "org.opencontainers.image.revision" = "${revision}", + "org.opencontainers.image.vendor" = "The CloudNativePG Contributors", + "org.opencontainers.image.title" = "CloudNativePG PostgreSQL ${pgVersion} ${tgt}", + "org.opencontainers.image.description" = "A ${tgt} PostgreSQL ${pgVersion} container image", + "org.opencontainers.image.documentation" = "https://github.com/cloudnative-pg/postgres-containers", + "org.opencontainers.image.authors" = "The CloudNativePG Contributors", + "org.opencontainers.image.licenses" = "Apache-2.0" + "org.opencontainers.image.base.name" = "docker.io/library/debian:${tag(base)}" + "org.opencontainers.image.base.digest" = "${digest(base)}" + } +// platforms = [ +// "linux/amd64", +// "linux/arm64" +// ] +} + +function tag { + params = [ imageNameWithSha ] + result = index(split("@", index(split(":", imageNameWithSha), 1)), 0) +} + +function distroVersion { + params = [ imageNameWithSha ] + result = index(split("-", tag(imageNameWithSha)), 0) +} + +function digest { + params = [ imageNameWithSha ] + result = index(split("@", imageNameWithSha), 1) +}