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.
- Appwrite turns Postgres extensions from a setup task into an image-level default, which removes the usual preload and config friction.
- The repository’s real product is not just package installation, but first-boot behavior that already knows how to load the right runtime pieces.
- The verify script matters because it proves extensions are functional, not merely present in the filesystem.
- Supporting PostgreSQL 17 and 18 at once signals a platform image meant to stay aligned with upstream PostgreSQL, not a one-off container hack.
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 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
| Approach | Startup behavior | Operational burden | What it feels like |
|---|---|---|---|
| Vanilla Postgres with manual setup | Extensions are added later, if you remember the config steps | High | A blank server that becomes useful only after careful surgery |
| Custom image with packages only | Packages are present, but preload and runtime wiring are left to the user | Medium | A parts kit with the hardest step still on your plate |
| Appwrite’s baked-in image | The image boots with preload defaults already in place | Low | A 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 track | Why it matters | Risk profile |
|---|---|---|
| PG 17 | Keeps backward-compatible tags available for existing deployments | Lower operational risk |
| PG 18 | Signals that Appwrite wants to stay close to upstream PostgreSQL evolution | Forward-leaning but more volatile |
| Dual-track build | Lets the project serve both stability and migration timing | Best 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.