This guide explains how to build Postgres extensions container images for CloudNativePG locally, using Docker Bake.
Important
If you are looking to contribute a new PostgreSQL extension to this
repository, please refer to the CONTRIBUTING_NEW_EXTENSION.md file.
This guide covers the entire lifecycle, from proposing the extension and
scaffolding the project to local validation and submitting a Pull Request.
Before you begin, ensure that you have met the following prerequisites, which primarily include:
- Task: required to run tasks defined in the
Taskfile. - Dagger: Must be installed and configured.
- Docker: Must be installed and running.
- Docker Command Line: The
dockercommand must be executable. - Docker Buildx: The
docker buildxplugin must be available. - Docker Context: A valid Docker context must be configured.
To verify that all prerequisites are correctly installed and configured:
task prereqsTo create a new extension project structure, use the create-extension task:
task create-extension NAME=<extension-name>This command generates a new directory named after your extension with the following scaffolded files:
Dockerfile: The base file to build the extension container image.metadata.hcl: Provides specific metadata information used to build and test the extension.README.md: A template to help you document the extension's usage.
Note
These files are generated from generic templates and should be customized to
meet your extension's specific requirements.
For a complete walkthrough of the requirements and package discovery phase,
see CONTRIBUTING_NEW_EXTENSION.md.
For more complex setups, you can use the dagger command directly to customize
distributions or package names:
dagger call -sm ./dagger/maintenance/ create --name="<name>" [ARGUMENTS]Common Arguments:
| Argument | Description | Default |
|---|---|---|
--distros |
Debian distributions the extension supports. | [trixie, bookworm] |
--package-name |
The Debian package name (uses %version% placeholder). |
postgresql-%version%-<name> |
--versions |
Supported Postgres major versions. | [18] |
--templates-dir |
Source directory containing custom template files. | Internal template dir |
To verify the configuration (running docker buildx bake --check) for all
projects without building or pulling layers:
task checks:allTo build all discovered projects:
task
# or
task bake:allTo build a single project (e.g., the directory named pgvector):
task bake TARGET=pgvectorTo build all images and immediately push them to the configured registry:
task bake:all PUSH=trueTo push images for a single project (e.g., the directory named pgvector):
task bake TARGET=pgvector PUSH=trueTo see the commands that would be executed without running the actual
docker buildx bake command, set the DRY_RUN flag:
task DRY_RUN=true
# or
task bake TARGET=pgvector DRY_RUN=trueTesting your extensions locally ensures high-quality PRs and faster iteration cycles. This environment uses a local Docker container registry and a Kind cluster with CloudNativePG pre-installed.
Important
Pre-submission requirement: You must successfully run local tests before submitting a Pull Request for any extension.
End-to-end (E2E) tests are powered by Chainsaw.
To simplify the workflow, use the e2e:test:full task.
This single command automates environment setup, image building, and test
execution:
# Replace <extension> with the name of the extension (e.g., pgvector)
task e2e:test:full TARGET="<extension>"Example for testing the pgvector extension:
task e2e:test:full TARGET="pgvector"When to use the step-by-step guide: If the automated test fails during environment setup, image building, or test execution, the step-by-step guide below provides granular control for debugging each phase independently.
The e2e:setup-env utility creates a Kind cluster and attaches a local Docker
registry (available at localhost:5000).
task e2e:setup-envNote
Use the REGISTRY_HOST_PORT variable to customize the local registry port.
If changed, you must pass this variable to all subsequent tasks that interact
with the registry to ensure connectivity.
If you need to pull images from a private registry during testing, you can configure authentication credentials when setting up the environment:
REGISTRY_PASSWORD="your-password" task e2e:setup-env \
REGISTRY_HOST="registry.example.com" \
REGISTRY_USERNAME="your-username"These credentials are configured at the kubelet level, allowing pods to pull images from the private registry without requiring ImagePullSecrets.
To interact with the cluster via kubectl from your local terminal:
task e2e:export-kubeconfig KUBECONFIG_PATH=./kubeconfig
export KUBECONFIG=$PWD/kubeconfigImportant
The local registry running alongside the Kind cluster is
reachable within Kubernetes at registry.pg-extensions:5000. When testing your
local builds, you must point the extension's reference to this internal
address.
For example, if you are testing a locally built pgvector image, use:
reference: registry.pg-extensions:5000/pgvector-testing:<tag>
To allow the test suite (which runs within the Docker network) to reach the Kubernetes API server, export the internal Kubeconfig:
task e2e:export-kubeconfig KUBECONFIG_PATH=./kubeconfig INTERNAL=trueBuild the image and push it to the local registry. This command tags the image
for localhost:5000 automatically.
task bake TARGET="<extension>" PUSH=trueNote
The destination registry is controlled by the REGISTRY_HOST_NAME and
REGISTRY_HOST_PORT variables defined in the Taskfile.yml.
Generate configuration values so Chainsaw knows which local image to target for the E2E tests:
task e2e:generate-values TARGET="<extension>" EXTENSION_IMAGE="<my-local-image>"If your extension image is hosted in a private registry, you can provide authentication credentials when generating test values:
REGISTRY_PASSWORD="your-password" task generate-values \
TARGET="<extension>" \
EXTENSION_IMAGE="<my-private-registry>/image:tag" \
REGISTRY_USERNAME="your-username"Run the test suite using the internal Kubeconfig. This executes both the
generic tests (global /test folder) and extension-specific tests (target
/test folder).
task e2e:test TARGET="<extension>" KUBECONFIG_PATH="./kubeconfig"It is possible to pass arguments to the Chainsaw test command by using the EXTRA_ARGS
argument, like:
task e2e:test TARGET="pgvector" KUBECONFIG_PATH="./kubeconfig" EXTRA_ARGS="--skip-delete,--fail-fast"To clean up all the resources created by the e2e:setup-env task, run:
task e2e:cleanup