Vũ Văn HảiFull-stack · AI-native
GuidesBlog
Discuss a project

© 2026 Vu Van Hai · Written from real deployment experience.

HomeGuidesBlogRSS
  1. Guides
  2. /Database
  3. /Fix Docker containers that cannot reach PostgreSQL on a VPS

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.

Updated: Sep 21, 20266 min read
DockerPostgreSQLNetworkingUFWVPS
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 up for the myapp project. Docker sees that "Room 1" (172.17.x.x) is free and gives it to your project.
  • Later on:
    1. You install another project (for example my-blog). It takes "Room 1" (172.17.x.x).
    2. Then you start the myapp project again (docker-compose up).
    3. 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).

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:

ValueExample
Network Namemyapp_default
IPAddress172.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_default

The 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 reload

5. 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.conf

Add 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-256

If 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.conf

Find 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 postgresql

6. Test again

Once PostgreSQL has restarted, restart the API container:

docker restart myapp

Check the logs to confirm the connection now succeeds:

docker logs myapp --tail 50

7. 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.0 to 172.31.255.255
  • In CIDR notation: 172.16.0.0/12

Once 172.16.0.0/12 is allowed:

IP Docker assignsAllowed?
172.17.0.1Yes (inside the range)
172.21.0.1Yes (inside the range)
172.30.0.1Yes (inside the range)

Step 1: Edit pg_hba.conf

sudo nano /etc/postgresql/16/main/pg_hba.conf

Add 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-256

Step 2: Open the UFW firewall

sudo ufw allow from 172.16.0.0/12 to any port 5432
sudo ufw reload

Step 3: Restart PostgreSQL

sudo systemctl restart postgresql

Step 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 subnetHost in the connection string
172.18.x.x172.18.0.1
172.21.x.x172.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 myapp

Troubleshooting

SymptomFix
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 failsCheck 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 5432UFW has not opened port 5432 for the Docker subnet - redo step 4
The container reports Connection refusedlisten_addresses lists individual IPs and the Docker gateway is missing - see step 5.2
It worked, then broke again after docker-compose down and upDocker assigned a new subnet - apply the 172.16.0.0/12 fix and update the gateway IP in the connection string
PreviousDBeaver tips: bigger toolbar icons and the TimeZone error fixNextTune VPS performance: swap, PostgreSQL and the DB pool

Related articles

  • Connect to PostgreSQL on a VPS through an SSH tunnel

    Use an SSH tunnel so your dev machine can reach PostgreSQL on a VPS without exposing port 5432 to the internet, plus the DATABASE_URL setup for local and production.

    Database

    Database
  • Set up Caddy as a reverse proxy on a VPS with Docker

    Run Caddy as a Dockerized reverse proxy on a fresh VPS: create a shared network, write the Caddyfile and docker-compose file, get HTTPS automatically, then add new domains with a single zero-downtime reload.

    VPS

    VPS
  • Migrate a PostgreSQL database between two VPS with pg_dump

    Move an entire PostgreSQL database (schema and data) from a source VPS to a target VPS through your local machine: dump with pg_dump, restore in a single transaction, verify row counts, then cut over and roll back safely.

    Database

    Database

Written by Vu Van Hai

I'm Hai, a full-stack developer based in Ho Chi Minh City. These guides come from systems I built and run myself. Need to build or untangle something similar? Get in touch.

Discuss a projectMore guides

Spot a mistake or a command that no longer works? Let me know

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