Skip to content

Commit c330729

Browse files
fcanovaigbartolinimnenciaNiccoloFei
authoredJan 16, 2025··
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>
1 parent dff09fc commit c330729

File tree

5 files changed

+540
-19
lines changed

5 files changed

+540
-19
lines changed
 

‎.github/workflows/bake.yaml

+146
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
name: Bake images
2+
3+
on:
4+
schedule:
5+
- cron: 0 8 * * 1
6+
workflow_dispatch:
7+
inputs:
8+
environment:
9+
type: choice
10+
options:
11+
- testing
12+
- production
13+
default: testing
14+
description: "Choose the environment to bake the images for"
15+
16+
jobs:
17+
# Start by building images for testing. We want to run security checks before pushing those to production.
18+
testbuild:
19+
name: Build for testing
20+
runs-on: ubuntu-latest
21+
permissions:
22+
contents: read
23+
packages: write
24+
security-events: write
25+
outputs:
26+
metadata: ${{ steps.build.outputs.metadata }}
27+
images: ${{ steps.images.outputs.images }}
28+
steps:
29+
- name: Checkout Code
30+
uses: actions/checkout@v4
31+
32+
- name: Log in to the GitHub Container registry
33+
uses: docker/login-action@v3
34+
with:
35+
registry: ghcr.io
36+
username: ${{ github.actor }}
37+
password: ${{ secrets.GITHUB_TOKEN }}
38+
39+
# TODO: review this when GitHub has linux/arm64 runners available (Q1 2025?)
40+
# https://github.com/github/roadmap/issues/970
41+
- name: Set up QEMU
42+
uses: docker/setup-qemu-action@v3
43+
with:
44+
platforms: 'arm64'
45+
46+
- name: Set up Docker Buildx
47+
uses: docker/setup-buildx-action@v3
48+
49+
- name: Build and push
50+
uses: docker/bake-action@v6
51+
id: build
52+
env:
53+
environment: testing
54+
registry: ghcr.io/${{ github.repository_owner }}
55+
revision: ${{ github.sha }}
56+
with:
57+
push: true
58+
59+
# Get a list of the images that were built and pushed. We only care about a single tag for each image.
60+
- name: Generated images
61+
id: images
62+
run: |
63+
echo "images=$(echo '${{ steps.build.outputs.metadata }}' | jq -c '[ .[]."image.name" | sub(",.*";"") ]')" >> "$GITHUB_OUTPUT"
64+
65+
security:
66+
name: Security checks
67+
runs-on: ubuntu-latest
68+
needs:
69+
- testbuild
70+
strategy:
71+
matrix:
72+
image: ${{fromJson(needs.testbuild.outputs.images)}}
73+
steps:
74+
- name: Checkout Code
75+
uses: actions/checkout@v4
76+
77+
- name: Log in to the GitHub Container registry
78+
uses: docker/login-action@v3
79+
with:
80+
registry: ghcr.io
81+
username: ${{ github.actor }}
82+
password: ${{ secrets.GITHUB_TOKEN }}
83+
84+
- name: Dockle
85+
uses: erzz/dockle-action@v1
86+
with:
87+
image: ${{ matrix.image }}
88+
exit-code: '1'
89+
90+
- name: Snyk
91+
uses: snyk/actions/docker@master
92+
continue-on-error: true
93+
env:
94+
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
95+
with:
96+
image: "${{ matrix.image }}"
97+
args: --severity-threshold=high --file=Dockerfile
98+
99+
- name: Upload result to GitHub Code Scanning
100+
uses: github/codeql-action/upload-sarif@v3
101+
continue-on-error: true
102+
with:
103+
sarif_file: snyk.sarif
104+
105+
# Build the image for production.
106+
#
107+
# TODO: no need to rebuild everything, just copy the testing images we have generated to the production registry
108+
# if we get here and we are building for production.
109+
prodbuild:
110+
if: github.event.inputs.environment == 'production' || github.event_name == 'schedule'
111+
name: Build for production
112+
runs-on: ubuntu-latest
113+
needs:
114+
- security
115+
permissions:
116+
contents: read
117+
packages: write
118+
security-events: write
119+
steps:
120+
- name: Checkout Code
121+
uses: actions/checkout@v4
122+
123+
- name: Log in to the GitHub Container registry
124+
uses: docker/login-action@v3
125+
with:
126+
registry: ghcr.io
127+
username: ${{ github.actor }}
128+
password: ${{ secrets.GITHUB_TOKEN }}
129+
130+
- name: Set up QEMU
131+
uses: docker/setup-qemu-action@v3
132+
with:
133+
platforms: 'arm64'
134+
135+
- name: Set up Docker Buildx
136+
uses: docker/setup-buildx-action@v3
137+
138+
- name: Build and push
139+
uses: docker/bake-action@v6
140+
id: build
141+
env:
142+
environment: production
143+
registry: ghcr.io/${{ github.repository_owner }}
144+
revision: ${{ github.sha }}
145+
with:
146+
push: true

