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 stop and docker start
  • Tables, users, and databases created during the session are missing after restart
  • You see FATAL: database "mydb" does not exist after 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

# 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 run when you meant docker start — alias common containers if needed
  • Watch for the VOLUME instruction in Dockerfiles — the official PostgreSQL image already declares /var/lib/postgresql/data as a volume, but you should explicitly map it

References


Advertisement