Skip to main content
RunBook Academy

PostgreSQLII · Installation, Packaging and Service ManagementInstallation

PostgreSQL in a container image

Intermediate⏱ ~25 minDockerpsql

What you'll learn

  • Describe what the official image entrypoint does and, critically, when it does nothing
  • Explain why the image is invisible to the Debian cluster tooling it contains
  • Choose a stop signal and grace period that produce a clean shutdown
  • Distinguish what containerisation solves for a database from what it does not

Prerequisites

Verified against PostgreSQL 18.x · PostgreSQL (comparison targets) 17.11, 16.15 · PostgreSQL (support calendar) 18, 17, 16, 15, 14 supported · pgBackRest 2.59.1 · PgBouncer 1.25.2 · Patroni 4.1.5 · Ubuntu (host baseline) 26.04 LTS · 2026-08-27

Not yet marked complete on this device.

Every lab in this course runs PostgreSQL in a container, because a disposable database you can destroy and recreate is exactly what practice requires. That convenience comes with a set of behaviours that differ from a package installation in ways worth knowing explicitly, particularly if a containerised database ever becomes something other than a lab.

This lesson is about the image’s behaviour. Whether a production database belongs in a container is a separate question, and Part XVIII answers it properly.

What the entrypoint does, and when it does nothing

The image’s entrypoint script runs before PostgreSQL starts, and its first decision is the one that surprises people: it checks whether the data directory is already initialised, and if it is, it skips almost everything.

On a genuinely empty data directory it runs initdb, applies POSTGRES_PASSWORD, POSTGRES_USER and POSTGRES_DB, starts a temporary server on the Unix socket only, runs any scripts found in /docker-entrypoint-initdb.d/, shuts that temporary server down, and then starts the real one.

On a data directory that already contains a cluster, it does none of that. It starts the server.

The image is invisible to its own cluster tooling

The official image is built from the Debian packages, so the Debian cluster wrappers are installed. It does not use them.

Read-only / Safepg_lsclusters inside the running official image
$ docker exec rbpg-multi pg_lsclusters
Ver Cluster Port Status Owner Data directory Log file

The wrappers enumerate clusters by reading /etc/postgresql/<version>/<name>/, and the image keeps its configuration inside the data directory in the Red Hat style instead. There is nothing registered for them to find.

Two consequences. First, any automation that discovers PostgreSQL by running pg_lsclusters reports zero clusters against a container, and zero is indistinguishable from “not installed” in most such scripts. Second, pg_ctlcluster, pg_createcluster and pg_upgradecluster are all present and none of them will manage the running cluster, so a runbook step that uses them silently addresses nothing.

Creating a second cluster inside the image with pg_createcluster does work and does register, which produces a container where one cluster is visible to the tooling and the one actually serving traffic is not.

PID 1, and who chooses your shutdown mode

In the image, PostgreSQL is PID 1. There is no systemd, no init system, and no supervisor. The container runtime signals the postmaster directly, and which signal it sends decides the shutdown mode from the previous lesson.

docker stop sends SIGTERM, waits for a grace period — ten seconds by default — and then sends SIGKILL.

SIGTERM to a PostgreSQL postmaster is smart shutdown: refuse new connections and wait, indefinitely, for existing clients to disconnect voluntarily. On a container with an application connection pool attached, they will not.

So the default sequence is: request the slowest possible shutdown, wait ten seconds, then kill it outright. The result is a crash recovery on the next start, every time, and nobody chose it.

Volumes are the data directory

# A named volume: survives container removal, which is the point.
docker run -d --name pg -v pgdata:/var/lib/postgresql/data postgres:18

# A bind mount: the data directory is a host path you can see
docker run -d --name pg -v /srv/pgdata:/var/lib/postgresql/data postgres:18

# Nothing: an anonymous volume you will lose track of and eventually prune
docker run -d --name pg postgres:18

The third form is what every quick example uses, including the labs in this course, and it is correct for a disposable database. An anonymous volume persists after docker rm, but nothing names it, so it is invisible in practice and docker volume prune removes it. For a database holding anything you would miss, that is the wrong default.

Note also that the PostgreSQL 18 image sets PGDATA to a subdirectory, /var/lib/postgresql/18/docker, rather than to the mount point itself. The entrypoint handles this, but a bind mount whose contents you are inspecting from the host will have the cluster one level down from where you expect.

What a container solves, and what it does not

Solved. Reproducible binaries and version pinning. Fast disposal and recreation. Isolation of the software from the host’s package management. Trivially running several versions side by side, which makes upgrade rehearsal genuinely easy.

Not solved. Durability, which is entirely a property of the volume and the storage beneath it. Backup, which is exactly as much work as before. High availability, which needs everything Part XV describes and gains nothing from the packaging. Connection management, capacity planning, vacuum behaviour, replication — the whole remainder of this course is unchanged.

The failure mode to name explicitly is the assumption that because the container can be recreated in seconds, the database can be. Recreating the container gives you a running PostgreSQL. It gives you your data only if the volume survived, and it gives you a correct database only if the volume was not the thing that failed.

Production discipline

  1. Treat POSTGRES_PASSWORD as an initialisation input, not desired state. Rotate credentials with ALTER ROLE; the variable is ignored on every start after the first.
  2. Never delete a volume to make an environment variable apply. That is the action that turns a configuration confusion into data loss, and the container comes back healthy afterwards.
  3. Set --stop-signal=SIGINT and a realistic --stop-timeout. The default combination requests a smart shutdown that cannot complete and then kills it, producing crash recovery on every stop.
  4. Use named volumes for anything you would miss. Anonymous volumes survive docker rm and are removed by docker volume prune, which is a routine command.
  5. Do not discover containerised PostgreSQL with pg_lsclusters. It reports nothing, and nothing is indistinguishable from absent.

Cross-course references

  • Docker & Containers — Part VIII (Storage) covers volume lifetime and bind-mount behaviour, and Part VI (Container lifecycle) covers stop signals and grace periods in detail.
  • Kubernetes for Production Sysadmins — Part LIV (Stateful workloads) and Part X (Termination) cover the cluster-scheduler equivalents of everything in this lesson.
  • Secrets, PKI & Certificate Management — Part XIV (Platform integration) covers why a password in an environment variable is visible in more places than expected.

Quiz

Knowledge check · 6 questions

  1. Q1. An operator updates POSTGRES_PASSWORD in a compose file and recreates the container against its existing named volume. The container starts successfully. What has happened to the superuser password?

  2. Q2. Why does docker stop against a default PostgreSQL container reliably produce a crash recovery on the next start?

  3. Q3. Which of these does running PostgreSQL in a container genuinely solve? Select all that apply.

  4. Q4. During initialisation the official image starts a temporary server that listens only on the Unix socket, so it is unreachable from the network.

  5. Q5. Running pg_lsclusters inside the official image lists the cluster that is currently serving connections.

  6. Q6. Reconstruct what happened and identify the decision that caused the loss.

    A small team runs a containerised PostgreSQL for an internal tool, started originally with docker run and no volume flag. After a security review they were asked to rotate the superuser password. An engineer changed POSTGRES_PASSWORD, recreated the container, and found the old password still worked. Concluding the volume was holding stale state, they ran docker volume prune and recreated the container again. The new password now works. The tool reports that all its data is missing. There are no backups because the database was considered non-critical.

Passing score: 75%. Answers are checked in this browser.