Chapter 01 · PostgreSQL Foundations, Release Cadence, Installation, and Lab Design
Install PostgreSQL on Windows, Linux, Containers, and Initialize a Cluster
Install and verify a supported PostgreSQL 18.x environment on Windows, Linux, or Docker, and understand exactly when initdb creates a new database cluster.
Learning outcomes
A successful installer dialog is not proof that PostgreSQL is usable. ServiceHub needs a reproducible local database endpoint whose server version, data directory, port, authentication, and lifecycle can be observed. This lesson therefore treats installation as a chain of verifiable states: install binaries, initialize a database cluster where the packaging model requires it, start the server, connect with a client, and prove what actually started.
The mandatory course baseline is a current patched PostgreSQL
18.x release. PostgreSQL 18 introduced an operational detail
that matters to new Docker labs: the official image uses a
version-specific PGDATA and its persistent-volume
target changed for 18 and later. Following a PostgreSQL 17-era
container recipe without noticing that change can produce a
misleading persistence setup.
Distinguish installing PostgreSQL binaries from initializing and starting a PostgreSQL database cluster.
Use official installation paths on Windows and Linux and verify the resulting server instead of trusting package completion.
Create a disposable PostgreSQL 18 Docker lab with the correct PostgreSQL 18+ persistent-volume boundary.
Use initdb, pg_ctl,
pg_isready, and psql appropriately
when building a manual local cluster.
Diagnose common startup failures such as wrong ports, incompatible data directories, authentication assumptions, or stale containers.
Every cluster in this lesson is disposable. Do not point
initdb, pg_ctl, container volume
commands, or cleanup commands at an existing production data
directory. Record the exact path/volume and port before
running anything destructive.
1. Four different states: installed, initialized, running, reachable
These states are often collapsed into the sentence “Postgres is installed,” but troubleshooting is easier when you separate them.
| State | Question | Evidence |
|---|---|---|
| Binaries installed |
Are tools such as postgres,
psql, and perhaps
initdb available?
|
postgres --version,
psql --version, package inventory
|
| Cluster initialized |
Has initdb created PostgreSQL’s
data-directory structure and initial databases?
|
Known PGDATA, PG_VERSION, valid
configuration/control files
|
| Server running | Is a PostgreSQL server process managing that cluster? |
Service/container status, pg_ctl status, logs
|
| Reachable/authenticated | Can the intended client reach the intended endpoint and authenticate? |
pg_isready plus a successful
psql query
|
pg_isready tells you whether a PostgreSQL server is
accepting or rejecting connections at an endpoint. It does not
prove your intended role can log in or access your objects. A
real psql connection plus an identity query closes
that gap.
2. Choose a port that makes the lab unmistakable
PostgreSQL conventionally listens on TCP port 5432, but the course uses host port 55432 for disposable labs. That reduces accidental collision with an existing local installation and makes screenshots or logs easier to interpret. It does not make the database secure; it is merely a lab-naming convention.
SELECT current_database() AS database_name, current_user AS role_name, inet_server_addr() AS server_address, inet_server_port() AS server_port, pg_backend_pid() AS backend_pid;SHOW data_directory;SHOW server_version;SHOW data_checksums;
On local Unix-domain socket connections,
inet_server_addr() and
inet_server_port() can be null because TCP is not
being used. The course will often specify
-h 127.0.0.1 when the transport itself matters.
3. Windows: use the supported installer, then verify
The PostgreSQL project’s Windows download page directs users to an interactive installer certified by EDB for supported PostgreSQL versions. It can install the PostgreSQL server and common tooling. The exact wizard screens and installation directory can change, so this lesson does not freeze screenshots or assume one drive letter.
- Open the official PostgreSQL Windows download page and choose a supported PostgreSQL 18 installer appropriate to the system.
- Use a disposable lab password for the bootstrap database superuser; do not reuse a personal or production secret.
- Choose the default port only if it is free, or choose 55432 for this course lab.
- Complete installation and note the service name and data-directory path presented by the installer.
-
Open a new PowerShell session so updated PATH settings are
visible, or use the explicit PostgreSQL
bindirectory if the tools are not on PATH.
psql --versionpostgres --versionGet-Service *postgres* | Format-Table Name, Status, StartTypeGet-Process postgres -ErrorAction SilentlyContinue | Select-Object Id, ProcessName
Package-managed Windows installations commonly initialize and
register a server service during installation, so running
initdb a second time is usually unnecessary. If you
deliberately want a second manual cluster, use a new empty
directory and another port; never initialize over the
installer-managed data directory.
psql -h 127.0.0.1 -p 55432 -U postgres -d postgres -W
Inside psql, run \conninfo,
SELECT version();, and
SHOW data_directory;. If your installer used 5432
instead, substitute the port you actually chose.
4. Linux: package source and service ownership matter
PostgreSQL provides distribution-specific download guidance for Debian, Ubuntu, Red Hat/Rocky/AlmaLinux, SUSE, and other Linux systems. Some operating-system repositories ship a supported PostgreSQL version; the PostgreSQL Global Development Group (PGDG) repositories provide versioned packages for many distributions. Do not paste an Ubuntu command into a Rocky Linux host simply because both are “Linux.”
On Debian/Ubuntu with the official PGDG repository already configured, package names typically include the major version:
sudo apt updatesudo apt install postgresql-18 postgresql-client-18psql --versionsystemctl status postgresql --no-pager
On RPM-family systems, package names, module handling, initialization commands, and service-unit names differ by repository and distribution. Follow the PostgreSQL download selector for the exact supported procedure rather than treating the APT example as portable.
PostgreSQL server files should be owned by the dedicated
operating-system account used to run the server. On Unix-like
systems, initdb must not be run as root.
“Database superuser” and “operating-system root” are different
identities with different risk boundaries.
5. Manual initialization: what initdb actually creates
initdb creates a new PostgreSQL database cluster in
an empty directory. It establishes cluster-wide configuration,
initial system catalogs, template databases, and the bootstrap
database superuser. PostgreSQL 18 enables data page checksums by
default when initdb creates a cluster. A later
lesson explains what checksums protect; for now, simply verify
their state rather than assuming an old default.
The following is an educational manual-cluster path for a disposable Unix-like environment where PostgreSQL 18 binaries are already installed. Use a non-root account and an empty directory:
export PGDATA="$HOME/bda-pg18-data"mkdir -p "$PGDATA"initdb -D "$PGDATA" -W --encoding=UTF8 \ --auth-local=scram-sha-256 \ --auth-host=scram-sha-256pg_ctl -D "$PGDATA" -l "$PGDATA/server.log" \ -o "-p 55432" startpg_ctl -D "$PGDATA" statuspg_isready -h 127.0.0.1 -p 55432
-W prompts for the bootstrap superuser password
during initialization. That is preferable to embedding a real
secret in a command or script. The --auth-* options
make authentication assumptions explicit. This is a teaching
cluster; production authentication and TLS design come later.
On Windows, the same utilities exist, but PowerShell environment-variable syntax and filesystem permissions differ:
$env:PGDATA = "$HOME\bda-pg18-data"New-Item -ItemType Directory -Force $env:PGDATA | Out-Nullinitdb -D $env:PGDATA -W --encoding=UTF8 --auth-local=scram-sha-256 --auth-host=scram-sha-256pg_ctl -D $env:PGDATA -l "$env:PGDATA\server.log" -o "-p 55432" startpg_ctl -D $env:PGDATA statuspg_isready -h 127.0.0.1 -p 55432
If a package installer already manages a cluster as a service,
prefer that service’s documented management workflow. Manual
pg_ctl examples are not an instruction to bypass
systemd or Windows service management for production.
6. Docker: PostgreSQL 18 changed the persistent-volume boundary
The Docker Official Image for PostgreSQL 18 uses a
version-specific default PGDATA of
/var/lib/postgresql/18/docker. For PostgreSQL 18
and later, the image’s declared persistent volume is
/var/lib/postgresql. This differs from many
PostgreSQL 17-and-earlier examples that mount
/var/lib/postgresql/data.
First set a disposable POSTGRES_PASSWORD value in
the current shell using your operating system’s secure-input or
local secret mechanism; do not type the value into the Docker
command itself. Then create a named volume and bind the server
only to the loopback interface on the host. Docker’s
-e POSTGRES_PASSWORD form forwards the already-set
host environment variable:
docker volume create bda_pg18_datadocker run --name bda-pg18 \ -e POSTGRES_PASSWORD \ -p 127.0.0.1:55432:5432 \ -v bda_pg18_data:/var/lib/postgresql \ -d postgres:18docker logs bda-pg18docker exec bda-pg18 psql -U postgres -d postgres -c "SELECT version();"
PowerShell can run the same Docker arguments on one line or use PowerShell’s continuation character. The backslash formatting above is shell-oriented presentation, not a requirement of Docker itself.
POSTGRES_PASSWORD is convenient for a disposable
local lab but environment variables and command history are
not a production secret-management design. Later security
lessons use stronger secret and authentication patterns. Never
paste a real credential into a tutorial command.
The tag postgres:18 intentionally follows the
maintained 18 major line. After pulling/recreating the
container, always query SELECT version() to record
the exact minor build used. If a regulated or fully reproducible
pipeline requires bit-for-bit pinning, use an approved immutable
image digest and a patch-management process rather than assuming
a floating major tag never changes.
7. What the official container entrypoint does
On first start with an empty persistent volume, the image
entrypoint initializes PostgreSQL and uses environment variables
such as POSTGRES_PASSWORD,
POSTGRES_USER, and POSTGRES_DB to
configure the initial cluster/database. On later starts with an
already initialized volume, initialization scripts are not
replayed as though the volume were empty.
This is a common source of confusion: changing
POSTGRES_PASSWORD and restarting an existing
container does not mean the already-created database role is
automatically rewritten to match the new environment variable.
Initialization-time variables are not a declarative
configuration management system for an existing database.
docker exec bda-pg18 psql -U postgres -d postgres \ -c "CREATE TABLE IF NOT EXISTS public.persistence_probe(id integer primary key); INSERT INTO public.persistence_probe VALUES (1) ON CONFLICT DO NOTHING;"docker rm -f bda-pg18# Re-create with the SAME named volume and the same intended lab credentials.docker run --name bda-pg18 \ -e POSTGRES_PASSWORD \ -p 127.0.0.1:55432:5432 \ -v bda_pg18_data:/var/lib/postgresql \ -d postgres:18docker exec bda-pg18 psql -U postgres -d postgres \ -c "TABLE public.persistence_probe; DROP TABLE public.persistence_probe;"
The expected row proves that the database state lives in the named volume, not in the container’s ephemeral writable layer. It does not prove that you have a backup. A volume can be deleted, corrupted, or lost with its host; later chapters require independent backup and restore tests.
8. Verify the initialized cluster, not just the package
After any installation path, connect and collect a minimum evidence bundle:
SELECT version() AS server_build, current_database() AS database_name, current_user AS role_name, pg_backend_pid() AS backend_pid;SHOW server_version;SHOW data_directory;SHOW port;SHOW data_checksums;SHOW server_encoding;SELECT datname, datallowconnFROM pg_catalog.pg_databaseORDER BY datname;
Then compare with client-side evidence:
psql --versionpg_isready -h 127.0.0.1 -p 55432
A good installation record says which PostgreSQL server build answered, which data directory it manages, which port was reached, which role/database accepted the session, and which client version issued the query.
9. Failure case 1: the port is already occupied
If another service already owns host port 55432, the new PostgreSQL server or Docker publish step cannot bind it. The wrong response is to keep retrying random startup commands. First identify the conflict, then either stop the disposable conflicting service or choose another documented lab port.
Get-NetTCPConnection -LocalPort 55432 -ErrorAction SilentlyContinue | Select-Object LocalAddress, LocalPort, State, OwningProcess
ss -ltnp | grep ':55432 ' || true
After choosing a new host port, update both the container/service configuration and your connection command. A successful connection to 5432 does not prove you reached the newly created lab if an older server is already listening there.
10. Failure case 2: “database files are incompatible with server”
A PostgreSQL major server refuses a data directory initialized
by an incompatible major version. This is a protective failure.
If you see an incompatibility message after changing server
binaries, stop and identify the source and target majors. Do not
delete PG_VERSION, edit it by hand, or copy random
catalog files to make the check disappear.
The repair for an intended major change is an actual supported upgrade path, covered in Chapter 23. The repair for an accidental mismatch is to start the original compatible server binary against that data directory or restore/rebuild the disposable environment correctly.
11. Failure case 3: readiness is not authentication
A learner may run pg_isready, receive “accepting
connections,” and conclude the credentials are correct. That
conclusion is too strong. Readiness checks the server’s
connection-acceptance state; authentication and authorization
happen after that.
pg_isready -h 127.0.0.1 -p 55432psql -h 127.0.0.1 -p 55432 -U postgres -d postgres -W
Inside the authenticated session use \conninfo and
the identity query. This progression—endpoint → authentication →
database/role identity—will become the course’s standard
diagnostic pattern.
12. Hands-on lab: choose one primary installation path
You do not need three simultaneous installations. Choose the path that best fits your machine. Docker is the easiest cross-platform disposable baseline if Docker is already available; a native Windows or Linux installation is equally valid.
Acceptance procedure
- Install a supported PostgreSQL 18.x server/client using the official platform guidance or create the Docker lab above.
- Use host port 55432 unless another disposable local service already uses it.
-
Connect to database
postgresas the local bootstrap administrator. - Run the SQL acceptance checks and record exact output.
- Open a second session and verify that it receives a different backend PID.
- Locate the PostgreSQL logs using the package/container’s documented path and identify one startup line.
- Do not delete the data directory/volume until Lesson 5’s reusable lab has been created.
Verification checklist
psql --versionidentifies the client.-
SELECT version()identifies the connected PostgreSQL 18.x server. -
SHOW data_directorypoints to the intended disposable cluster. -
SHOW portand the client endpoint match your design. -
SHOW data_checksumsrecords checksum state instead of assuming a legacy default. - The server/container restarts cleanly and the same cluster remains reachable.
Check your understanding
- Why are “binaries installed” and “database cluster initialized” different states?
- What does
initdbcreate? - Why does the PostgreSQL 18 Docker volume target differ from many PostgreSQL 17 tutorials?
-
Does
pg_isreadyprove your database role can authenticate? -
Why should an incompatible data-directory error never be
“fixed” by editing
PG_VERSION?
Review the answers
Installing binaries places programs on the system;
initdb creates the actual cluster data
directory, catalogs, templates, and configuration.
PostgreSQL 18 changed the official Docker image to a
version-specific PGDATA and a
/var/lib/postgresql volume boundary.
pg_isready tests server readiness, not
successful role authentication. Finally, major-version
compatibility is a real storage-format boundary; editing
identity files hides evidence without converting the data
and risks loss.
13. Cleanup and rollback
For a manual disposable cluster, stop it cleanly before removing the directory:
pg_ctl -D "$PGDATA" stop -m fast# Verify the exact path before any later rm -rf operation.
For Docker, keep the named volume through Chapter 01. When you eventually decide to destroy the lab, treat volume deletion as destructive data loss:
docker rm -f bda-pg18# DESTRUCTIVE: run only when the lab no longer matters.docker volume rm bda_pg18_data
Never make “delete the volume and start over” your production troubleshooting pattern. Disposable labs allow that convenience precisely so later chapters can teach recovery without risking valuable data.
14. Summary and bridge to psql
A PostgreSQL installation is complete only when the intended
binaries, initialized cluster, running server, endpoint,
authentication path, and server identity have all been verified.
Windows installers, Linux packages, manual
initdb/pg_ctl workflows, and Docker
all lead to the same conceptual outcome but have different
lifecycle owners.
The next lesson focuses on the client side. You will use
psql and libpq connection parameters deliberately,
remove secrets from command history, define service/password
files, verify the target server before changing anything, and
make scripts stop on errors instead of continuing silently.