Skip to content
Guide · Run databases in Docker

PostgreSQL in Docker: Run Postgres with Docker & Docker Compose

The official postgres image on Docker Hub is the quickest way to get a local PostgreSQL server: one docker run command, or a short compose.yaml that adds pgAdmin. This guide explains the documented options for PostgreSQL 18, including the new data directory layout, persistent volumes, initialisation scripts, connecting from the host, taking a backup out of the container, and the errors people most often hit.

Steps checked 8 October 2026 against the official documentation for each product. Versions covered: PostgreSQL 18.6 (postgres:18 image; 19 Beta 4 available as 19beta4), pgAdmin 4 9.18 (dpage/pgadmin4), Docker Compose Specification. Next review due April 2027. Installers, versions and download pages change; follow the official page if a step differs.
Short answer
  • Start PostgreSQL in Docker with docker run --name pg -e POSTGRES_PASSWORD=... -p 5432:5432 -v pgdata:/var/lib/postgresql -d postgres:18; POSTGRES_PASSWORD is the only required variable.
  • From PostgreSQL 18 the image keeps data in /var/lib/postgresql/18/docker and declares the volume at /var/lib/postgresql, so mount there. For 17 and earlier, mount /var/lib/postgresql/data.
  • Environment variables and /docker-entrypoint-initdb.d scripts only take effect when the data directory is empty; an existing volume is left untouched.
  • In Docker Compose, add a pg_isready healthcheck and depends_on: condition: service_healthy so pgAdmin or your app waits until Postgres accepts connections.
  • Back up with docker exec and pg_dump; the dump is portable to any PostgreSQL server of the same or a newer version.
How we know: Research-based: image tags, environment variables, paths and examples were checked against the official postgres image documentation, the PostgreSQL 18 manual, the pgAdmin 4 container documentation and Docker's documentation on 8 October 2026. We have not run these commands for this guide; if your image version differs, follow the official page.

Run PostgreSQL in Docker with docker run

Running PostgreSQL in Docker needs only Docker itself. On Windows 11 that is Docker Desktop, whose default backend is WSL 2; on macOS it is Docker Desktop; on Linux, Docker Engine. The postgres image is a Docker Official Image, so it is pulled by its short name. On 8 October 2026 the 18 and latest tags point to PostgreSQL 18.6, and PostgreSQL 14 to 17 have their own major-version tags. PostgreSQL 19 is in beta (tag 19beta4); the PostgreSQL project does not advise running beta versions in production.

Pin a major version tag such as postgres:18 rather than latest, so a future major release does not start against data files it cannot read. The command below starts a server, publishes port 5432 to the host and keeps the data in a named volume:

Start PostgreSQL 18 with a named volume (bash, PowerShell or cmd)
docker run --name pg -e POSTGRES_PASSWORD=<your-password> -p 5432:5432 -v pgdata:/var/lib/postgresql -d postgres:18
Open psql inside the running container
docker exec -it pg psql -U postgres
Environment variables documented for the postgres image
VariableRequiredWhat it does
POSTGRES_PASSWORDYes (unless auth is trust)Sets the superuser password. Must not be empty.
POSTGRES_USERNoCreates this superuser and a database of the same name; defaults to postgres.
POSTGRES_DBNoName of the default database; defaults to the value of POSTGRES_USER.
POSTGRES_INITDB_ARGSNoExtra arguments for initdb, for example --data-checksums.
POSTGRES_HOST_AUTH_METHODNoAuth method for host connections; scram-sha-256 by default from PostgreSQL 14. The image documentation advises against trust.
PGDATANoData directory; /var/lib/postgresql/18/docker for PostgreSQL 18.
*_FILE variantsNoRead POSTGRES_PASSWORD, POSTGRES_USER, POSTGRES_DB or POSTGRES_INITDB_ARGS from a file, for Docker secrets.

Docker mount for Postgres data: volumes and the PostgreSQL 18 change

A container's writable layer is deleted with the container, so the database files must live in a volume or a bind mount. Docker describes volumes as the preferred mechanism for persisting container data, and a named volume such as pgdata survives docker rm. Note that docker compose down -v removes named volumes declared in the Compose file, which deletes the database.

