I was running a FastAPI app in Docker with a PostgreSQL container next to it. The app worked. It wrote rows, it read them back, everything behaved.
The database itself, fastapi-db, was one I'd created inside the dockerised PostgreSQL — the container defined in my compose file. My app had created the products table in it and was happily writing rows.
Then I opened pgAdmin to look at that data directly, connected to localhost:5432, and found nothing. No fastapi-db. No products table. Just postgres, template0, template1 — the default databases of an empty install.
So my application was clearly talking to a database. pgAdmin was clearly talking to a database. They weren't the same database.
What I assumed first
My first thought was that pgAdmin wasn't refreshing, or that Docker wasn't persisting anything. Both wrong, and both cost me time.
The thing that actually settled it was running this in pgAdmin's query tool:
SELECT version();
It came back with:
PostgreSQL 18.4 on x86_64-windows
Windows. Not the Linux build that would be running inside a container. pgAdmin was connected to a PostgreSQL I'd installed on Windows months earlier and forgotten about — one that was running as a background service the whole time.
To confirm who owned the port:
netstat -ano | findstr :5432
That returns the PID holding the port, and it belonged to the Windows PostgreSQL service. It had claimed localhost:5432 at boot, long before Docker was involved.
The part I'd misunderstood
Here's what I hadn't grasped: my app and pgAdmin were reaching the database over two completely different routes.
Inside Docker Compose, containers reach each other by service name on the Docker network:
fastapi-container ───► postgres-db:5432
That never touches the host machine's ports at all. It's container-to-container, resolved by Docker's internal DNS. Which is why my app worked perfectly and gave me no hint anything was wrong.
pgAdmin runs on Windows, outside Docker. It can only reach the container through a published port — the host-side mapping in the compose file:
localhost:5432 ───► (published port) ───► container:5432
And localhost:5432 on my machine was already spoken for.
So one path was working fine and the other was quietly landing somewhere else entirely. Same port number in both places, two unrelated destinations.
The fix
Change the host side of the mapping so it doesn't collide:
ports:
- "5433:5432"
The two numbers do different jobs, and this is the bit worth internalising. The left one is the port on your machine. The right one is the port inside the container. Postgres inside the container is still listening on 5432 and doesn't need to change — only the door on the outside moves.
Then a new pgAdmin connection to localhost:5433, and fastapi-db appeared immediately — the database I'd created inside the container, with the products table and the same rows my app had been writing all along.
My FastAPI config didn't change at all. It was still using postgres-db:5432, because it was never going through the host in the first place.
A smaller thing that had also confused me
pgAdmin's sidebar shows a tree called Servers, and I had two entries under it. I assumed that meant two PostgreSQL server processes.
It doesn't. In pgAdmin, a "Server" is a saved connection profile — a stored host and port. Two entries means "I know how to reach two PostgreSQL servers," the same way two contacts in your phone don't mean two phones.
That's why one of mine showed a red X. The server wasn't gone; pgAdmin just wasn't currently connected to it.
What I'd tell past me
If a containerised app can see its database and your GUI client can't, don't start by suspecting your app. Ask which database each one is actually reaching. Two commands answer it:
SELECT version();
netstat -ano | findstr :5432
The first tells you what you're connected to. The second tells you who owns the port.
And the general lesson, which took me longer than it should have: inside Docker and outside Docker are two different networks. Containers reach each other by service name and never touch your host's ports. Everything else — your GUI client, your browser, curl — can only get in through a published port, and that port is competing with every other thing installed on your machine.
Same number, different world.
Top comments (0)