Fix: PostgreSQL Data Not Persisting After Docker Container Restart
Problem
You restart your PostgreSQL Docker container and all your data is gone:
docker run -d --name my-postgres -e POSTGRES_PASSWORD=secret postgres:16
# Create tables, insert data...
docker stop my-postgres
docker start my-postgres
# Tables are gone. Database is empty.
Symptoms
- Data exists while the container runs but vanishes after
docker stopanddocker start - Tables, users, and databases created during the session are missing after restart
- You see
FATAL: database "mydb" does not existafter a restart
Root Cause
The PostgreSQL container stores data inside the container filesystem at /var/lib/postgresql/data. Without a volume, this data lives in the container’s writable layer — which persists across docker stop/start but is destroyed on docker rm or when Docker prunes containers.
Even with docker stop/start, if you accidentally run docker run again instead of docker start, you create a new container with a fresh, empty data directory.
Solution
Option 1: Named volume (recommended)
# Create the volume
docker volume create pgdata
# Run PostgreSQL with the volume attached
docker run -d \
--name my-postgres \
-e POSTGRES_PASSWORD=secret \
-v pgdata:/var/lib/postgresql/data \
postgres:16
Named volumes are managed by Docker and work across all platforms without path issues.
Option 2: Bind mount
mkdir -p ~/postgres-data
docker run -d \
--name my-postgres \
-e POSTGRES_PASSWORD=secret \
-v ~/postgres-data:/var/lib/postgresql/data \
postgres:16
Bind mounts map a host directory into the container. Useful when you want direct filesystem access to the data.
docker-compose equivalent
version: '3.8'
services:
db:
image: postgres:16
environment:
POSTGRES_PASSWORD: secret
volumes:
- pgdata:/var/lib/postgresql/data
volumes:
pgdata:
Recover data from an old container
If your container still exists (check with docker ps -a), you can copy the data out:
docker cp my-postgres:/var/lib/postgresql/data ./recovered-data
Then mount ./recovered-data as a volume for the new container.
Verification
# Create container with volume
docker run -d --name test-pg -e POSTGRES_PASSWORD=test -v pgtest:/var/lib/postgresql/data postgres:16
# Create test data
docker exec test-pg psql -U postgres -c "CREATE DATABASE verify_db;"
docker exec test-pg psql -U postgres -c "\l" | grep verify_db
# Stop, remove, and recreate
docker stop test-pg
docker rm test-pg
docker run -d --name test-pg -e POSTGRES_PASSWORD=test -v pgtest:/var/lib/postgresql/data postgres:16
# Verify data survived
docker exec test-pg psql -U postgres -c "\l" | grep verify_db
# verify_db | postgres | ...
Prevention
- Always use volumes for stateful services like databases
- Prefer named volumes over bind mounts for portability
- Include volume config in docker-compose.yml and commit it
- Don’t accidentally
docker runwhen you meantdocker start— alias common containers if needed - Watch for the
VOLUMEinstruction in Dockerfiles — the official PostgreSQL image already declares/var/lib/postgresql/dataas a volume, but you should explicitly map it