appwrite/docker-postgres: The Postgres Image That Ships With Its Own Opinion

A deep dive into how Appwrite packages extensions, preload config, and multi-version builds into a zero-friction database image for vector search, cron, and cloud-native deployments.

7 to 9 min read View on GitHub More from appwrite

A sealed Postgres container rendered as a precise machine with curated modules mounted inside. The image shows extension components and a startup chamber where preload settings are already stamped into place, explaining that this is not a generic database image but a preconfigured platform primitive.
Appwrite’s image does not just install PostgreSQL extensions. It bakes in the runtime assumptions they need to wake up on first boot.
Key Takeaways

The Quiet Trick That Makes This Image Different

Most Postgres containers start as blank slates. Appwrite’s image starts with an opinion. The interesting move is not that it installs extensions, but that it changes the startup default so the extensions that need preload are already waiting when a new cluster initializes.

That distinction matters. If you have ever wrestled with `shared_preload_libraries`, custom flags, or hand-edited configs, you know the pain is not installation. It is making the runtime state line up every single time the container boots.

# Core idea, simplified from the image build
ARG PG_MAJOR=18
RUN apt-get update \
  && apt-get install -y postgresql-${PG_MAJOR}-pgvector postgresql-${PG_MAJOR}-postgis-3 postgresql-${PG_MAJOR}-cron postgresql-${PG_MAJOR}-pg-stat-statements \
  && printf "\nshared_preload_libraries = 'pg_stat_statements,pg_cron'\n" >> /usr/share/postgresql/${PG_MAJOR}/postgresql.conf.sample

# The important part is the sample config change.
# New clusters inherit the preload defaults automatically.

Why Appwrite Needs a Specialized Postgres

This repo exists because Appwrite is not shipping a generic database server. It is shipping the database layer for a platform that needs vector search, geospatial queries, background jobs, and standard Postgres utilities without making users assemble the stack by hand.

That is a platform choice. Vanilla Postgres is flexible, but flexibility is not the same as product. Appwrite is packaging a known-good shape for its cloud workloads and making that shape the default.

Inside the Dockerfile

The Dockerfile is lean, but each line pulls weight. A single `PG_MAJOR` argument lets the same build logic support multiple upstream versions. Then `apt-get` installs a curated set of extension packages, and the image appends preload settings to Postgres’s sample config so the first initialized cluster starts with the right background workers enabled.

The image becomes useful because build-time config flows into first boot, and first boot determines whether the extensions are actually alive.

# The pattern, conceptually
FROM postgres:${PG_MAJOR}

# Install extension packages
RUN apt-get update && apt-get install -y \
  postgresql-${PG_MAJOR}-pgvector \
  postgresql-${PG_MAJOR}-postgis-3 \
  postgresql-${PG_MAJOR}-cron \
  postgresql-${PG_MAJOR}-pg-stat-statements

# Preload config baked into the image's sample file
RUN printf "shared_preload_libraries = 'pg_stat_statements,pg_cron'\n" \
  >> "/usr/share/postgresql/${PG_MAJOR}/postgresql.conf.sample"

The Extensions Are the Product

ApproachStartup behaviorOperational burdenWhat it feels like
Vanilla Postgres with manual setupExtensions are added later, if you remember the config stepsHighA blank server that becomes useful only after careful surgery
Custom image with packages onlyPackages are present, but preload and runtime wiring are left to the userMediumA parts kit with the hardest step still on your plate
Appwrite’s baked-in imageThe image boots with preload defaults already in placeLowA database primitive that behaves like a product

The bundle is the point: `pgvector` for embeddings, PostGIS for spatial queries, `pg_cron` for scheduled work, and `pg_stat_statements` for visibility. Add contrib modules like `uuid-ossp` and `pgcrypto`, and the image starts to look less like a database server and more like a platform substrate.

Tests That Prove More Than Installation

The verify script is where the repo earns trust. It does not stop at container startup. It waits for readiness, creates extensions, runs vector operations, and checks that scheduled work actually fires. In other words, it tests the behavior users care about, not just the presence of binaries.

# Conceptual shape of tests/verify.sh
until pg_isready -h "$PGHOST" -p "$PGPORT"; do
  sleep 1
done

psql -v ON_ERROR_STOP=1 -c "CREATE EXTENSION IF NOT EXISTS vector;"
psql -v ON_ERROR_STOP=1 -c "CREATE EXTENSION IF NOT EXISTS postgis;"
psql -v ON_ERROR_STOP=1 -c "CREATE EXTENSION IF NOT EXISTS pg_cron;"
psql -v ON_ERROR_STOP=1 -c "SELECT '[1,2,3]'::vector <-> '[1,2,4]'::vector;"
psql -v ON_ERROR_STOP=1 -c "SELECT cron.schedule('verify-job', '* * * * *', 'SELECT 1');"

That difference is subtle but important. A container can be alive and still be wrong. The verify script closes that gap by checking the mechanics behind the promise.

The Packaging Philosophy Behind PG 17 and PG 18

Supporting both PostgreSQL 17 and 18 from one repository says something about Appwrite’s posture. The image is not frozen to a single stable version, and it is not gambling on a future major release without a fallback. It is straddling the upgrade path on purpose.

Version trackWhy it mattersRisk profile
PG 17Keeps backward-compatible tags available for existing deploymentsLower operational risk
PG 18Signals that Appwrite wants to stay close to upstream PostgreSQL evolutionForward-leaning but more volatile
Dual-track buildLets the project serve both stability and migration timingBest of both, at the cost of more CI discipline

The interesting part is not the number of versions. It is the discipline required to make one source build cleanly across them while keeping the extension story intact.

What This Teaches About Platform Design

This repo is a small case study in platform thinking. Hide sharp edges. Make the correct setup the default setup. Verify the behavior people actually depend on. Once you do that, infrastructure starts to feel like a product instead of a chores list.

That matters even more in the vector search era. Postgres is no longer just a relational database. For many teams, it is the place where transactional data, embeddings, background jobs, and operational metadata meet. Appwrite’s image acknowledges that reality and packages it accordingly.


Sources