Skip to content
pgAdmin in Docker: Setup, docker-compose & Common Fixes

Click to use (opens in a new tab)

pgAdmin in Docker: Setup, docker-compose & Common Fixes

August 27, 2026 by Chat2DBChat2DB Team

Running pgAdmin in Docker is the cleanest way to get the standard PostgreSQL GUI without installing anything: one container, one browser tab. It is also full of small traps — the localhost that does not mean what you think, settings that vanish with the container, and volume permission errors. This guide gives you working docker run and docker-compose setups, explains how networking between pgAdmin and Postgres containers actually works, and fixes the errors everyone hits.

Quick start with docker run

docker run -d --name pgadmin \
  -p 8080:80 \
  -e PGADMIN_DEFAULT_EMAIL=admin@example.com \
  -e PGADMIN_DEFAULT_PASSWORD=change-me \
  -v pgadmin_data:/var/lib/pgadmin \
  dpage/pgadmin4:latest

Open http://localhost:8080 and log in with that email/password. Three notes on the flags:

  • dpage/pgadmin4 is the official image, maintained by the pgAdmin project.
  • The named volume pgadmin_data is not optional in practice — without it, every registered server, saved password and preference disappears when the container is recreated.
  • The email must look like a valid address or the container exits immediately with 'admin' does not appear to be a valid email address in docker logs pgadmin.

The #1 confusion: connecting to Postgres on "localhost"

You add a server in pgAdmin, enter host localhost, and get connection refused — even though psql -h localhost works fine from your terminal. The reason: inside the pgAdmin container, localhost is the pgAdmin container itself, not your machine.

What to enter as host depends on where PostgreSQL runs:

PostgreSQL locationHost to use in pgAdmin
Another container on the same Docker networkThe container/service name, e.g. db
On your host machine (Mac/Windows)host.docker.internal
On your host machine (Linux)172.17.0.1, or add --add-host=host.docker.internal:host-gateway
Remote server / RDSIts DNS name as usual

For the host-machine case on Linux, PostgreSQL must also listen beyond localhost (listen_addresses = '*' in postgresql.conf plus a pg_hba.conf rule for the Docker subnet).

The proper setup: docker-compose with Postgres

Running both in one compose file puts them on a shared network where service names just work:

# docker-compose.yml
services:
  db:
    image: postgres:18
    environment:
      POSTGRES_USER: app
      POSTGRES_PASSWORD: app-secret
      POSTGRES_DB: app_db
    ports:
      - "5432:5432"          # optional: expose to host tools too
    volumes:
      - pg_data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U app -d app_db"]
      interval: 5s
      retries: 10
 
  pgadmin:
    image: dpage/pgadmin4:9
    environment:
      PGADMIN_DEFAULT_EMAIL: admin@example.com
      PGADMIN_DEFAULT_PASSWORD: change-me
    ports:
      - "8080:80"
    volumes:
      - pgadmin_data:/var/lib/pgadmin
    depends_on:
      db:
        condition: service_healthy
 
volumes:
  pg_data:
  pgadmin_data:
docker compose up -d

In pgAdmin (localhost:8080): Add New Server → Name anything → Connection tab → Host db, Port 5432, Username app, Password app-secret. The service name resolves via Docker's internal DNS. Pinning dpage/pgadmin4:9 (rather than latest) avoids surprise major-version UI changes on docker compose pull.

Preconfigured servers: skip the manual add

For team dev environments, bake the server list into the container so nobody registers servers by hand:

// servers.json
{
  "Servers": {
    "1": {
      "Name": "Local Dev DB",
      "Group": "Dev",
      "Host": "db",
      "Port": 5432,
      "MaintenanceDB": "app_db",
      "Username": "app",
      "SSLMode": "prefer"
    }
  }
}

Mount it read-only:

  pgadmin:
    volumes:
      - pgadmin_data:/var/lib/pgadmin
      - ./servers.json:/pgadmin4/servers.json:ro

Servers appear for every user on first login (passwords are still entered once, then saved). Two related environment variables worth knowing for local-only setups: PGADMIN_CONFIG_SERVER_MODE: "False" and PGADMIN_CONFIG_MASTER_PASSWORD_REQUIRED: "False" run pgAdmin in single-user desktop mode with no login screen at all — never do this on anything reachable from the network.

Common errors and fixes

PermissionError: [Errno 13] Permission denied: '/var/lib/pgadmin/sessions' — you bind-mounted a host directory (-v ./pgadmin:/var/lib/pgadmin) that the container user (uid 5050) cannot write. Fix: sudo chown -R 5050:5050 ./pgadmin, or just use a named volume as in the examples.

connection refused from pgAdmin to db — the containers are not on the same network (compose handles this; mixed docker run setups need docker network create + --network), or Postgres is not ready yet — hence the healthcheck + depends_on.condition above.

password authentication failed for user "postgres" against a Postgres container — the POSTGRES_PASSWORD env only applies on first initialization; if pg_data already existed, the old password still stands. Either use the old one or delete the volume for a fresh init.

Login page loops / CSRF errors behind a reverse proxy — set PGADMIN_CONFIG_PROXY_X_FORWARDED_FOR and friends, or simplest, expose pgAdmin under its own hostname rather than a sub-path.

Uploads/backups vanish — pgAdmin's Storage Manager writes inside /var/lib/pgadmin/storage/<user>/; without the volume, those dump files die with the container.

Is containerized pgAdmin actually the best dev GUI?

pgAdmin is the standard, and the Docker setup above works well. It is also fair to say the workflow has drawbacks: a server-side web app with its own login layer, session timeouts, and a UI that many find heavier than necessary for "browse tables, run queries".

If what you want is a fast client for the Postgres container you just started, a desktop or web client connecting straight to localhost:5432 is less machinery: no second container, no uid-5050 volumes, no login screen. Chat2DB (opens in a new tab) is a good free option here — it connects to the compose file's exposed port directly, handles 25+ database types (so the same tool covers the MySQL and Redis containers in your stack), and adds AI text-to-SQL on top: ask "show the 20 newest orders with their customer names" and it writes the join against your actual schema. There is also a no-install browser version at app.chat2db.ai (opens in a new tab). Many teams run both: pgAdmin in compose for admin tasks, Chat2DB as the daily query tool.

Keeping the image updated

pgAdmin releases monthly, and web-exposed admin tools deserve patching. With a pinned major tag the routine is:

docker compose pull pgadmin
docker compose up -d pgadmin      # recreates only the pgadmin container
docker image prune -f

Settings survive because they live in the pgadmin_data volume, not the container. Check the running version under Help → About, and skim the release notes before a major-version jump (dpage/pgadmin4:9:10) — preference schemas occasionally migrate one way only, so take a quick volume backup first: docker run --rm -v pgadmin_data:/data -v $(pwd):/backup alpine tar czf /backup/pgadmin_data.tgz /data.

Cleanup

docker compose down            # stop, keep data volumes
docker compose down -v         # stop AND delete pg_data + pgadmin_data
docker volume ls | grep pgadmin

down -v is the reset button for a corrupted pgAdmin state (bad saved sessions, forgotten master password) — you lose registered servers, which is exactly why the servers.json mount is worth setting up once.

Summary

The recipe that works: official dpage/pgadmin4 image, a named volume on /var/lib/pgadmin, a valid email for PGADMIN_DEFAULT_EMAIL, and — when Postgres is also containerized — a compose file where pgAdmin reaches it by service name, never localhost. Add servers.json so the server list survives resets and teammates, and remember host.docker.internal for databases running on the host. With those five details handled, pgAdmin in Docker is a set-and-forget part of the dev stack.