Skip to content

Commit 52d8b4b

Browse files
committed
docs: extension_control_path and image volumes
Signed-off-by: Gabriele Bartolini <[email protected]>
1 parent e1c00b5 commit 52d8b4b

File tree

3 files changed

+297
-0
lines changed

3 files changed

+297
-0
lines changed
511 KB
Loading
Lines changed: 297 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,297 @@
1+
---
2+
title: "The Immutable Future of PostgreSQL Extensions in Kubernetes with CloudNativePG"
3+
date: 2025-03-03T11:38:14+01:00
4+
description: "How managing Postgres extensions in Kubernetes will change"
5+
tags: ["postgresql", "postgres", "kubernetes", "k8s", "cloudnativepg", "cnpg", "postgresql", "postgres", "dok", "data on kubernetes", "extensions", "container images", "sbom", "pgvector", "imagevolume", "extension_control_path"]
6+
cover: cover.jpg
7+
thumb: thumb.jpg
8+
draft: false
9+
---
10+
11+
_Managing extensions is one of the biggest challenges in running PostgreSQL on
12+
Kubernetes. In this article, I explain why I believe CloudNativePG—now a CNCF
13+
Sandbox project—is on the verge of a breakthrough. Two important new features
14+
for both PostgreSQL and Kubernetes—the `extension_control_path` option and
15+
image volumes—will guarantee immutability to extension container images._
16+
17+
<!--more-->
18+
19+
---
20+
21+
A core principle of Kubernetes is the immutability of container images. With
22+
[CloudNativePG, we embraced this approach from day one](https://www.enterprisedb.com/blog/why-edb-chose-immutable-application-containers),
23+
firmly adhering to best practices such as immutable infrastructure,
24+
Infrastructure as Code (IaC), and shifting security left.
25+
26+
This immutability means that PostgreSQL container images must remain read-only
27+
to ensure security and reliable change management. However, this approach
28+
clashes with one of PostgreSQL’s greatest strengths: **extensibility**.
29+
Extensions have indeed played a crucial role in PostgreSQL’s rise as the most
30+
popular and versatile database engine.
31+
32+
While core extensions like `pg_stat_statements` and `postgres_fdw` are
33+
included in every [PostgreSQL operand image](https://github.com/cloudnative-pg/postgres-containers),
34+
third-party extensions—such as PostGIS, TimescaleDB, and pgvector—are separate
35+
projects and are not bundled with PostgreSQL by default.
36+
37+
In traditional virtual machine (VM) deployments, extensions can be installed at
38+
runtime using the package manager of the Linux distribution. However, with
39+
CloudNativePG, modifying the container image is not possible; you must rely on
40+
declarative configuration and pre-existing images. Additionally, deployment is
41+
only part of the challenge: updates as part of Day 2 operations are usually
42+
handled case-by-case, impacting scalability and maintainability of
43+
infrastructures and applications sensibly.
44+
45+
## The Current Approach: Pre-built Operand Images
46+
47+
Until now, the only way to run third-party extensions with CloudNativePG (and
48+
most PostgreSQL operators) without breaking immutability has been to embed them
49+
in the operand image. However, this approach has significant downsides:
50+
51+
- **Increased image size and footprint** – The more extensions included, the
52+
larger the image.
53+
- **Rigid extension selection** – Users are often left frustrated because:
54+
- Their required extension isn’t included.
55+
- They want a lighter image without unnecessary extensions.
56+
- They don’t want specific libraries due to CVEs or security concerns.
57+
- **Operational complexity** – Users must maintain custom images, as outlined
58+
in our guide on [creating container images](https://cloudnative-pg.io/blog/creating-container-images/) and
59+
following our [image requirements](https://cloudnative-pg.io/documentation/current/container_images/).
60+
61+
Not everyone has the resources or expertise to do this. Compliance is another
62+
critical factor.
63+
64+
We have long been exploring ways to enable dynamic loading of extensions in
65+
CloudNativePG. I recall insightful discussions with **Tembo**, an early
66+
[adopter of CloudNativePG](https://github.com/cloudnative-pg/cloudnative-pg/blob/main/ADOPTERS.md),
67+
about reserving a volume for dynamically installing third-party extensions.
68+
However, we decided not to pursue that idea, as it violated our immutability
69+
principle (Tembo later pursued their [Trunk](https://github.com/tembo-io/trunk)
70+
project).
71+
72+
To achieve dynamic extension loading **without** breaking immutability, we
73+
needed improvements in **both PostgreSQL and Kubernetes**.
74+
75+
Fortunately, these improvements are finally happening.
76+
77+
## What’s Missing in PostgreSQL?
78+
79+
Currently, PostgreSQL requires extensions to be installed in a system
80+
directory, such as `/usr/share/postgresql/17/extension`, where it looks for
81+
[extension control files](https://www.postgresql.org/docs/current/extend-extensions.html).
82+
83+
In traditional setups, Linux package managers (Debian/RPM) install extension
84+
packages, placing control files in this directory. While this works in a
85+
mutable environment, it is incompatible with an immutable setup like
86+
CloudNativePG, where the system directory is **read-only** for security.
87+
88+
To overcome this limitation, PostgreSQL needs to support alternative extension
89+
locations. This is precisely what the patch developed by my EDB colleagues
90+
Peter Eisentraut, Andrew Dunstan, and Matheus Alcantara and
91+
[proposed for PostgreSQL 18 ](https://commitfest.postgresql.org/patch/4913/)
92+
aims to do. The patch is based on an initial work by Christoph Berg.
93+
It is part of a broader [discussion started by David Wheeler](https://www.postgresql.org/message-id/flat/[email protected])
94+
(Tembo), which I contributed to to align it with Kubernetes' immutability
95+
needs. We further refined this direction during in-person conversations at
96+
PostgreSQL Europe in Athens in October 2024
97+
([David wrote a great recap here](https://justatheory.com/2024/11/rfc-extension-packaging-lookup/)).
98+
99+
### Introducing `extension_control_path`
100+
101+
The proposed patch introduces a new PostgreSQL configuration option (GUC),
102+
`extension_control_path`, which allows users to specify additional
103+
directories for extension control files. It is set to `$system` by default,
104+
but users can define multiple paths—just like other path-like configuration
105+
options in PostgreSQL.
106+
107+
Combined with `dynamic_library_path`, this feature enables PostgreSQL to
108+
locate control files and shared libraries from multiple directories, **breaking
109+
free from the single system-wide location constraint**.
110+
111+
I hope this patch is merged into PostgreSQL and becomes part of **PostgreSQL
112+
18**, ensuring that future versions fully support this approach.
113+
114+
## What’s Missing in Kubernetes?
115+
116+
Even with PostgreSQL supporting multiple extension paths, we still need a way
117+
to dynamically mount extensions into a running CloudNativePG cluster **without
118+
modifying the primary container image**. This is where Kubernetes is stepping
119+
up.
120+
121+
### Introducing `ImageVolume` resources
122+
123+
The [ImageVolume feature](https://kubernetes.io/blog/2024/08/16/kubernetes-1-31-image-volume-source/)
124+
was introduced as an **alpha feature in Kubernetes 1.31** and is expected to
125+
reach beta soon. Its goal is to be promoted to a beta release state in
126+
Kubernetes 1.33.
127+
128+
This feature allows us to mount a container image as a **read-only and
129+
immutable volume** inside a running pod. It will enable PostgreSQL extensions
130+
that are packaged as **independent OCI-compliant container images** to be
131+
mounted inside CloudNativePG clusters at a known directory (e.g.,
132+
`/extensions/<EXTENSION_NAME>`).
133+
134+
At this point, all we’ll need to do is set the ``extension_control_path`` and
135+
``dynamic_library_path`` options accordingly. An operator like CloudNativePG
136+
can automate this process, making it seamless for users. The same approach can
137+
be repeated multiple times, once per required extension.
138+
139+
Image extension images at that point must be compatible with the Postgres base
140+
image that the CloudNativePG has deployed (for example, the same distribution
141+
and architecture).
142+
143+
144+
## How We Tested These Features in CloudNativePG
145+
146+
Although we weren’t directly involved in developing these new PostgreSQL
147+
features, **some of us at CloudNativePG contributed to testing the
148+
corresponding patch**.
149+
150+
I want to give special thanks to **Niccolò Fei** for his outstanding work
151+
validating the patch developed by Peter, Andrew, and Matheus. This includes
152+
patches for the CloudNativePG operator and extension images.
153+
154+
Our testing focused on:
155+
156+
- **Streamlining the build process** for PostgreSQL operand images that
157+
incorporate patches from the PostgreSQL commit fest (for details, see my
158+
[previous article](https://www.gabrielebartolini.it/articles/2024/09/how-to-test-a-postgresql-commitfest-patch-in-kubernetes/)).
159+
- **Developing a pilot patch for CloudNativePG** to declaratively add
160+
PostgreSQL extensions via a container image. The operator mounts the image as
161+
an `ImageVolume`, automatically configuring `extension_control_path` and
162+
`dynamic_library_path`.
163+
See [CloudNativePG PR #6546](https://github.com/cloudnative-pg/cloudnative-pg/pull/6546).
164+
- **Creating self-contained extension images**, such as one for `pgvector`.
165+
166+
### Lightweight Extension Images
167+
168+
Focusing on the last point, Niccolò proposed a
169+
[Dockerfile for pgvector](https://github.com/EnterpriseDB/pgvector/blob/dev/5645/Dockerfile.cnpg)
170+
that produces a **minimal** image—containing only the `lib` and `share`
171+
directories—at just **1.6MB**.
172+
173+
To inspect its contents, use:
174+
175+
```sh
176+
dive ghcr.io/cloudnative-pg/pgvector-18-testing:latest
177+
```
178+
179+
### Deploying Extensions Declaratively
180+
181+
To illustrate the proposed solution, consider this example (note: the format is
182+
still in alpha and may change):
183+
184+
```yaml
185+
apiVersion: postgresql.cnpg.io/v1
186+
kind: Cluster
187+
metadata:
188+
name: postgresql-with-extensions
189+
spec:
190+
instances: 1
191+
# This points to the temporary build of the commit fest patch
192+
imageName: ghcr.io/cloudnative-pg/postgresql-trunk:18-cf-4913
193+
postgresql:
194+
extensions:
195+
- name: pgvector
196+
image:
197+
reference: ghcr.io/cloudnative-pg/pgvector-18-testing:latest
198+
storage:
199+
storageClass: standard
200+
size: 1Gi
201+
```
202+
203+
204+
The key addition is the `.spec.postgresql.extensions` section, where users can
205+
define extensions and their corresponding images (each an `ImageVolume`
206+
resource). This configuration can be updated dynamically.
207+
208+
### What Happens Under the Hood
209+
210+
When an extension image is specified:
211+
212+
1. The operator triggers a rolling update, starting with replicas.
213+
214+
2. Each referenced image is mounted as a **read-only volume** using Kubernetes'
215+
`ImageVolume` resource.
216+
217+
3. In this example, `ghcr.io/cloudnative-pg/pgvector-18-testing:latest` is
218+
mounted on `/extensions/pgvector`.
219+
220+
4. CloudNativePG updates `dynamic_library_path` and `extension_control_path` to
221+
include the `/extensions/pgvector/lib` and `/extensions/pgvector/share`
222+
directories, respectively.
223+
224+
Once deployed, you can verify the extension’s availability:
225+
226+
```sql
227+
postgres=# SELECT * FROM pg_available_extensions WHERE name = 'vector';
228+
-[ RECORD 1 ]-----+-----------------------------------------------------
229+
name | vector
230+
default_version | 0.8.0
231+
installed_version |
232+
comment | vector data type and ivfflat and hnsw access methods
233+
```
234+
235+
**Important:** This feature is not yet available out of the box. To use `ImageVolume`, you need:
236+
237+
- **Kubernetes 1.31+** with the [ImageVolume feature gate](https://kubernetes.io/docs/reference/command-line-tools-reference/feature-gates/)
238+
enabled.
239+
- **CRI-O** as the container runtime (*containerd support has been merged but
240+
is not yet available*).
241+
242+
We applied the same approach to **PostGIS during our pilot project**, creating
243+
a self-contained extension image. The method proved effective even for complex
244+
extensions.
245+
246+
## Conclusion
247+
248+
This is just the beginning. This initiative marks the first iteration toward a
249+
new approach to managing PostgreSQL extensions in Kubernetes—bridging the gap
250+
between extension developers and consumers. The most important takeaway is that
251+
we’re heading in the right direction, opening the door to new possibilities and
252+
allowing the best solutions to emerge through exploration.
253+
254+
While PostgreSQL’s `extension_control_path` and Kubernetes'
255+
`ImageVolume` feature are fundamental pieces, there’s another
256+
critical component: **the distribution of PostgreSQL extensions**.
257+
258+
It's time for PostgreSQL extension developers to embrace **OCI images as
259+
first-class artifacts**, alongside traditional RPM and Debian packages.
260+
Ideally, every extension should provide a self-contained image for mainstream
261+
Linux distributions. Whether these images are built from existing packages or
262+
directly from source (e.g., via `Makefile`) is a decision best left to those
263+
with deeper expertise in the packaging ecosystem.
264+
265+
With **PostgreSQL 18** supporting configurable extension paths and **Kubernetes
266+
1.33** introducing `ImageVolume`, CloudNativePG is entering a **new era of
267+
dynamic, immutable, and scalable extension management**. By packaging
268+
extensions as independent OCI-compliant images, we can finally **decouple
269+
PostgreSQL operand images from extensions**, keeping them minimal and flexible.
270+
This unlocks several key benefits:
271+
272+
- **Install third-party extensions dynamically**—no need to rebuild container
273+
images.
274+
- **Facilitate testing and validation of extensions**
275+
- **Simplify PostgreSQL extension upgrades** without affecting the core
276+
database image.
277+
- **Ensure strict immutability** while enhancing security, change management,
278+
scalability, and maintainability.
279+
280+
This is a game-changer for **CloudNativePG** and the broader
281+
**PostgreSQL-on-Kubernetes ecosystem**.
282+
283+
The future of PostgreSQL extensions in Kubernetes is **immutable, yet
284+
flexible—just as it should be**.
285+
286+
---
287+
288+
Stay tuned for the upcoming recipes! For the latest updates, consider
289+
subscribing to my [LinkedIn](https://www.linkedin.com/in/gbartolini/) and
290+
[Twitter](https://twitter.com/_GBartolini_) channels.
291+
292+
If you found this article informative, feel free to share it within your
293+
network on social media using the provided links below. Your support is
294+
immensely appreciated!
295+
296+
_Cover Picture: [“Sofyan Efendi Dipinggiran Sungai“](https://commons.wikimedia.org/wiki/File:Sofyan_Efendi_Dipinggiran_Sungai_IMG_3249.jpg)._
297+
197 KB
Loading

0 commit comments

Comments
 (0)