Sign inSign up

alexeye/postgres-azure-flex

By alexeye

•Updated 4 days ago

PostgreSQL mirroring the Azure Flexible Server extension set - pgvector, PostGIS, TimescaleDB, Apach

Image
0

8.9K

alexeye/postgres-azure-flex repository overview

⁠PostgreSQL Azure Flexible Server Compatible

Docker Hub Docker Pulls Image Size

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.

⁠Scope

The extension list is not curated by taste — it is Microsoft's published list, and the test suite enforces it in both directions:

  1. Included if Azure lists it for that PostgreSQL major and it installs from the PGDG apt repository (or, for PLV8, from a prebuilt donor image).
  2. Nothing is compiled from source. Source builds are slow, break on every upstream release, and are the reason earlier builds took hours. Azure-listed extensions that would need one are documented as known gaps⁠ instead.
  3. Extensions Azure does not list are rejected, however useful they might be. test.sh fails the build if one reappears.

⁠Available Tags

TagPostgreSQLNotes
18, latest18.xRecommended. GA on Azure Flexible Server. No PLV8 — see gaps
1717.xIncludes PLV8
1616.xIncludes PLV8 and pg_failover_slots
18-cnpg18.xCloudNativePG operand — not for docker run, see below
17-cnpg17.xCloudNativePG operand
18-YYYYMMDD18.xImmutable dated snapshot
17-YYYYMMDD17.xImmutable dated snapshot
16-YYYYMMDD16.xImmutable dated snapshot
18-cnpg-YYYYMMDD18.xImmutable dated operand snapshot
17-cnpg-YYYYMMDD17.xImmutable dated operand snapshot

⁠Two Flavours

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
Basepostgres:<major>-bookwormghcr.io/cloudnative-pg/postgresql:<major>-standard-bookworm
Fordocker run, Compose, CIThe CloudNativePG operator in Kubernetes
Runs asroot, entrypoint drops to postgresuid 26, already non-root
Entrypointdocker-library entrypointnone — the operator supplies its instance manager
/docker-entrypoint-initdb.dshippedabsent — the operator never runs it
postgresql.conf.sampleour defaults appendedstock — the operator generates the config
Extensionsidenticalidentical

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.

⁠Running it under CloudNativePG

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.

⁠What was verified

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 present
  • postgresql.conf.sample carries no appended settings; no /docker-entrypoint-initdb.d
  • Under --read-only --user 26:26: initdb succeeds, the server starts, and vector, pg_trgm, hstore, pgcrypto, uuid-ossp all create
  • shared_preload_libraries is empty on a stock boot
  • test.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.

⁠Quick Start

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;

⁠Included Extensions

⁠Vector, graph, and geospatial
ExtensionDescription
pgvectorVector similarity search with HNSW and IVFFlat indexes
Apache AGEGraph database with openCypher query language
PostGISFull geospatial stack (geometry, raster, topology, tiger geocoder)
pgRoutingGeospatial routing and network analysis
pointcloudLIDAR / point cloud storage
address_standardizerAddress parsing and normalization
⁠Time-series, scheduling, and partitioning
ExtensionDescription
TimescaleDBHypertables, compression, continuous aggregates
pg_cronCron-based job scheduler
pg_partmanAutomatic partition management (plus the pg_partman_bgw worker)
⁠Maintenance and query tuning
ExtensionDescription
pg_repackOnline table reorganization without long locks
pg_squeezeAutomatic bloat removal
HypoPGHypothetical indexes for query planning
pg_hint_planQuery hints for plan control
plpgsql_checkStatic analysis and profiling for PL/pgSQL
⁠Analytics and data types
ExtensionDescription
HLLHyperLogLog cardinality estimation
t-digestQuantile approximation for streaming data
pg_ivmIncrementally maintained materialized views
ip4rIPv4/IPv6 range types
semverSemantic versioning data type
orafceOracle compatibility functions
⁠Replication, CDC, and audit
ExtensionDescription
pglogicalLogical replication
wal2jsonJSON output plugin for logical decoding
pg_failover_slotsFailover slot synchronization (PG 16 only — Azure lists it for 16 and below)
pgauditAudit logging
credcheckPassword and credential policy enforcement
⁠JavaScript
ExtensionDescription
PLV8JavaScript stored procedures using the V8 engine (PG 16/17 only — see gaps)
⁠Foreign data wrappers
ExtensionDescription
tds_fdwQuery SQL Server / SAP ASE
postgres_fdw, dblinkQuery other PostgreSQL servers
⁠Built-in contrib extensions

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.

⁠Known Gaps

Azure lists these; this image does not ship them. Everything else on Azure's list is present.

ExtensionWhy not
azure_ai, azure_storageAzure-managed service integrations; cannot be replicated off-Azure
pg_diskannMicrosoft-proprietary; no public source or package
oracle_fdwPGDG'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, topnNot in PGDG; would require a source build

⁠Preloaded Extensions

These are in shared_preload_libraries by default, so they work with no configuration:

ExtensionWhat it enables
pg_stat_statementsQuery performance statistics
pg_cronScheduled jobs with cron syntax
TimescaleDBHypertables, compression, continuous aggregates
Apache AGEGraph 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:

ExtensionWhy it is opt-in
pgauditWrites an audit log entry per matching statement
credcheckEnforces password rules on CREATE ROLE / ALTER ROLE immediately
pg_partman_bgwRuns a background worker on an interval
pg_squeezeRewrites tables in the background
pglogicalChanges replication behaviour
pg_hint_planChanges plan selection
pg_failover_slotsPG 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.

⁠Docker Compose

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:

⁠Environment Variables

VariableDefaultDescription
POSTGRES_PASSWORD-Required. Superuser password
POSTGRES_USERpostgresSuperuser username
POSTGRES_DBpostgresDefault database name

⁠Security

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:

CriticalHigh
postgres:18-bookworm (upstream)532
This image645

So the large majority is inherited. Where it comes from:

SourceCan we fix it?
openssl, perl, libtiff — Debian has no fix yetNo. Already at the newest version Debian ships. These are 4 of the 6 criticals.
The base's gosu binary (Go stdlib) — 22 findingsNo. 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 addOnly by dropping PostGIS. This is the one critical we add over the base.
Debian packages with a fix availableYes — and we do. Currently zero.

What we actually do about it:

  • Debian security updates are applied at build time, so packages with an available fix are patched even when the base image lags.
  • Weekly rebuilds pull in new base-image and extension patches automatically.
  • CI scans every build before pushing, and splits the report into fixable (actionable, should be empty) and everything (informational). The counts are in the run summary rather than buried in a log.
  • Provenance and SBOM attestations are published with each image, so Scout reports the real base image instead of guessing, and you get an accurate package inventory.
⁠The gosu findings are marked not-affected (VEX)

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-alpine as an update for a PG 18 image. That advice is a major-version downgrade and a libc change. Ignore it.

⁠Image Details

  • Base image: official postgres:XX-bookworm
  • Architectures: linux/amd64, linux/arm64
  • CI: built, then tested, then pushed — a failing test cannot publish an image
  • Attestations: max-mode provenance + SBOM
  • License: Apache License 2.0
  • Source: github.com/AlexeyEvlampiev/custom-docker⁠

Tag summary

Content type

Image

Digest

sha256:5b07a640d…

Size

401.2 MB

Last updated

4 days ago

docker pull alexeye/postgres-azure-flex