How I Restored My Entire PostgreSQL Database After Reinstalling Windows

Date: 8/25/2025Section: DevOps

After nuking Windows and reinstalling everything from scratch, I thought I lost all my Docker containers, PostgreSQL data, and volumes.
All I had was a backup of my old Docker folder:

C:\Users\<me>\AppData\Local\Docker

When I restored Docker Desktop, my containers and images were gone — but my data wasn’t lost.

Here’s the complete journey of how I restored everything: Docker volumes, PostgreSQL, user credentials, and database compatibility.


1. Understanding Where Docker Stores Data

Docker Desktop on Windows uses WSL2 internally.
All your images, volumes, and containers live inside a single virtual disk file:

C:\Users\<me>\AppData\Local\Docker\wsl\disk\docker_data.vhdx

This .vhdx file is everything.
If you’ve backed it up, you have all your Docker volumes, including PostgreSQL data.

Lesson learned
Next time, just back up this file — it’s enough.

2. Restoring Docker Data After Reinstalling Windows

Step 1 — Reinstall Docker Desktop

  • Download the same or newer Docker Desktop version.
  • Launch it once so it initializes the WSL distros (docker-desktop and docker-desktop-data).

Step 2 — Replace the VHDX

  1. Quit Docker Desktop completely.
  2. Open PowerShell and stop WSL:
    wsl --shutdown
    
  3. Go to:
    C:\Users\<me>\AppData\Local\Docker\wsl\disk\
    
  4. Rename the new docker_data.vhdxdocker_data.vhdx.bak.
  5. Paste your old backed-up VHDX here.

Step 3 — Restart Docker

Start-Process "C:\Program Files\Docker\Docker\Docker Desktop.exe"

Now, all your volumes are back!
You can confirm with:

docker volume ls

3. Restoring PostgreSQL From a Docker Volume

After restoring, I saw all my volumes:

local   computer-use-agent-backend_data
local   33d42ed1502e9388e...5df5790b25c
local   67c1d2f03871edded...7383e77c83f
local   telegram-bot_bot-data
...

But docker images showed nothing.
That’s normal — images are not auto-restored, but volumes do come back.

So, we just need to run a new Postgres container and point it at the existing volume. But how to know which volume belongs to PostgreSQL?

Step 1 - Locating the PostgreSQL volume

We can look for volume sizes and guess which volume is related to PostgreSQL, in order to see the volume sizes, we need to execute this command:

docker system df -v

From the output, search for volume information, it will look something like this:

Local Volumes space usage:

VOLUME NAME                       LINKS     SIZE
67c1d2f03871eddeda...383e77c83f   0         9240B
33d42ed1502e9388e3...df5790b25c   0         802.5MB
db4de6a5346e99bfc7...efe05b0f23   0         40.39MB
telegram-bot_bot-data             0         78.44MB  
...

We can see that volume starting with 33d42 is bigger in size, so it can be our PostgreSQL volume. By listing volumes with their sizes, we can rule out the small volumes…

Next, we will inspect our volumes one by one and look for PostgreSQL related files in them:

docker run --rm -it -v <volume_name>:/data alpine sh -c "ls -lh /data | head"

We stop when we see this kind of output:

-rw-------    1 999      ping           3 Jan  9  2025 PG_VERSION
drwx------   11 999      ping        4.0K May  1 05:48 base
drwx------    2 999      ping        4.0K Aug 21 08:54 global
drwx------    2 999      ping        4.0K Jan  9  2025 pg_commit_ts
drwx------    2 999      ping        4.0K Jan  9  2025 pg_dynshmem
-rw-------    1 999      ping        5.6K Jan  9  2025 pg_hba.conf
-rw-------    1 999      ping        2.6K Jan  9  2025 pg_ident.conf
drwx------    4 999      ping        4.0K Aug 21 08:59 pg_logical
drwx------    4 999      ping        4.0K Jan  9  2025 pg_multixact

We save the volume name to session variable $VOLUME for reuse:

$VOLUME = "33d42ed1502e9388e335005311308097787d8595f54c8784ba7c75df5790b25c"

Step 2 — Check Your PostgreSQL Version

