Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ci: build minimal and standard images #135

Merged
merged 13 commits into from
Jan 16, 2025
146 changes: 146 additions & 0 deletions .github/workflows/bake.yaml
Original file line number Diff line number Diff line change
@@ -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 changes: 129 additions & 0 deletions BUILD.md
Original file line number Diff line number Diff line change
@@ -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 changes: 34 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -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

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in the debian package postinst script, both the postgres user and the postgres group are created without hardcoding an ID.

https://salsa.debian.org/postgresql/postgresql-common/-/blob/22bf910531252c373a38175904a12f74ce820e86/debian/postgresql-common.postinst#L31-39

i just tried installing postgres-17 on an empty bookworm-slim container and I got:

root@34bd069f5bfe:/# id postgres
uid=100(postgres) gid=102(postgres) groups=102(postgres),101(ssl-cert)

i'm not entirely sure the reasoning for specifically choosing 26 instead of 100 for cnpg? it doesn't seem like a good coding practice at all to assume a user will have a specific UID

regardless, if we need to lock the UID in cnpg, should we also be ensuring that the GID is always 102 (or explicitly setting it to some other value)?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Historical reasons, 26 has been in use for a lot of things and it was the id long time ago, changing that now will affect a lot of people and is just a UID, in many system is even replaced.

The UID needs to be known for permissions and fs rules and security context, etc.

If we start changing to something else, then Debian may change the rule (as they already did) and we will have to change it again? in my opinion, changing will bring more problems than keep it them. If debian change the default we will have to change again and system already running with the permissions on the old UID may have issues when triggering an update of the image to the new one with a different UID

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

makes sense ~ thx for explaining

no historical GID right?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Historically, it has also been GID=26, but this doesn't matter during the image build. The operator works well even if the GID is not present in the image as long as the GID doesn't change.

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" && \

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

over in #132 (comment) I asked if it makes sense to move pg-failover-slots to the minimal image; I'm not sure where the other discussion will go but thought I'd flag it here just as FYI

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The minimal needs to be minimal in my opinion, just postgres without anything else

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with @sxd. The goal of minimal is to provide just PostgreSQL and allow us to start working on the extension container images project, which will take years to complete.

I would move the existing extensions to standard. After all, as you pointed out, producing images with the new process will be much easier. I expect many image customisations to happen in the future.

apt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false && \
rm -rf /var/lib/apt/lists/* /var/cache/* /var/log/*

USER 26
Loading