The image changed its layout in PostgreSQL 18. PGDATA is now version specific (/var/lib/postgresql/18/docker) and the declared VOLUME is /var/lib/postgresql. Mount your volume at /var/lib/postgresql for 18 and later; this lets a future major upgrade use pg_upgrade --link across the two version folders. For PostgreSQL 17 and earlier the image documentation is explicit: mount at /var/lib/postgresql/data, because a mount at /var/lib/postgresql does not persist data on those versions. Data is then written to an anonymous volume that is not reused when the container is re-created.

A bind mount (-v /my/own/datadir:/var/lib/postgresql) puts the files in a known folder on the host, at the cost of managing directory permissions yourself. The image runs as the postgres system user, and running it as another UID needs the extra steps in the image's "Arbitrary --user Notes".

Docker Compose file for PostgreSQL and pgAdmin

For a local development stack, a compose.yaml is easier to keep than a long docker run line. The file below follows the current Compose Specification: there is no top-level version: key, which Docker documents as obsolete. It combines the image's own example (including shm_size), Docker's documented pg_isready healthcheck for postgres:18, and the pgAdmin container's required variables.

compose.yaml: PostgreSQL 18 with pgAdmin 4
services:
  db:
    image: postgres:18
    restart: unless-stopped
    shm_size: 128mb
    environment:
      POSTGRES_USER: app
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD in .env}
      POSTGRES_DB: mydb
    ports:
      - "5432:5432"
    volumes:
      - pgdata:/var/lib/postgresql
      - ./initdb:/docker-entrypoint-initdb.d:ro
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}"]
      interval: 10s
      retries: 5
      start_period: 30s
      timeout: 10s

  pgadmin:
    image: dpage/pgadmin4:latest
    restart: unless-stopped
    environment:
      PGADMIN_DEFAULT_EMAIL: you@example.com
      PGADMIN_DEFAULT_PASSWORD: ${PGADMIN_PASSWORD:?set PGADMIN_PASSWORD in .env}
    ports:
      - "5050:80"
    volumes:
      - pgadmin:/var/lib/pgadmin
    depends_on:
      db:
        condition: service_healthy

volumes:
  pgdata:
  pgadmin:
.env in the same folder (keep it out of version control)
POSTGRES_PASSWORD=<your-password>
PGADMIN_PASSWORD=<your-pgadmin-password>

Start the stack and register the server in pgAdmin

  1. Save both files in one folder and run docker compose up -d. Compose reads the .env file in the project directory; the ${VAR:?error} form stops with an error if a password is missing.
  2. Run docker compose ps and wait until db reports healthy. Compose only starts pgadmin after the healthcheck passes.
  3. Open http://localhost:5050 and sign in with PGADMIN_DEFAULT_EMAIL and PGADMIN_DEFAULT_PASSWORD. Without PGADMIN_ENABLE_TLS, the container listens on port 80 in plain text, which is why the file maps host port 5050 to 80.
  4. Register a server with host name db (the Compose service name), port 5432, user app and your password. Inside a Compose network each service is reachable by its service name; localhost would point at the pgAdmin container itself.

The pgAdmin documentation says the container runs as UID 5050, mounts /var/lib/pgadmin for its settings, and includes pg_dump, pg_dumpall, pg_restore and psql for several PostgreSQL versions, so backups and restores can be run from pgAdmin as well as from the command line. To pre-load server definitions, mount a servers.json file at /pgadmin4/servers.json.

Initialisation scripts in /docker-entrypoint-initdb.d

To create tables or load sample data on first start, put *.sql, *.sql.gz or *.sh files in a folder mounted at /docker-entrypoint-initdb.d (the ./initdb folder in the Compose file above). After initdb creates the default user and database, the entrypoint runs .sql files, runs executable .sh files and sources non-executable ones, in sorted name order. SQL files are executed as POSTGRES_USER.

Two documented catches: the scripts run only when the container starts with an empty data directory, and if a script fails, the entrypoint exits and a restart with the now-initialised directory will not run the remaining scripts. To re-run them, remove the volume (losing its data) and start again. During initialisation the temporary server listens only on the Unix socket, so psql calls in .sh scripts should not pass a host name.

initdb/01-schema.sql (PostgreSQL)
CREATE TABLE customers (
    customer_id  integer GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    name         text NOT NULL,
    created_at   timestamptz NOT NULL DEFAULT now()
);

INSERT INTO customers (name) VALUES ('Ada'), ('Grace');

Connecting from the host: docker postgres localhost and server settings

With -p 5432:5432, tools on the host connect to localhost port 5432 with the user and password you set, for example from pgAdmin desktop or DBeaver. Host connections use scram-sha-256 password authentication by default. Inside the container, Unix-socket connections use trust authentication, which is why docker exec ... psql does not ask for a password.

