Self-hosted service
docker-library/postgres avatar
docker-library/postgres

docker-library/postgres: two base images and one entrypoint script

Docker Official Image packaging for Postgres

2,519 stars1,186 forksShellMIT

At a glance

What is it?
The official Postgres image is built from an Alpine and a Debian template that share the same entrypoint logic. This is what the repository actually holds, and where the real documentation lives.
Who is it for?
docker-library/postgres is a packaging repository, not a tutorial, and the two things it is genuinely useful for are answering which Postgres versions are supported and explaining why the first start is different from every start after it. Version 19 is the newest directory in the tree, with 14 still present, and the last push was on 2026-09-24 on the master branch.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 12 days ago.
What is it written in?
Mainly Shell, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on September 28, 2026, and from our analysis. They are not legal advice.

Editorial analysis

A repository with two templates instead of one Dockerfile

The difference from the PHP packaging repository is visible in the first line of the tree. There are two templates, `Dockerfile-alpine.template` and `Dockerfile-debian.template`, rather than one Linux template. Every published Postgres image therefore exists in two flavours, and which one you get depends on the tag suffix you choose.

That choice is not cosmetic. Alpine is built against musl libc and Debian against glibc, and some binary extensions behave differently between them. It is the same reason the PHP repository can get away with a single template and this one cannot. If you are installing a Postgres extension from a prebuilt binary package, the base image family is a decision you may need to make deliberately rather than inherit.

Both templates are generated into the version directories by `apply-templates.sh`, and the version directories run from `14/` through `19/`. Six major versions are in the tree, which tells you the support window the community maintains. `versions.json` records the same list in machine-readable form, and `versions.sh` is what reconciles the two.

The default branch is `master` and the primary language GitHub reports is Shell rather than Dockerfile, which is accurate: the interesting code in this repository is the shell, not the build instructions.

Why the first start behaves differently from every start after

Two shell scripts at the root do the actual work, and `docker-ensure-initdb.sh` is the more interesting of the pair. Postgres refuses to initialise a data directory that already has content, so an image that blindly ran the init sequence would either fail on the second start or quietly skip it. The ensure script exists to distinguish those two cases by checking whether the data directory has been initialised yet.

That single check is the reason a Postgres container can be run with a plain volume mounted and no setup step. The first start finds an empty directory, runs initdb, and creates the cluster. Every later start finds a populated directory and does nothing, which is correct and also the reason people get confused when they expect their configuration to be reapplied. Changes that initdb applied once do not get reapplied on restart, and understanding that ordering is most of what there is to know about running this image.

`docker-entrypoint.sh` is the outer script that makes those decisions and then hands off to the real postgres binary. It is where argument handling, user switching and the init path live. Neither script is documented in this repository's README, so reading them is the only way to answer questions about the startup contract.

The four scripts that produce a published image

The generation pipeline is short and lives entirely in shell: `versions.sh` reads and validates the version list, `apply-templates.sh` fills the two templates into the version directories, `update.sh` handles fetching upstream information, and `generate-stackbrew-library.sh` produces the separate metadata Homebrew consumes.

bash
sh versions.sh
sh apply-templates.sh

What is absent is as informative as what is present. There is no test directory, no example compose file and no application code, so nothing here demonstrates a running database. Verification happens after generation, when the produced Dockerfiles are built by the official images pipeline, and that pipeline is documented in the change lifecycle FAQ the README links to.

There is also no Docker Hub documentation in this tree, because the README states plainly that the full image description is generated and maintained in the docker-library/docs repository, in the `postgres` directory there. The last line of the README gives the mechanism: the file is produced by `generate-repo-stub-readme.sh`. Anyone who wants to correct a statement about how to use the image is editing the wrong repository if they start here.

Environment variables are the configuration surface

Because the entrypoint script does the initialisation, the configuration that matters is passed as environment variables rather than as a config file you mount. The two you will meet first are the superuser credentials and the name of the database to create alongside the cluster. Setting them on the first run is what stops the image from falling back to defaults you did not choose.

The ordering rule that trips people up is that these values are consumed once, during initdb. Changing `POSTGRES_PASSWORD` after the cluster exists does nothing, because the password is already stored in the cluster's own catalog. That is not a limitation of the image so much as a property of how Postgres works, but the image does nothing to soften the surprise either, since the README does not discuss it.

