PostgreSQL mirroring the Azure Flexible Server extension set - pgvector, PostGIS, TimescaleDB, Apach
8.9K
PostgreSQL images that mirror the extension set published for Azure Database for PostgreSQL Flexible Server. For local development and CI against something that behaves like your Azure server, and for self-hosted deployments that want the same extension surface.
Looking for Parquet, S3 or Delta Lake? Those are not on Azure's list, so they are not here. See
alexeye/postgres-lakehouse — the same image plus an analytics layer, for when you control the deployment.
The extension list is not curated by taste — it is Microsoft's published list, and the test suite enforces it in both directions:
test.sh fails the build if one reappears.| Tag | PostgreSQL | Notes |
|---|---|---|
18, latest | 18.x | Recommended. GA on Azure Flexible Server. No PLV8 — see gaps |
17 | 17.x | Includes PLV8 |
16 | 16.x | Includes PLV8 and pg_failover_slots |
18-cnpg | 18.x | CloudNativePG operand — not for docker run, see below |
17-cnpg | 17.x | CloudNativePG operand |
18-YYYYMMDD | 18.x | Immutable dated snapshot |
17-YYYYMMDD | 17.x | Immutable dated snapshot |
16-YYYYMMDD | 16.x | Immutable dated snapshot |
18-cnpg-YYYYMMDD | 18.x | Immutable dated operand snapshot |
17-cnpg-YYYYMMDD | 17.x | Immutable dated operand snapshot |
Both are built from the same Dockerfile and install the same extension packages.
test.sh detects which one it is given and asserts the same extension surface
against both, so they cannot drift apart.
18, 17, 16 (standard) | 18-cnpg, 17-cnpg | |
|---|---|---|
| Base | postgres:<major>-bookworm | ghcr.io/cloudnative-pg/postgresql:<major>-standard-bookworm |
| For | docker run, Compose, CI | The CloudNativePG operator in Kubernetes |
| Runs as | root, entrypoint drops to postgres | uid 26, already non-root |
| Entrypoint | docker-library entrypoint | none — the operator supplies its instance manager |
/docker-entrypoint-initdb.d | shipped | absent — the operator never runs it |
postgresql.conf.sample | our defaults appended | stock — the operator generates the config |
| Extensions | identical | identical |
Use -cnpg only under the operator. It has no entrypoint, so docker run
on it does nothing useful. Conversely the standard flavour will not start as an
operand: it has no passwd entry for uid 26, and Postgres refuses with
could not look up effective user ID.
Pin by digest in production. This manifest is the one used to validate the image:
apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
name: example
spec:
instances: 2
imageName: alexeye/postgres-azure-flex:18-cnpg # pin @sha256:... in production
# The image has a passwd entry for uid 26, which is what the operator's
# default securityContext expects. Note that no official CloudNativePG
# operand has gid 26 either — 17.5 is gid 999, the bookworm ones are gid 102.
# What matters is that uid 26 resolves; the group comes from the pod.
postgresUID: 26
postgresGID: 26
storage:
size: 10Gi
postgresql:
# This image ships NO shared_preload_libraries of its own — set here what
# you actually need. Anything listed must be an extension the image has.
parameters:
shared_preload_libraries: "pg_stat_statements"
# Needed only if you use wal2json for logical decoding: recent PostgreSQL
# minors gate output plugins behind an allowlist that excludes it.
# parameters:
# output_plugin_libraries: "pgoutput, test_decoding, wal2json"
bootstrap:
initdb:
database: app
owner: app
# Extension creation is the application's job — there is no initdb.d hook
# in this flavour. Either list them here or let your migration tool do it.
postInitApplicationSQL:
- CREATE EXTENSION IF NOT EXISTS vector
- CREATE EXTENSION IF NOT EXISTS pg_trgm
- CREATE EXTENSION IF NOT EXISTS hstore
- CREATE EXTENSION IF NOT EXISTS pgcrypto
- CREATE EXTENSION IF NOT EXISTS "uuid-ossp"
Backups. The -cnpg flavour deliberately ships no barman-cloud-*
binaries. CloudNativePG 1.30 states they are no longer required — the Barman
Cloud Plugin runs them in a sidecar. If you are still on the in-tree
barmanObjectStore, migrate to the plugin before switching to this image, or
WAL archiving will stop silently.
Against 18-cnpg, built locally on 2026-09-15:
id → uid=26(postgres); initdb, postgres, pg_ctl, pg_controldata,
pg_basebackup, du all on PATH; en_US.utf8 presentpostgresql.conf.sample carries no appended settings; no
/docker-entrypoint-initdb.d--read-only --user 26:26: initdb succeeds, the server starts, and
vector, pg_trgm, hstore, pgcrypto, uuid-ossp all createshared_preload_libraries is empty on a stock boottest.sh: 112 passed / 0 failed (the standard flavour: 104 / 0)Not yet verified: a real Kubernetes cluster with the operator, a replica
joining via pg_basebackup, and a WAL archive landing in object storage.
docker run -d \
--name postgres \
-e POSTGRES_PASSWORD=mysecretpassword \
-p 5432:5432 \
alexeye/postgres-azure-flex:18
Extensions are created during container initialization, so you can connect and use them straight away:
-- Vector similarity search
SELECT '[1,2,3]'::vector <-> '[4,5,6]'::vector AS distance;
-- Graph queries (Apache AGE)
SELECT * FROM cypher('my_graph', $$ CREATE (n:Person {name: 'Alice'}) RETURN n $$) AS (n agtype);
-- Time-series (TimescaleDB)
SELECT create_hypertable('metrics', 'time');
-- Scheduled jobs (pg_cron)
SELECT cron.schedule('nightly-vacuum', '0 3 * * *', 'VACUUM ANALYZE');
-- Geospatial (PostGIS)
SELECT ST_Distance(ST_MakePoint(-73.9857, 40.7484)::geography,
ST_MakePoint(-0.1276, 51.5074)::geography);
-- Incrementally maintained materialized views (pg_ivm)
SELECT create_immv('daily_totals', 'SELECT day, sum(amount) FROM sales GROUP BY day');
-- PL/pgSQL linting (plpgsql_check)
SELECT * FROM plpgsql_check_function('my_function(int)');
-- JavaScript stored procedures (PLV8 — PG 16/17 only)
CREATE FUNCTION js_add(a int, b int) RETURNS int AS $$ return a + b; $$ LANGUAGE plv8;
| Extension | Description |
|---|---|
| pgvector | Vector similarity search with HNSW and IVFFlat indexes |
| Apache AGE | Graph database with openCypher query language |
| PostGIS | Full geospatial stack (geometry, raster, topology, tiger geocoder) |
| pgRouting | Geospatial routing and network analysis |
| pointcloud | LIDAR / point cloud storage |
| address_standardizer | Address parsing and normalization |
| Extension | Description |
|---|---|
| TimescaleDB | Hypertables, compression, continuous aggregates |
| pg_cron | Cron-based job scheduler |
| pg_partman | Automatic partition management (plus the pg_partman_bgw worker) |
| Extension | Description |
|---|---|
| pg_repack | Online table reorganization without long locks |
| pg_squeeze | Automatic bloat removal |
| HypoPG | Hypothetical indexes for query planning |
| pg_hint_plan | Query hints for plan control |
| plpgsql_check | Static analysis and profiling for PL/pgSQL |
| Extension | Description |
|---|---|
| HLL | HyperLogLog cardinality estimation |
| t-digest | Quantile approximation for streaming data |
| pg_ivm | Incrementally maintained materialized views |
| ip4r | IPv4/IPv6 range types |
| semver | Semantic versioning data type |
| orafce | Oracle compatibility functions |
| Extension | Description |
|---|---|
| pglogical | Logical replication |
| wal2json | JSON output plugin for logical decoding |
| pg_failover_slots | Failover slot synchronization (PG 16 only — Azure lists it for 16 and below) |
| pgaudit | Audit logging |
| credcheck | Password and credential policy enforcement |
| Extension | Description |
|---|---|
| PLV8 | JavaScript stored procedures using the V8 engine (PG 16/17 only — see gaps) |
| Extension | Description |
|---|---|
| tds_fdw | Query SQL Server / SAP ASE |
| postgres_fdw, dblink | Query other PostgreSQL servers |
uuid-ossp, pgcrypto, hstore, citext, ltree, pg_trgm, unaccent, fuzzystrmatch, dict_int, dict_xsyn, btree_gin, btree_gist, bloom, intarray, intagg, cube, earthdistance, isn, tablefunc, lo, sslinfo, pg_stat_statements, pg_buffercache, pg_freespacemap, pg_visibility, pg_prewarm, pgrowlocks, pgstattuple, pageinspect, amcheck, auto_explain, tsm_system_rows, tsm_system_time.
Azure lists these; this image does not ship them. Everything else on Azure's list is present.
| Extension | Why not |
|---|---|
azure_ai, azure_storage | Azure-managed service integrations; cannot be replicated off-Azure |
pg_diskann | Microsoft-proprietary; no public source or package |
oracle_fdw | PGDG's oracle-libs is a stub that downloads Oracle Instant Client from Oracle at install time — a build-time dependency on a third-party download |
plv8 (PG 18 only) | PGDG does not package PLV8 for Debian, and sibedge/postgres-plv8 has no GA PG 18 tag (newest is 18beta3, stale since Aug 2025). Use the 17 or 16 tag if you need PLV8 |
anon, login_hook, postgis_sfcgal, postgres_protobuf, rdkit, session_variable, temporal_tables, topn | Not in PGDG; would require a source build |
These are in shared_preload_libraries by default, so they work with no configuration:
| Extension | What it enables |
|---|---|
| pg_stat_statements | Query performance statistics |
| pg_cron | Scheduled jobs with cron syntax |
| TimescaleDB | Hypertables, compression, continuous aggregates |
| Apache AGE | Graph queries with openCypher |
Everything else that can be preloaded is left off deliberately — each one changes server behaviour or enforces policy, and should be an explicit choice:
| Extension | Why it is opt-in |
|---|---|
pgaudit | Writes an audit log entry per matching statement |
credcheck | Enforces password rules on CREATE ROLE / ALTER ROLE immediately |
pg_partman_bgw | Runs a background worker on an interval |
pg_squeeze | Rewrites tables in the background |
pglogical | Changes replication behaviour |
pg_hint_plan | Changes plan selection |
pg_failover_slots | PG 16 only; changes slot handling |
To enable one, extend the preload list:
docker run -d \
-e POSTGRES_PASSWORD=mysecretpassword \
alexeye/postgres-azure-flex:18 \
-c shared_preload_libraries='pg_stat_statements,pg_cron,timescaledb,age,pgaudit'
Or mount postgresql.conf.sample from this directory, which documents the common settings for each.
services:
postgres:
image: alexeye/postgres-azure-flex:18
environment:
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-changeme}
POSTGRES_DB: myapp
ports:
- "5432:5432"
# PG 18+ stores data in a major-version subdirectory, so the mount point is
# /var/lib/postgresql. On the `17` and `16` tags use /var/lib/postgresql/data.
volumes:
- pgdata:/var/lib/postgresql
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 10s
timeout: 5s
retries: 5
restart: unless-stopped
volumes:
pgdata:
| Variable | Default | Description |
|---|---|---|
POSTGRES_PASSWORD | - | Required. Superuser password |
POSTGRES_USER | postgres | Superuser username |
POSTGRES_DB | postgres | Default database name |
What the vulnerability scanners show, and what it means.
Docker Hub, Scout and Trivy will report a non-zero CVE count on this image. That is expected, and the honest breakdown is:
Measured on :18 against its own base, postgres:18-bookworm:
| Critical | High | |
|---|---|---|
postgres:18-bookworm (upstream) | 5 | 32 |
| This image | 6 | 45 |
So the large majority is inherited. Where it comes from:
| Source | Can we fix it? |
|---|---|
openssl, perl, libtiff — Debian has no fix yet | No. Already at the newest version Debian ships. These are 4 of the 6 criticals. |
The base's gosu binary (Go stdlib) — 22 findings | No. Go toolchain CVEs; upstream has to rebuild. gosu 1.19 is the newest release and is already what's here. It runs once at container start to exec the server as the postgres user, and never opens a socket — the vulnerable HTTP/TLS paths are unreachable. |
libtiff, via the PostGIS/GDAL stack we add | Only by dropping PostGIS. This is the one critical we add over the base. |
| Debian packages with a fix available | Yes — and we do. Currently zero. |
What we actually do about it:
Each image ships an OpenVEX document at
/usr/local/share/vex/gosu-go-stdlib.vex.json declaring those 22 gosu findings
not_affected / vulnerable_code_not_in_execute_path.
That is not hand-waving. govulncheck — the Go team's reachability analyser —
run against the exact binary from the image reports:
$ govulncheck -mode=binary ./gosu
No vulnerabilities found.
Your code is affected by 0 vulnerabilities.
The vulnerable code is linked in but never called. gosu parses argv, calls setuid/setgid and execs; it opens no sockets and reads no untrusted input.
Docker Scout shows the justification automatically, but only applies it if you
trust the author — --vex-author defaults to Docker's own statements. That
opt-in is deliberate: no publisher should be able to silence your scanner
unilaterally. To have it take effect:
docker scout cves --vex-author "AlexeyEvlampiev <[email protected]>" alexeye/postgres-azure-flex:18
Verified: that command reports 0C 0H 0M 0L where the default reports 22.
The claim is re-checked whenever the base image changes — see
vex/generate.py
for the procedure. If gosu ever gains a reachable vulnerability, the document
is withdrawn.
Check any tag yourself:
docker scout cves --only-severity critical,high --only-fixed alexeye/postgres-azure-flex:18
If that returns findings, it is a genuine bug in this image — please open an issue.
A note on Scout's "recommended base image": it has suggested
postgres:16-alpineas an update for a PG 18 image. That advice is a major-version downgrade and a libc change. Ignore it.
postgres:XX-bookwormlinux/amd64, linux/arm64Content type
Image
Digest
sha256:5b07a640d…
Size
401.2 MB
Last updated
4 days ago
docker pull alexeye/postgres-azure-flex