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.

Intermediate95–125 minutesInstallation + disposable cluster labPostgreSQL 18.xWindows · Linux · DockerLast reviewed: August 2026

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.

01

Distinguish installing PostgreSQL binaries from initializing and starting a PostgreSQL database cluster.

02

Use official installation paths on Windows and Linux and verify the resulting server instead of trusting package completion.

03

Create a disposable PostgreSQL 18 Docker lab with the correct PostgreSQL 18+ persistent-volume boundary.

04

Use initdb, pg_ctl, pg_isready, and psql appropriately when building a manual local cluster.

05

Diagnose common startup failures such as wrong ports, incompatible data directories, authentication assumptions, or stale containers.

Safety boundary

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.

sql · verify the endpoint after connecting
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.

  1. Open the official PostgreSQL Windows download page and choose a supported PostgreSQL 18 installer appropriate to the system.
  2. Use a disposable lab password for the bootstrap database superuser; do not reuse a personal or production secret.
  3. Choose the default port only if it is free, or choose 55432 for this course lab.
  4. Complete installation and note the service name and data-directory path presented by the installer.
  5. Open a new PowerShell session so updated PATH settings are visible, or use the explicit PostgreSQL bin directory if the tools are not on PATH.
PowerShell · verify Windows binaries and service
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.

PowerShell · verify a connection
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:

bash · example after the official PGDG APT repository is configured
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.

Operating-system account boundary

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:

bash · initialize a disposable manual cluster
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:

PowerShell · manual disposable cluster concept
$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:

terminal · cross-platform Docker lab
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.

Secret handling

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.

terminal · prove persistence across container recreation
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:

sql · installation acceptance checks
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:

terminal · client acceptance checks
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.

PowerShell · inspect a Windows TCP port
Get-NetTCPConnection -LocalPort 55432 -ErrorAction SilentlyContinue |  Select-Object LocalAddress, LocalPort, State, OwningProcess
bash · inspect a Linux TCP port
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.

terminal · endpoint readiness then authenticated proof
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

  1. Install a supported PostgreSQL 18.x server/client using the official platform guidance or create the Docker lab above.
  2. Use host port 55432 unless another disposable local service already uses it.
  3. Connect to database postgres as the local bootstrap administrator.
  4. Run the SQL acceptance checks and record exact output.
  5. Open a second session and verify that it receives a different backend PID.
  6. Locate the PostgreSQL logs using the package/container’s documented path and identify one startup line.
  7. Do not delete the data directory/volume until Lesson 5’s reusable lab has been created.

Verification checklist

  • psql --version identifies the client.
  • SELECT version() identifies the connected PostgreSQL 18.x server.
  • SHOW data_directory points to the intended disposable cluster.
  • SHOW port and the client endpoint match your design.
  • SHOW data_checksums records checksum state instead of assuming a legacy default.
  • The server/container restarts cleanly and the same cluster remains reachable.

Check your understanding

  1. Why are “binaries installed” and “database cluster initialized” different states?
  2. What does initdb create?
  3. Why does the PostgreSQL 18 Docker volume target differ from many PostgreSQL 17 tutorials?
  4. Does pg_isready prove your database role can authenticate?
  5. 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:

bash · clean shutdown of a manual lab
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:

terminal · destructive Docker cleanup, only after verification
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.

Authoritative references

Keep knowledge open

Help the academy stay free and grow.

If these tutorials save you time, a small donation supports new lessons, technical review, diagrams, examples, and long-term maintenance.

ETHEthereum / ERC-20 only
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0

Send only assets compatible with the Ethereum/ERC-20 network. Do not send TRC-20/TRON assets.