First, find out which major version of Postgres your volume used:

docker run --rm -v ${VOLUME}:/var/lib/postgresql/data alpine sh -c "cat /var/lib/postgresql/data/PG_VERSION"

For me, it returned 16, so I needed the postgres:16 image.


Step 3 — Pull the Matching Postgres Image

docker pull postgres:16

Step 4 — Run Postgres Using the Existing Volume

docker run -d `
  --name "pg-restored" `
  -e POSTGRES_PASSWORD=changeme `
  -v ${VOLUME}:/var/lib/postgresql/data `
  -p 5432:5432 `
  postgres:16

Step 5 — Verify

docker logs -f pg-restored

Look for:

database system is ready to accept connections

Your Postgres DBs are now back ✅.


5. Extra Tips & Troubleshooting

Fixing Collation Version Mismatch

After starting Postgres, I saw this warning:

WARNING: database "postgres" has a collation version mismatch
DETAIL: The database was created using collation version 2.36,
but the operating system provides version 2.41.

This happens because the glibc version changed between systems.

In order to fix it we need to execute these commands on affected DBs:

REINDEX (VERBOSE) DATABASE <db-name>;
ALTER DATABASE <db-name> REFRESH COLLATION VERSION;

I had multiple DBs affected, so I wrote PowerShell loop to fix them all:

# Set container name
$C = 'pg-restored'

# Set the target version to ignore DBs in that version
$target = '2.41'; 

# List DBs whose datcollversion differs (and isn’t NULL)
$DBS = (docker exec -i $C psql -U postgres -t -A -c "SELECT datname FROM pg_database WHERE datcollversion IS NOT NULL AND datcollversion <> '$target';") -split "`n" | ForEach-Object { $_.Trim() } | Where-Object { $_ }

foreach ($db in $DBS) {
  Write-Host "== Reindexing and refreshing $db =="
  docker exec -it $C psql -U postgres -d $db -c "REINDEX (VERBOSE) DATABASE $db;"
  docker exec -it $C psql -U postgres -c "ALTER DATABASE $db REFRESH COLLATION VERSION;"
}

# (Optionally you can try refreshing template0, although it may fail)
# Write-Host "== Refreshing template0 =="
# docker exec -it $C psql -U postgres -c "ALTER DATABASE template0 REFRESH COLLATION VERSION;"

# Verify the refresh
docker exec -it $C psql -U postgres -c "SELECT datname, datcollate, datctype, datcollversion FROM pg_database ORDER BY 1;"

Resetting Forgotten PostgreSQL Passwords

I couldn’t remember my DB passwords. Postgres doesn’t store plaintext passwords, so the only solution is to reset them.

Step 1 — List Users

docker exec -it pg-restored psql -U postgres -c "\du+"

Step 2 — Reset a User’s Password

docker exec -it pg-restored psql -U postgres -c "ALTER ROLE <user-name> WITH PASSWORD 'NewStrongPassword!';"

If You’re Locked Out

If postgres requires a password you don’t know, log in as the container’s OS-level postgres user:

docker exec -it -u postgres pg-restored psql

From there, you can reset any password.


Changing Database Owners

To change the owner of a single DB:

docker exec -it pg-restored psql -U postgres -c "ALTER DATABASE <db-name> OWNER TO <user-name>;"

Generating PostgreSQL Connection URLs

Standard format:

postgresql://<user>:<password>@<host>:<port>/<database>

Example:

postgresql://postgres%27:1234@localhost:5432/test

Here, the username was postgres' — we URL-encoded '%27.


Final Lessons Learned

  1. Back up docker_data.vhdx — it contains all your Docker volumes.
  2. Postgres data lives entirely inside the volume — images are replaceable.
  3. Always match the original Postgres version (PG_VERSION).
  4. If you get collation mismatch warnings, reindex + refresh.
  5. If you forget passwords, reset them — they’re not recoverable.
  6. Recreate connection URLs properly — encode special characters.

Final Words

This whole process taught me one thing: Docker volumes are king.
Even if your images, containers, and Windows installation are gone, as long as you have your docker_data.vhdx backup, your PostgreSQL databases are safe.