- Start PostgreSQL in Docker with
docker run --name pg -e POSTGRES_PASSWORD=... -p 5432:5432 -v pgdata:/var/lib/postgresql -d postgres:18;POSTGRES_PASSWORDis the only required variable. - From PostgreSQL 18 the image keeps data in
/var/lib/postgresql/18/dockerand 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.dscripts only take effect when the data directory is empty; an existing volume is left untouched. - In Docker Compose, add a
pg_isreadyhealthcheck anddepends_on: condition: service_healthyso pgAdmin or your app waits until Postgres accepts connections. - Back up with
docker execandpg_dump; the dump is portable to any PostgreSQL server of the same or a newer version.
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:
docker run --name pg -e POSTGRES_PASSWORD=<your-password> -p 5432:5432 -v pgdata:/var/lib/postgresql -d postgres:18docker exec -it pg psql -U postgres| Variable | Required | What it does |
|---|---|---|
POSTGRES_PASSWORD | Yes (unless auth is trust) | Sets the superuser password. Must not be empty. |
POSTGRES_USER | No | Creates this superuser and a database of the same name; defaults to postgres. |
POSTGRES_DB | No | Name of the default database; defaults to the value of POSTGRES_USER. |
POSTGRES_INITDB_ARGS | No | Extra arguments for initdb, for example --data-checksums. |
POSTGRES_HOST_AUTH_METHOD | No | Auth method for host connections; scram-sha-256 by default from PostgreSQL 14. The image documentation advises against trust. |
PGDATA | No | Data directory; /var/lib/postgresql/18/docker for PostgreSQL 18. |
*_FILE variants | No | Read 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.
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:POSTGRES_PASSWORD=<your-password>
PGADMIN_PASSWORD=<your-pgadmin-password>Start the stack and register the server in pgAdmin
- Save both files in one folder and run
docker compose up -d. Compose reads the.envfile in the project directory; the${VAR:?error}form stops with an error if a password is missing. - Run
docker compose psand wait untildbreports healthy. Compose only startspgadminafter the healthcheck passes. - Open
http://localhost:5050and sign in withPGADMIN_DEFAULT_EMAILandPGADMIN_DEFAULT_PASSWORD. WithoutPGADMIN_ENABLE_TLS, the container listens on port 80 in plain text, which is why the file maps host port 5050 to 80. - Register a server with host name
db(the Compose service name), port5432, userappand your password. Inside a Compose network each service is reachable by its service name;localhostwould 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.
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:
docker exec pg pg_dump -U postgres -Fc -f /tmp/mydb.dump mydb
docker cp pg:/tmp/mydb.dump ./mydb.dumpdocker 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.dumpVersion 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.
| Symptom | Likely cause | Fix |
|---|---|---|
| Data is gone after re-creating the container | Volume mounted at the wrong path for the version, so data went to an anonymous volume | PostgreSQL 18+: mount /var/lib/postgresql. 17 and earlier: mount /var/lib/postgresql/data. |
A new POSTGRES_PASSWORD or POSTGRES_DB has no effect | The variables only apply to an empty data directory | Change the password with SQL in the running server, or start from an empty volume. |
| Init scripts did not run | The volume already contained a database, or an earlier script failed | Check docker logs, then re-create the volume if you can discard the data. |
App or pgAdmin fails to connect right after up | Postgres is still initialising and not accepting connections | Add the pg_isready healthcheck and condition: service_healthy. |
could not resize shared memory segment ... No space left on device | The default /dev/shm for containers is 64 MB | Set shm_size in Compose or --shm-size=256MB on docker run. |
Container exits at start with an error about POSTGRES_PASSWORD | The variable is empty or missing | Set it (or a POSTGRES_PASSWORD_FILE); only trust auth skips it, which is not recommended. |
| Port 5432 is already in use | A local PostgreSQL service or another container holds the port | Map 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
- postgres Docker Official Image documentation (docker-library/docs)
- PostgreSQL versioning policy
- PostgreSQL home page (current releases)
- pgAdmin 4 documentation: Container Deployment
- Docker docs: Control startup order in Compose
- Docker docs: Volumes
- Docker docs: Compose version and name elements
- Docker docs: Networking in Compose
- Docker docs: docker compose down
- Docker docs: docker container cp
- Docker docs: Install Docker Desktop on Windows
- PostgreSQL 18 documentation: pg_isready
- PostgreSQL 18 documentation: pg_dump
Checked 8 October 2026.
How we research guides: our editorial method. We link only to official downloads and never host installers.