‎BUILD.md

+129
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
# Building PostgreSQL Container Images for CloudNativePG
2+
3+
This guide outlines the process for building PostgreSQL operand images for
4+
CloudNativePG using [Docker Bake](https://docs.docker.com/build/bake/) and a
5+
[GitHub workflow](.github/workflows/bake.yaml).
6+
7+
The central component of this framework is the
8+
[Bake file (`docker-bake.hcl`)](docker-bake.hcl).
9+
10+
## Prerequisites
11+
12+
Ensure the following tools and components are available before proceeding:
13+
14+
1. [Docker Buildx](https://github.com/docker/buildx): A CLI plugin for advanced
15+
image building.
16+
2. Build Driver for Multi-Architecture Images: For example, `docker-container`
17+
(see [Build Drivers](https://docs.docker.com/build/builders/drivers/) and
18+
["Install QEMU Manually"](https://docs.docker.com/build/building/multi-platform/#install-qemu-manually)).
19+
3. [Distribution Registry](https://distribution.github.io/distribution/):
20+
Formerly known as Docker Registry, to host and manage the built images.
21+
22+
### Verifying Requirements
23+
24+
To confirm your environment is properly set up, run:
25+
26+
```bash
27+
docker buildx bake --check
28+
```
29+
30+
If errors appear, you may need to switch to a different build driver. For
31+
example, use the following commands to configure a `docker-container` build
32+
driver:
33+
34+
```bash
35+
docker buildx create \
36+
--name docker-container \
37+
--driver docker-container \
38+
--use \
39+
--driver-opt network=host \
40+
--bootstrap
41+
```
42+
43+
> *Note:* The `--driver-opt network=host` setting is required only for testing
44+
> when you push to a distribution registry listening on `localhost`.
45+
46+
> *Note:* This page is not intended to serve as a comprehensive guide for
47+
> building multi-architecture images with Docker and Bake. If you encounter any
48+
> issues, please refer to the resources listed above for detailed instructions
49+
> and troubleshooting.
50+
51+
## Default Target
52+
53+
The `default` target in Bake represents a Cartesian product of the following
54+
dimensions:
55+
56+
- **Base Image**
57+
- **Type** (e.g. `minimal` or `standard`)
58+
- **Platforms**
59+
- **PostgreSQL Versions**
60+
61+
## Building Images
62+
63+
To build PostgreSQL images using the `default` target — that is, for all the
64+
combinations of base image, format, platforms, and PostgreSQL versions — run:
65+
66+
```bash
67+
docker buildx bake --push
68+
```
69+
70+
> *Note:* The `--push` flag is required to upload the images to the registry.
71+
> Without it, the images will remain cached within the builder container,
72+
> making testing impossible.
73+
74+
If you want to limit the build to a specific combination, you can specify the
75+
target in the `VERSION-TYPE-BASE` format. For example, to build an image for
76+
PostgreSQL 17 with the `minimal` format on the `bookworm` base image:
77+
78+
```bash
79+
docker buildx bake --push postgresql-17-minimal-bookworm
80+
```
81+
82+
You can also limit the build to a single platform, for example AMD64, with:
83+
84+
```bash
85+
docker buildx bake --push --set "*.platform=linux/amd64"
86+
```
87+
88+
The two can be mixed as well:
89+
90+
```bash
91+
docker buildx bake --push \
92+
--set "*.platform=linux/amd64" \
93+
postgresql-17-minimal-bookworm
94+
```
95+
96+
## The Distribution Registry
97+
98+
The images must be pushed to any registry server that complies with the **OCI
99+
Distribution Specification**.
100+
101+
By default, the build process assumes a registry server running locally at
102+
`localhost:5000`. To use a different registry, set the `registry` environment
103+
variable when executing the `docker` command, as shown:
104+
105+
```bash
106+
registry=<REGISTRY_URL> docker buildx ...
107+
```
108+
109+
## Local Testing
110+
111+
You can test the image-building process locally if you meet the necessary
112+
[prerequisites](prerequisites).
113+
114+
To do this, you'll need a local registry server. If you don't already have one,
115+
you can deploy a temporary, disposable [distribution registry](https://distribution.github.io/distribution/about/deploying/)
116+
with the following command:
117+
118+
```bash
119+
docker run -d --rm -p 5000:5000 --name registry registry:2
120+
```
121+
122+
This command runs a lightweight, temporary instance of the `registry:2`
123+
container on port `5000`.
124+
125+
## Trademarks
126+
127+
*[Postgres, PostgreSQL and the Slonik Logo](https://www.postgresql.org/about/policies/trademarks/)
128+
are trademarks or registered trademarks of the PostgreSQL Community Association
129+
of Canada, and used with their permission.*

‎Dockerfile

+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
ARG BASE=debian:bookworm-slim
2+
FROM $BASE AS minimal
3+
4+
ARG PG_VERSION
5+
ARG PG_MAJOR=${PG_VERSION%%.*}
6+
7+
ENV PATH=$PATH:/usr/lib/postgresql/$PG_MAJOR/bin
8+
9+
RUN apt-get update && \
10+
apt-get install -y --no-install-recommends postgresql-common ca-certificates gnupg && \
11+
/usr/share/postgresql-common/pgdg/apt.postgresql.org.sh -y && \
12+
apt-get install -y --no-install-recommends -o Dpkg::::="--force-confdef" -o Dpkg::::="--force-confold" postgresql-common && \
13+
sed -ri 's/#(create_main_cluster) .*$/\1 = false/' /etc/postgresql-common/createcluster.conf && \
14+
apt-get install -y --no-install-recommends \
15+
-o Dpkg::::="--force-confdef" -o Dpkg::::="--force-confold" "postgresql-${PG_MAJOR}=${PG_VERSION}*" && \
16+
apt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false && \
17+
rm -rf /var/lib/apt/lists/* /var/cache/* /var/log/*
18+
19+
RUN usermod -u 26 postgres
20+
USER 26
21+
22+
23+
FROM minimal AS standard
24+
25+
USER root
26+
RUN apt-get update && \
27+
apt-get install -y --no-install-recommends locales-all \
28+
"postgresql-${PG_MAJOR}-pgaudit" \
29+
"postgresql-${PG_MAJOR}-pgvector" \
30+
"postgresql-${PG_MAJOR}-pg-failover-slots" && \
31+
apt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false && \
32+
rm -rf /var/lib/apt/lists/* /var/cache/* /var/log/*
33+
34+
USER 26

‎README.md

+122-19
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,133 @@
1-
# PostgreSQL Container Images
1+
> **IMPORTANT:** As of January 2025, we have transitioned to a new image build
2+
> process (see issue [#132](https://github.com/cloudnative-pg/postgres-containers/issues/132)
3+
> for details). Previously, the images were based on the
4+
> [Official Postgres image](https://hub.docker.com/_/postgres), maintained by the
5+
> [PostgreSQL Docker Community](https://github.com/docker-library/postgres),
6+
> and included Barman Cloud built from source.
7+
> This legacy approach, referred to as `system` images, will remain available
8+
> for backward compatibility but is planned for a future deprecation.
29
3-
Maintenance scripts to generate Immutable Application Containers
4-
for all available PostgreSQL versions (13 to 17) to be used as
5-
operands with the [CloudNativePG operator](https://cloudnative-pg.io)
6-
for Kubernetes.
10+
---
711

8-
These images are built on top of the [Official Postgres image](https://hub.docker.com/_/postgres)
9-
maintained by the [PostgreSQL Docker Community](https://github.com/docker-library/postgres),
10-
by adding the following software:
12+
# CNPG PostgreSQL Container Images
13+
14+
This repository provides maintenance scripts for generating immutable
15+
application containers for all supported PostgreSQL versions (13 to 17).
16+
These containers are designed to serve as operands for the
17+
[CloudNativePG (CNPG) operator](https://cloudnative-pg.io) in Kubernetes
18+
environments.
19+
20+
## Key Features
21+
22+
The CNPG PostgreSQL Container Images:
23+
24+
- Are based on Debian Linux `stable` and `oldstable`
25+
- Support **multi-architecture builds**, including `linux/amd64` and
26+
`linux/arm64`.
27+
- Include **build attestations**, such as Software Bills of Materials (SBOMs)
28+
and provenance metadata.
29+
- Are published on the
30+
[CloudNativePG GitHub Container Registry](https://github.com/cloudnative-pg/postgres-containers/pkgs/container/postgresql).
31+
- Are **automatically rebuilt weekly** (every Monday) to ensure they remain
32+
up-to-date.
33+
34+
## Image Types
35+
36+
We currently build and support two primary types of PostgreSQL images:
37+
38+
- [`minimal`](#minimal-images)
39+
- [`standard`](#standard-images)
40+
41+
Both `minimal` and `standard` images are intended to be used with backup
42+
plugins, such as [Barman Cloud](https://github.com/cloudnative-pg/plugin-barman-cloud).
43+
44+
> **Note:** for backward compatibility, we also maintain the
45+
> [`system`](#system-images) image type. Switching from `system` images to
46+
> `minimal` or `standard` images on an existing cluster is not supported.
47+
48+
### Minimal Images
49+
50+
Minimal images are lightweight and built on top of the
51+
[official Debian images](https://hub.docker.com/_/debian).
52+
They use the [APT PostgreSQL packages](https://wiki.postgresql.org/wiki/Apt)
53+
maintained by the PostgreSQL Global Development Group (PGDG).
54+
55+
These images are identified by the inclusion of `minimal` in their tag names,
56+
for example: `17.2-minimal-bookworm`.
57+
58+
### Standard Images
59+
60+
Standard images are an extension of the `minimal` images, enhanced with the
61+
following additional features:
62+
63+
- PGAudit
64+
- Postgres Failover Slots
65+
- pgvector
66+
- All Locales
67+
68+
Standard images are identifiable by the `standard` tag in their names, such as:
69+
`17.2-standard-bookworm`.
70+
71+
> **Note:** Standard images are designed to offer functionality equivalent to
72+
> the legacy `system` images when used with CloudNativePG. To achieve parity,
73+
> you must use the [Barman Cloud Plugin](https://github.com/cloudnative-pg/plugin-barman-cloud)
74+
> as a replacement for the native Barman Cloud support in `system` images.
75+
76+
### System Images
77+
78+
System images are based on the [Official Postgres image](https://hub.docker.com/_/postgres),
79+
maintained by the
80+
[PostgreSQL Docker Community](https://github.com/docker-library/postgres).
81+
These images include additional software to extend PostgreSQL functionality:
1182

1283
- Barman Cloud
1384
- PGAudit
1485
- Postgres Failover Slots
1586
- pgvector
1687

17-
Currently, images are automatically rebuilt once a week (Monday).
88+
The [`Debian`](Debian) folder contains image catalogs, which can be used as:
89+
- [`ClusterImageCatalog`](https://cloudnative-pg.io/documentation/current/image_catalog/)
90+
- [`ImageCatalog`](https://cloudnative-pg.io/documentation/current/image_catalog/)
91+
92+
> **Deprecation Notice:** System images and the associated Debian-based image
93+
> catalogs will be deprecated in future releases of CloudNativePG and
94+
> eventually removed. Users are encouraged to migrate to `minimal` or
95+
> `standard` images for new clusters as soon as feasible.
96+
97+
## Build Attestations
98+
99+
CNPG PostgreSQL Container Images are built with the following attestations to
100+
ensure transparency and traceability:
101+
102+
- **[Software Bill of Materials
103+
(SBOM)](https://docs.docker.com/build/metadata/attestations/sbom/):** A
104+
comprehensive list of software artifacts included in the image or used during
105+
its build process, formatted using the [in-toto SPDX predicate standard](https://github.com/in-toto/attestation/blob/main/spec/predicates/spdx.md).
106+
107+
- **[Provenance](https://docs.docker.com/build/metadata/attestations/slsa-provenance/):**
108+
Metadata detailing how the image was built, following the [SLSA Provenance](https://slsa.dev)
109+
framework.
110+
111+
For example, you can retrieve the SBOM for a specific image using the following
112+
command:
113+
114+
```bash
115+
docker buildx imagetools inspect <IMAGE> --format "{{ json .SBOM.SPDX }}"
116+
```
117+
118+
This command outputs the SBOM in JSON format, providing a detailed view of the
119+
software components and build dependencies.
120+
121+
## Building Images
122+
123+
For detailed instructions on building PostgreSQL container images, refer to the
124+
[BUILD.md](BUILD.md) file.
125+
126+
## License and copyright
127+
128+
This software is available under [Apache License 2.0](LICENSE).
129+
130+
Copyright The CloudNativePG Contributors.
18131

19132
Barman Cloud is distributed by EnterpriseDB under the
20133
[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
28141
pgvector is distributed under the
29142
[PostgreSQL License](https://github.com/pgvector/pgvector/blob/master/LICENSE).
30143

31-
Images are available via
32-
[GitHub Container Registry](https://github.com/cloudnative-pg/postgres-containers/pkgs/container/postgresql).
33-
34-
## License and copyright
35-
36-
This software is available under [Apache License 2.0](LICENSE).
37-
38-
Copyright The CloudNativePG Contributors.
39-
40144
## Trademarks
41145

42146
*[Postgres, PostgreSQL and the Slonik Logo](https://www.postgresql.org/about/policies/trademarks/)
43147
are trademarks or registered trademarks of the PostgreSQL Community Association
44148
of Canada, and used with their permission.*
45-

‎docker-bake.hcl

+109
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
variable "environment" {
2+
default = "testing"
3+
validation {
4+
condition = contains(["testing", "production"], environment)
5+
error_message = "environment must be either testing or production"
6+
}
7+
}
8+
9+
variable "registry" {
10+
default = "localhost:5000"
11+
}
12+
13+
// Use the revision variable to identify the commit that generated the image
14+
variable "revision" {
15+
default = ""
16+
}
17+
18+
fullname = ( environment == "testing") ? "${registry}/postgresql-testing" : "{registry}/postgresql"
19+
now = timestamp()
20+
authors = "The CloudNativePG Contributors"
21+
url = "https://github.com/cloudnative-pg/postgres-containers"
22+
23+
target "default" {
24+
matrix = {
25+
tgt = [
26+
"minimal",
27+
"standard"
28+
]
29+
pgVersion = [
30+
"13.18",
31+
"14.15",
32+
"15.10",
33+
"16.6",
34+
"17.2"
35+
]
36+
base = [
37+
// renovate: datasource=docker versioning=loose
38+
"debian:bookworm-slim@sha256:d365f4920711a9074c4bcd178e8f457ee59250426441ab2a5f8106ed8fe948eb",
39+
// renovate: datasource=docker versioning=loose
40+
"debian:bullseye-slim@sha256:b0c91cc181796d34c53f7ea106fbcddaf87f3e601cc371af6a24a019a489c980"
41+
]
42+
}
43+
platforms = [
44+
"linux/amd64",
45+
"linux/arm64"
46+
]
47+
dockerfile = "Dockerfile"
48+
name = "postgresql-${index(split(".",pgVersion),0)}-${tgt}-${distroVersion(base)}"
49+
tags = [
50+
"${fullname}:${index(split(".",pgVersion),0)}-${tgt}-${distroVersion(base)}",
51+
"${fullname}:${pgVersion}-${tgt}-${distroVersion(base)}",
52+
"${fullname}:${pgVersion}-${formatdate("YYYYMMDDhhmm", now)}-${tgt}-${distroVersion(base)}"
53+
]
54+
context = "."
55+
target = "${tgt}"
56+
args = {
57+
PG_VERSION = "${pgVersion}"
58+
BASE = "${base}"
59+
}
60+
attest = [
61+
"type=provenance,mode=max",
62+
"type=sbom"
63+
]
64+
annotations = [
65+
"index,manifest:org.opencontainers.image.created=${now}",
66+
"index,manifest:org.opencontainers.image.url=${url}",
67+
"index,manifest:org.opencontainers.image.source=${url}",
68+
"index,manifest:org.opencontainers.image.version=${pgVersion}",
69+
"index,manifest:org.opencontainers.image.revision=${revision}",
70+
"index,manifest:org.opencontainers.image.vendor=${authors}",
71+
"index,manifest:org.opencontainers.image.title=CloudNativePG PostgreSQL ${pgVersion} ${tgt}",
72+
"index,manifest:org.opencontainers.image.description=A ${tgt} PostgreSQL ${pgVersion} container image",
73+
"index,manifest:org.opencontainers.image.documentation=https://github.com/cloudnative-pg/postgres-containers",
74+
"index,manifest:org.opencontainers.image.authors=${authors}",
75+
"index,manifest:org.opencontainers.image.licenses=Apache-2.0",
76+
"index,manifest:org.opencontainers.image.base.name=docker.io/library/${tag(base)}",
77+
"index,manifest:org.opencontainers.image.base.digest=${digest(base)}"
78+
]
79+
labels = {
80+
"org.opencontainers.image.created" = "${now}",
81+
"org.opencontainers.image.url" = "${url}",
82+
"org.opencontainers.image.source" = "${url}",
83+
"org.opencontainers.image.version" = "${pgVersion}",
84+
"org.opencontainers.image.revision" = "${revision}",
85+
"org.opencontainers.image.vendor" = "${authors}",
86+
"org.opencontainers.image.title" = "CloudNativePG PostgreSQL ${pgVersion} ${tgt}",
87+
"org.opencontainers.image.description" = "A ${tgt} PostgreSQL ${pgVersion} container image",
88+
"org.opencontainers.image.documentation" = "${url}",
89+
"org.opencontainers.image.authors" = "${authors}",
90+
"org.opencontainers.image.licenses" = "Apache-2.0"
91+
"org.opencontainers.image.base.name" = "docker.io/library/debian:${tag(base)}"
92+
"org.opencontainers.image.base.digest" = "${digest(base)}"
93+
}
94+
}
95+
96+
function tag {
97+
params = [ imageNameWithSha ]
98+
result = index(split("@", index(split(":", imageNameWithSha), 1)), 0)
99+
}
100+
101+
function distroVersion {
102+
params = [ imageNameWithSha ]
103+
result = index(split("-", tag(imageNameWithSha)), 0)
104+
}
105+
106+
function digest {
107+
params = [ imageNameWithSha ]
108+
result = index(split("@", imageNameWithSha), 1)
109+
}

0 commit comments

Comments
 (0)
Please sign in to comment.