There is a second axis to configuration and it is the extensions. Postgres extensions have to be installed into the image, which means either deriving your own image with an install step or using a prebuilt variant. Nothing in this repository's tree enumerates what is available, so that question also belongs to the Docker Hub documentation. It is worth knowing before you commit to this image, because an extension you need that has no Alpine build will push you toward the Debian variant or toward your own build.

Picking between Alpine and Debian in practice

The two-template structure is the repository's only real design decision, and it has consequences that show up later. Alpine images are smaller, which matters when you are pulling into a constrained CI environment. Debian images are the ones most Postgres extension authors build against, so binary packages for extensions are likelier to exist for them.

The failure mode when you get this wrong is specific and annoying rather than catastrophic. An extension built for glibc will not load under musl, and the resulting error tends to surface as an obscure failure to load a shared library rather than a clear message about the base image. If you are installing extensions, check which libc your chosen tag uses before debugging anything else.

There is a third consideration that has nothing to do with libc: how closely you want the container to match a managed Postgres service. Managed offerings are Linux and glibc based, so a Debian tag is the closer match for reproducing production behaviour locally. Alpine is the better choice when image size is the binding constraint and you are not relying on native extension binaries.

None of this is in the README, which is three paragraphs of repository pointers. The practical way to answer it is to read the Docker Hub page, then verify by running the tag you intend to use with the extension you actually need.

Which repository answers which question

The README names four places and assigns each a job. This repository is the source of the image. The docker-library/docs repository, in its `postgres` directory, holds the usage documentation that gets published to Docker Hub. The library/postgres label on the official-images repository tracks outstanding pull requests for this image. And the `library/postgres` file in that same repository is described as the current source of truth for the image.

That last distinction is the one worth dwelling on, because it means the version list you care about does not primarily live in `versions.json`. The file in this repository is an input to generation; the file in official-images is what the publishing pipeline consults. When the two disagree, the official-images file is the one that decides what gets built.

The section the README does not fill in is the change lifecycle itself. Its link goes to an FAQ entry titled around an image source changing in Git and what happens next, which is the single most useful link in the file for anyone who has wondered why a merged change takes days to appear. The gap is honest for a generated repository: the processes that run after this one live in other projects.

Licensing is MIT for the packaging, and the Postgres server itself is licensed separately and fetched from upstream at build time, which is the usual arrangement for official images.

Editorial conclusion

docker-library/postgres is a packaging repository, not a tutorial, and the two things it is genuinely useful for are answering which Postgres versions are supported and explaining why the first start is different from every start after it. Version 19 is the newest directory in the tree, with 14 still present, and the last push was on 2026-09-24 on the master branch. Start with docker-entrypoint.sh if you want to understand the initdb behaviour, and go to the Docker Hub page for anything about volume mounts, authentication or tuning, because all of that documentation is generated in docker-library/docs rather than here.

Frequently asked questions

Which Postgres versions does the official Docker image support?

The version directories in the repository run from 14 through 19, so six major releases are packaged at the time of the last push on 2026-09-24. versions.json records the same list in machine-readable form, and the authoritative file is library/postgres in the docker-library/official-images repository.

Should I use the alpine or debian variant of the postgres image?

Alpine is smaller but builds against musl libc, and Debian builds against glibc, which is what most managed Postgres services and most extension authors target. If you install a prebuilt extension binary, the Debian variant is the safer pick. Choosing Alpine makes sense when image size is the binding constraint.

Why does my Postgres container ignore a changed password?

The entrypoint consumes credentials only when the data directory is empty, which is when initdb runs. Once the cluster exists, docker-entrypoint.sh leaves it alone, so a changed environment variable has no effect. The fix is to change the password inside the running cluster or to start again with an empty data directory.

Where are the usage instructions for the official Postgres image?

They live on Docker Hub and are generated from the postgres directory of the docker-library/docs repository, not from this repository. The README here is itself produced by generate-repo-stub-readme.sh, so editing documentation means editing the docs repository.

Official sources

  1. docker-library/postgres on GitHub
  2. Issues
  3. License: MIT
  4. Project website
  5. README
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/docker-library-postgres.svg)](https://hysenlabs.com/projects/docker-library-postgres)