ci: build minimal and standard images (#135)

Build images without barman-cloud, to be used with backup plugins.

Other changes:

- Implement timestamp-based versioning for images
- Simplify build workflows for enhanced local testing and contribution
- Adopt OCI annotations and generate SBOMs for improved transparency

Closes #132

Signed-off-by: Francesco Canovai <francesco.canovai@enterprisedb.com>
Signed-off-by: Gabriele Bartolini <gabriele.bartolini@enterprisedb.com>
Signed-off-by: Marco Nenciarini <marco.nenciarini@enterprisedb.com>
Signed-off-by: Niccolò Fei <niccolo.fei@enterprisedb.com>
Co-authored-by: Gabriele Bartolini <gabriele.bartolini@enterprisedb.com>
Co-authored-by: Marco Nenciarini <marco.nenciarini@enterprisedb.com>
Co-authored-by: Niccolò Fei <niccolo.fei@enterprisedb.com>
This commit is contained in:
Francesco Canovai
2025-01-16 14:03:20 +01:00
committed by GitHub
parent dff09fc22b
commit c330729d7f
5 changed files with 540 additions and 19 deletions

146
.github/workflows/bake.yaml vendored Normal file
View File

@@ -0,0 +1,146 @@
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"
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
- 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
- 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

129
BUILD.md Normal file
View File

@@ -0,0 +1,129 @@
# Building PostgreSQL Container Images for CloudNativePG
This guide outlines the process for building PostgreSQL operand images for
CloudNativePG using [Docker Bake](https://docs.docker.com/build/bake/) and a
[GitHub workflow](.github/workflows/bake.yaml).
The central component of this framework is the
[Bake file (`docker-bake.hcl`)](docker-bake.hcl).
## Prerequisites
Ensure the following tools and components are available before proceeding:
1. [Docker Buildx](https://github.com/docker/buildx): A CLI plugin for advanced
image building.
2. Build Driver for Multi-Architecture Images: For example, `docker-container`
(see [Build Drivers](https://docs.docker.com/build/builders/drivers/) and
["Install QEMU Manually"](https://docs.docker.com/build/building/multi-platform/#install-qemu-manually)).
3. [Distribution Registry](https://distribution.github.io/distribution/):
Formerly known as Docker Registry, to host and manage the built images.
### Verifying Requirements
To confirm your environment is properly set up, run:
```bash
docker buildx bake --check
```
If errors appear, you may need to switch to a different build driver. For
example, use the following commands to configure a `docker-container` build
driver:
```bash
docker buildx create \
--name docker-container \
--driver docker-container \
--use \
--driver-opt network=host \
--bootstrap
```
> *Note:* The `--driver-opt network=host` setting is required only for testing
> when you push to a distribution registry listening on `localhost`.
> *Note:* This page is not intended to serve as a comprehensive guide for
> building multi-architecture images with Docker and Bake. If you encounter any
> issues, please refer to the resources listed above for detailed instructions
> and troubleshooting.
## Default Target
The `default` target in Bake represents a Cartesian product of the following
dimensions:
- **Base Image**
- **Type** (e.g. `minimal` or `standard`)
- **Platforms**
- **PostgreSQL Versions**
## Building Images
To build PostgreSQL images using the `default` target — that is, for all the
combinations of base image, format, platforms, and PostgreSQL versions — run:
```bash
docker buildx bake --push
```
> *Note:* The `--push` flag is required to upload the images to the registry.
> Without it, the images will remain cached within the builder container,
> making testing impossible.
If you want to limit the build to a specific combination, you can specify the
target in the `VERSION-TYPE-BASE` format. For example, to build an image for
PostgreSQL 17 with the `minimal` format on the `bookworm` base image:
```bash
docker buildx bake --push postgresql-17-minimal-bookworm
```
You can also limit the build to a single platform, for example AMD64, with:
```bash
docker buildx bake --push --set "*.platform=linux/amd64"
```
The two can be mixed as well:
```bash
docker buildx bake --push \
--set "*.platform=linux/amd64" \
postgresql-17-minimal-bookworm
```
## The Distribution Registry
The images must be pushed to any registry server that complies with the **OCI
Distribution Specification**.
By default, the build process assumes a registry server running locally at
`localhost:5000`. To use a different registry, set the `registry` environment
variable when executing the `docker` command, as shown:
```bash
registry=<REGISTRY_URL> docker buildx ...
```
## Local Testing
You can test the image-building process locally if you meet the necessary
[prerequisites](prerequisites).
To do this, you'll need a local registry server. If you don't already have one,
you can deploy a temporary, disposable [distribution registry](https://distribution.github.io/distribution/about/deploying/)
with the following command:
```bash
docker run -d --rm -p 5000:5000 --name registry registry:2
```
This command runs a lightweight, temporary instance of the `registry:2`
container on port `5000`.
## 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.*

34
Dockerfile Normal file
View File

@@ -0,0 +1,34 @@
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 -y --no-install-recommends -o Dpkg::::="--force-confdef" -o Dpkg::::="--force-confold" postgresql-common && \
sed -ri 's/#(create_main_cluster) .*$/\1 = false/' /etc/postgresql-common/createcluster.conf && \
apt-get install -y --no-install-recommends \
-o Dpkg::::="--force-confdef" -o Dpkg::::="--force-confold" "postgresql-${PG_MAJOR}=${PG_VERSION}*" && \
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
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

141
README.md
View File

@@ -1,20 +1,133 @@
# PostgreSQL Container Images
> **IMPORTANT:** As of January 2025, we have transitioned to a new image build
> process (see issue [#132](https://github.com/cloudnative-pg/postgres-containers/issues/132)
> for details). Previously, the images were based on the
> [Official Postgres image](https://hub.docker.com/_/postgres), maintained by the
> [PostgreSQL Docker Community](https://github.com/docker-library/postgres),
> and included Barman Cloud built from source.
> This legacy approach, referred to as `system` images, will remain available
> for backward compatibility but is planned for a future deprecation.
Maintenance scripts to generate Immutable Application Containers
for all available PostgreSQL versions (13 to 17) to be used as
operands with the [CloudNativePG operator](https://cloudnative-pg.io)
for Kubernetes.
---
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:
# CNPG PostgreSQL Container Images
This repository provides maintenance scripts for generating immutable
application containers for all supported PostgreSQL versions (13 to 17).
These containers are designed to serve as operands for the
[CloudNativePG (CNPG) operator](https://cloudnative-pg.io) in Kubernetes
environments.
## Key Features
The CNPG PostgreSQL Container Images:
- Are based on Debian Linux `stable` and `oldstable`
- Support **multi-architecture builds**, including `linux/amd64` and
`linux/arm64`.
- Include **build attestations**, such as Software Bills of Materials (SBOMs)
and provenance metadata.
- Are published on the
[CloudNativePG GitHub Container Registry](https://github.com/cloudnative-pg/postgres-containers/pkgs/container/postgresql).
- Are **automatically rebuilt weekly** (every Monday) to ensure they remain
up-to-date.
## Image Types
We currently build and support two primary types of PostgreSQL images:
- [`minimal`](#minimal-images)
- [`standard`](#standard-images)
Both `minimal` and `standard` images are intended to be used with backup
plugins, such as [Barman Cloud](https://github.com/cloudnative-pg/plugin-barman-cloud).
> **Note:** for backward compatibility, we also maintain the
> [`system`](#system-images) image type. Switching from `system` images to
> `minimal` or `standard` images on an existing cluster is not supported.
### Minimal Images
Minimal images are lightweight and built on top of the
[official Debian images](https://hub.docker.com/_/debian).
They use the [APT PostgreSQL packages](https://wiki.postgresql.org/wiki/Apt)
maintained by the PostgreSQL Global Development Group (PGDG).
These images are identified by the inclusion of `minimal` in their tag names,
for example: `17.2-minimal-bookworm`.
### Standard Images
Standard images are an extension of the `minimal` images, enhanced with the
following additional features:
- PGAudit
- Postgres Failover Slots
- pgvector
- All Locales
Standard images are identifiable by the `standard` tag in their names, such as:
`17.2-standard-bookworm`.
> **Note:** Standard images are designed to offer functionality equivalent to
> the legacy `system` images when used with CloudNativePG. To achieve parity,
> you must use the [Barman Cloud Plugin](https://github.com/cloudnative-pg/plugin-barman-cloud)
> as a replacement for the native Barman Cloud support in `system` images.
### System Images
System images are based on the [Official Postgres image](https://hub.docker.com/_/postgres),
maintained by the
[PostgreSQL Docker Community](https://github.com/docker-library/postgres).
These images include additional software to extend PostgreSQL functionality:
- Barman Cloud
- PGAudit
- Postgres Failover Slots
- pgvector
Currently, images are automatically rebuilt once a week (Monday).
The [`Debian`](Debian) folder contains image catalogs, which can be used as:
- [`ClusterImageCatalog`](https://cloudnative-pg.io/documentation/current/image_catalog/)
- [`ImageCatalog`](https://cloudnative-pg.io/documentation/current/image_catalog/)
> **Deprecation Notice:** System images and the associated Debian-based image
> catalogs will be deprecated in future releases of CloudNativePG and
> eventually removed. Users are encouraged to migrate to `minimal` or
> `standard` images for new clusters as soon as feasible.
## Build Attestations
CNPG PostgreSQL Container Images are built with the following attestations to
ensure transparency and traceability:
- **[Software Bill of Materials
(SBOM)](https://docs.docker.com/build/metadata/attestations/sbom/):** A
comprehensive list of software artifacts included in the image or used during
its build process, formatted using the [in-toto SPDX predicate standard](https://github.com/in-toto/attestation/blob/main/spec/predicates/spdx.md).
- **[Provenance](https://docs.docker.com/build/metadata/attestations/slsa-provenance/):**
Metadata detailing how the image was built, following the [SLSA Provenance](https://slsa.dev)
framework.
For example, you can retrieve the SBOM for a specific image using the following
command:
```bash
docker buildx imagetools inspect <IMAGE> --format "{{ json .SBOM.SPDX }}"
```
This command outputs the SBOM in JSON format, providing a detailed view of the
software components and build dependencies.
## Building Images
For detailed instructions on building PostgreSQL container images, refer to the
[BUILD.md](BUILD.md) file.
## 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 +141,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.*

109
docker-bake.hcl Normal file
View File

@@ -0,0 +1,109 @@
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()
authors = "The CloudNativePG Contributors"
url = "https://github.com/cloudnative-pg/postgres-containers"
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"
]
}
platforms = [
"linux/amd64",
"linux/arm64"
]
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=${url}",
"index,manifest:org.opencontainers.image.source=${url}",
"index,manifest:org.opencontainers.image.version=${pgVersion}",
"index,manifest:org.opencontainers.image.revision=${revision}",
"index,manifest:org.opencontainers.image.vendor=${authors}",
"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=${authors}",
"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" = "${url}",
"org.opencontainers.image.source" = "${url}",
"org.opencontainers.image.version" = "${pgVersion}",
"org.opencontainers.image.revision" = "${revision}",
"org.opencontainers.image.vendor" = "${authors}",
"org.opencontainers.image.title" = "CloudNativePG PostgreSQL ${pgVersion} ${tgt}",
"org.opencontainers.image.description" = "A ${tgt} PostgreSQL ${pgVersion} container image",
"org.opencontainers.image.documentation" = "${url}",
"org.opencontainers.image.authors" = "${authors}",
"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)}"
}
}
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)
}