Why PostgreSQL Docker Data Disappears After Container Recreation
Keep PostgreSQL Docker data across container recreation with the correct volume path for versions 17 and 18+, and connect applications by container name.

If your PostgreSQL database appears empty after you recreate its Docker container, check where the named volume was mounted. For the official image, mount it at /var/lib/postgresql/data on PostgreSQL 17 and below, but at /var/lib/postgresql on PostgreSQL 18 and above. Recreate the container with the same named volume. A volume attached at the wrong path can leave the database files somewhere other than the storage you intended to keep.
Choose the mount path for the image version
The official image documentation makes this a major-version distinction, not a matter of preference. On PostgreSQL 17 and below, the image declares /var/lib/postgresql/data as its volume. If you mount your named volume only at /var/lib/postgresql, Docker can create an anonymous volume at the actual data path. Recreating the container will not automatically reuse that anonymous volume.
For PostgreSQL 17 and below, use:
docker volume create pgdata
docker run -d --name pgdb \
-v pgdata:/var/lib/postgresql/data \
postgres:17
For PostgreSQL 18 and above, the image uses a major-version-specific PGDATA directory. For PostgreSQL 18, that directory is /var/lib/postgresql/18/docker, and the image declares its volume at the parent path. Use:
docker volume create pgdata
docker run -d --name pgdb \
-v pgdata:/var/lib/postgresql \
postgres:18
These two commands illustrate the mount paths; they omit the initialization credentials needed for the runnable configuration below. Choose one version and layout for a volume, rather than running both examples against pgdata. In particular, changing an existing PostgreSQL 17 container to the PostgreSQL 18 mount path does not move its files or upgrade its database.
Keep the volume and connect over a Docker network
Here is one internal-only configuration for postgres:18. Before running it, provide a password file at $PWD/postgres_password and restrict access to that file as appropriate for your host. The example supplies its path, not a literal password. The image documents file-based initialization values for POSTGRES_PASSWORD, POSTGRES_USER, POSTGRES_DB, and POSTGRES_INITDB_ARGS.
docker volume create pgdata
docker network create app_db
docker run -d --name pgdb --network app_db \
-e POSTGRES_PASSWORD_FILE=/run/secrets/postgres_password \
-v "$PWD/postgres_password:/run/secrets/postgres_password:ro" \
-v pgdata:/var/lib/postgresql \
postgres:18
For postgres:17, change the image tag to postgres:17 and the named-volume destination to /var/lib/postgresql/data. Keep the same destination when you later recreate that container.
There is no -p option here. The database port is not published to the host; containers attached to app_db can use pgdb as the database host. A user-defined bridge network provides name resolution even when a recreated container receives a different IP address. Publish a port separately only if a client on the host needs one.
The image entrypoint creates the default PostgreSQL user and database with initdb. Do not make an application depend on the service merely because its container has started: wait for initialization to finish, then establish the application connection. The image documentation also advises against trust authentication because it allows connections without a password.
Check persistence before depending on it
Use a disposable database for this check. After initialization completes, use a SQL client already available in an application container attached to app_db. Connect to host pgdb, database postgres, as user postgres, using the initialization password. This connection does not require a published host port.
In that SQL client, write a recognizable row:
CREATE TABLE public.container_persistence_probe (
id integer PRIMARY KEY,
note text NOT NULL
);
INSERT INTO public.container_persistence_probe (id, note)
VALUES (1, 'written before recreation');
Close the client connection, stop and remove the container, and rerun the matching docker run command above with the same pgdata volume and mount destination:
docker stop pgdb
docker rm pgdb
After the recreated container finishes starting, reconnect through pgdb and check:
SELECT id, note FROM public.container_persistence_probe WHERE id = 1;
The row should still be available if the same database files were reused. Inspect the named volume with docker volume inspect pgdata and check the container’s mounts to confirm that pgdata is attached at the path for its image version. Check the container’s published ports as well: this internal-only example should have no host port mapping. Do not remove the named volume when the data must remain; Docker-managed named volumes persist independently of containers.
The PostgreSQL 18+ parent mount can make a future pg_upgrade --link operation easier, according to the Docker PostgreSQL guide. It does not perform an upgrade. Plan any migration of existing files as a separate operation, especially if they currently use the PostgreSQL 17-and-below directory layout.
