Fix Docker containers that cannot reach PostgreSQL on a VPS
Docker picks a new network range every time you run docker-compose down and up, so PostgreSQL on the host rejects the container. Diagnose the subnet, open UFW and pg_hba.conf, then allow the whole 172.16.0.0/12 range to fix it once.
On this page
- 1. Why does Docker change the IP?
- 2. Diagnose: find the current IP and network name
- 3. Inspect the subnet and gateway
- 4. Open the firewall (UFW)
- 5. Update the PostgreSQL config
- 5.1. Edit pg_hba.conf
- 5.2. Check postgresql.conf
- 5.3. Restart PostgreSQL
- 6. Test again
- 7. The silver bullet fix (recommended)
- The theory
- Step 1: Edit pg_hba.conf
- Step 2: Open the UFW firewall
- Step 3: Restart PostgreSQL
- Step 4: Update the connection string in your code
- Quick summary
- Troubleshooting
A container that worked fine suddenly cannot connect to the PostgreSQL installed on the
host? The usual cause: Docker reallocates its network IP range (dynamic allocation)
whenever you run docker-compose down and up again, and PostgreSQL refuses the
connection because the new IP is not on its allow list. This guide covers the
diagnosis, the quick fix for the current subnet, and a one-time fix that covers every
subnet. It applies to PostgreSQL 14/16 on an Ubuntu/Debian VPS running Docker.
Throughout, myapp is the example container name and myapp_default the example Docker
network name - replace them with your own. Paths assume PostgreSQL 16; change 16 to
match your version.
1. Why does Docker change the IP?
Think of Docker managing networks like a hotel assigning rooms:
- First run: you run
docker-compose upfor themyappproject. Docker sees that "Room 1" (172.17.x.x) is free and gives it to your project. - Later on:
- You install another project (for example
my-blog). It takes "Room 1" (172.17.x.x). - Then you start the
myappproject again (docker-compose up). - Docker sees "Room 1" is occupied, so it automatically moves the project to
"Room 2" (
172.18.x.x) or "Room 3" (172.19.x.x).
- You install another project (for example
The result: you had configured PostgreSQL to accept only "Room 1" (172.17.0.1).
Now that the project lives in "Room 2" (172.18.0.1), PostgreSQL sees an unknown IP and
blocks it immediately - and the connection error appears.
2. Diagnose: find the current IP and network name
Run this to see which IP the container received and which network it belongs to
(replace myapp with your container name):
# With jq installed (nicer output)
docker inspect -f '{{json .NetworkSettings.Networks}}' myapp | jq .
# Without jq (use grep)
docker inspect myapp | grep -A 20 "Networks"You are looking for two values:
| Value | Example |
|---|---|
| Network Name | myapp_default |
| IPAddress | 172.19.0.2 |
3. Inspect the subnet and gateway
With the network name from step 2, run this to see how that network is laid out:
# Replace the network name with the one you found in step 2
docker network inspect myapp_defaultThe output includes a Config section like this:
"Config": [
{
"Subnet": "172.18.0.0/16",
"Gateway": "172.18.0.1"
}
]The Gateway is the IP address the container uses to reach the outside world, including
PostgreSQL on the host.
4. Open the firewall (UFW)
Run this on the VPS so UFW lets the Docker subnet through to the PostgreSQL port (5432):
# Replace 172.21.0.0/16 with the subnet you found in step 3
sudo ufw allow from 172.21.0.0/16 to any port 5432
sudo ufw reload5. Update the PostgreSQL config
5.1. Edit pg_hba.conf
# Locate the config file (usually /etc/postgresql/14/main/ or 16/main/)
sudo nano /etc/postgresql/16/main/pg_hba.confAdd this line at the end of the file (adjust the subnet to yours):
# Allow Docker containers to connect to PostgreSQL
host all all 172.21.0.0/16 scram-sha-256If your server authenticates with md5, change scram-sha-256 to md5. The error log
tells you which method is in use.
5.2. Check postgresql.conf
sudo nano /etc/postgresql/16/main/postgresql.confFind the listen_addresses line:
- If it is already
listen_addresses = '*', there is nothing to change. - If it lists individual IPs (the approach in
Set up PostgreSQL on a VPS securely), you must
add the Docker gateway to the list (for example
,172.21.0.1).
5.3. Restart PostgreSQL
sudo systemctl restart postgresql6. Test again
Once PostgreSQL has restarted, restart the API container:
docker restart myappCheck the logs to confirm the connection now succeeds:
docker logs myapp --tail 507. The silver bullet fix (recommended)
The catch with the fix above: every docker-compose down and up may move Docker to a
different range (for example 172.22.x.x), and you would have to repeat all the steps.
The better fix: grant access to the entire IP range Docker can use, once and for all.
The theory
Under RFC 1918, Docker is allowed to use the private class B IP range:
- From
172.16.0.0to172.31.255.255 - In CIDR notation:
172.16.0.0/12
Once 172.16.0.0/12 is allowed:
| IP Docker assigns | Allowed? |
|---|---|
172.17.0.1 | Yes (inside the range) |
172.21.0.1 | Yes (inside the range) |
172.30.0.1 | Yes (inside the range) |
Step 1: Edit pg_hba.conf
sudo nano /etc/postgresql/16/main/pg_hba.confAdd this line (or replace the old one):
# Silver bullet: allow any IP in the Docker range (172.16.x.x -> 172.31.x.x)
host all all 172.16.0.0/12 scram-sha-256Step 2: Open the UFW firewall
sudo ufw allow from 172.16.0.0/12 to any port 5432
sudo ufw reloadStep 3: Restart PostgreSQL
sudo systemctl restart postgresqlStep 4: Update the connection string in your code
The connection string still has to point at the gateway IP of the current Docker
network. The gateway is always .1 of the subnet:
| Docker subnet | Host in the connection string |
|---|---|
172.18.x.x | 172.18.0.1 |
172.21.x.x | 172.21.0.1 |
To never touch the connection string again, you can use host.docker.internal (on
Mac/Windows) or set extra_hosts in docker-compose.yml to map a fixed name to the
gateway. On a Linux VPS, though, using the gateway IP directly is the simplest and most
effective option.
Quick summary
# 1. Find the container's network name and IP
docker inspect myapp | grep -A 20 "Networks"
# 2. Inspect the subnet and gateway
docker network inspect <network-name>
# 3. Open the firewall (silver bullet - do it once)
sudo ufw allow from 172.16.0.0/12 to any port 5432
sudo ufw reload
# 4. Edit pg_hba.conf - add this line:
# host all all 172.16.0.0/12 scram-sha-256
# 5. Restart PostgreSQL
sudo systemctl restart postgresql
# 6. Restart the container
docker restart myappTroubleshooting
| Symptom | Fix |
|---|---|
Log shows no pg_hba.conf entry for host "172.x.x.x" | The container's subnet is missing from pg_hba.conf - add a rule for it (or for 172.16.0.0/12), then restart PostgreSQL |
| The rule matches the subnet but authentication still fails | Check the auth method in the error log: change scram-sha-256 to md5 if the server uses md5 |
| The container times out connecting to port 5432 | UFW has not opened port 5432 for the Docker subnet - redo step 4 |
The container reports Connection refused | listen_addresses lists individual IPs and the Docker gateway is missing - see step 5.2 |
It worked, then broke again after docker-compose down and up | Docker assigned a new subnet - apply the 172.16.0.0/12 fix and update the gateway IP in the connection string |