To publish the port only on your own machine, prefix the host address: -p 127.0.0.1:5432:5432. If a locally installed PostgreSQL already uses 5432, map a different host port (-p 5433:5432) and connect to 5433.

Server settings can be passed after the image name, because the entrypoint forwards them to the postgres server, for example postgres:18 -c shared_buffers=256MB -c max_connections=200. For a full configuration file, mount it and add -c config_file=/etc/postgresql/postgresql.conf; the image documentation notes that such a file must set listen_addresses = '*' so other containers can connect.

Backing up a Postgres container with pg_dump

A named volume is not a backup. The portable way to back up one database is a logical dump made with pg_dump, which is already inside the image. Writing the archive inside the container and copying it out with docker cp avoids shell redirection problems with binary output on Windows:

Custom-format dump of mydb, copied to the host
docker exec pg pg_dump -U postgres -Fc -f /tmp/mydb.dump mydb
docker cp pg:/tmp/mydb.dump ./mydb.dump
Restore into a fresh database in the same container
docker cp ./mydb.dump pg:/tmp/mydb.dump
docker exec pg createdb -U postgres -T template0 mydb_restored
docker exec pg pg_restore -U postgres -d mydb_restored /tmp/mydb.dump

Version rule for dumps

pg_dump refuses to dump a server with a newer major version than its own, and its output is only guaranteed to load into the same or a newer major version. Running the tool inside the server's own container keeps the versions matched. Formats, parallel restores and pg_dumpall are covered in the pg_dump and pg_restore guide listed under related pages.

Common PostgreSQL Docker errors and fixes

Most problems with the postgres image come from its first-start behaviour. These are the cases the image documentation describes, with the documented fix.

Symptoms and documented causes
SymptomLikely causeFix
Data is gone after re-creating the containerVolume mounted at the wrong path for the version, so data went to an anonymous volumePostgreSQL 18+: mount /var/lib/postgresql. 17 and earlier: mount /var/lib/postgresql/data.
A new POSTGRES_PASSWORD or POSTGRES_DB has no effectThe variables only apply to an empty data directoryChange the password with SQL in the running server, or start from an empty volume.
Init scripts did not runThe volume already contained a database, or an earlier script failedCheck docker logs, then re-create the volume if you can discard the data.
App or pgAdmin fails to connect right after upPostgres is still initialising and not accepting connectionsAdd the pg_isready healthcheck and condition: service_healthy.
could not resize shared memory segment ... No space left on deviceThe default /dev/shm for containers is 64 MBSet shm_size in Compose or --shm-size=256MB on docker run.
Container exits at start with an error about POSTGRES_PASSWORDThe variable is empty or missingSet it (or a POSTGRES_PASSWORD_FILE); only trust auth skips it, which is not recommended.
Port 5432 is already in useA local PostgreSQL service or another container holds the portMap another host port, for example 5433:5432.

Frequently asked questions

Which postgres image on Docker Hub should I use?

Use the Docker Official Image postgres with a major version tag, for example postgres:18. Debian-based tags (trixie, bookworm) are the default; -alpine tags are smaller, but extensions not in postgres-contrib must be compiled into your own image.

Where does the postgres container store its data?

In PostgreSQL 18 and later, in /var/lib/postgresql/18/docker inside the volume mounted at /var/lib/postgresql. In 17 and earlier, in /var/lib/postgresql/data. With a named volume, Docker stores the files in its own storage area on the host.

How do I change the Postgres password after the first start?

Changing POSTGRES_PASSWORD does nothing once the data directory exists. Connect with docker exec -it pg psql -U postgres and change the role's password with SQL, then update the value in your .env file so new environments match.

Can I run PostgreSQL 19 in Docker?

A beta image is published (tag 19beta4 on 8 October 2026). Use it to try new features against a copy of your data; the PostgreSQL project does not advise running beta versions in production.

How do I upgrade a Postgres container to a new major version?

A new major version cannot simply start on old data files. The usual routes are a dump with pg_dump or pg_dumpall restored into a container of the new version, or pg_upgrade; the PostgreSQL 18 image layout was changed so that pg_upgrade --link can work across version folders in one volume.

Sources

Checked 8 October 2026.

How we research guides: our editorial method. We link only to official downloads and never host installers.

Database installed?

Write your first queries with the free beginner course, then practise on real problems.