Sitemap
Press enter or click to view image in full size

How to Set Up PostgreSQL 18 and pgAdmin 4 on Windows Using Docker: A Complete Developer’s Guide

--

A step-by-step tutorial for installing, configuring, connecting, and managing PostgreSQL 18 and pgAdmin 4 using Docker Desktop, Docker Compose, and Git Bash on Windows.

Estimated reading time: 15–20 minutes | Level: Beginner to Intermediate

Introduction

Setting up a reliable local database environment is one of the first steps when developing backend applications.

PostgreSQL is a powerful, open-source relational database management system widely used for applications ranging from small projects to enterprise platforms.

pgAdmin 4 is a graphical administration tool that allows developers to manage PostgreSQL databases, execute SQL queries, inspect schemas, and monitor database activity through a web interface.

Traditionally, developers install PostgreSQL and pgAdmin directly on their operating systems. Although this works, managing multiple installations, upgrading versions, and maintaining consistent development environments can become challenging.

Docker offers a cleaner alternative.

By running PostgreSQL and pgAdmin inside Docker containers, we can isolate both applications from Windows, maintain persistent database storage, and reproduce the same environment on another machine.

In this tutorial, we will build a complete local development environment using:

  • PostgreSQL 18
  • pgAdmin 4
  • Docker Desktop for Windows
  • Docker Compose
  • Git Bash
  • Docker-managed persistent volumes

By the end, we will have a working PostgreSQL database accessible through pgAdmin and ready to connect to backend applications such as NestJS, Spring Boot, or Laravel.

1. Understanding the Architecture

Before writing any configuration, let’s understand how our components communicate.

Windows Host
│
├── Docker Desktop (WSL 2)
│ │
│ └── Docker Network: postgres-network
│ │
│ ├── PostgreSQL 18 Container
│ │ ├── Internal Port: 5432
│ │ └── Persistent Data Volume
│ │
│ └── pgAdmin 4 Container
│ ├── Internal Port: 80
│ └── Persistent Config Volume
│
├── Browser
│ └── http://localhost:5050
│
└── Backend Application
└── localhost:5432

There are three important concepts here.

Containers: PostgreSQL and pgAdmin run as separate applications inside isolated Linux containers.

Networking: Both containers communicate through a private Docker network. Docker provides service-name-based DNS resolution, allowing pgAdmin to connect to PostgreSQL using the hostname postgres.

Persistent storage: Database files and pgAdmin configuration are stored in Docker volumes. Removing or recreating a container does not automatically delete these volumes.

We will expose PostgreSQL on port 5432 and pgAdmin on port 5050, bound to the Windows loopback interface for local development.

2. Prerequisites

Before proceeding, ensure the following software and system capabilities are available.

RequirementPurposeWindows 10/11 supported by Docker DesktopHost operating systemDocker DesktopContainer runtime and managementWSL 2Linux environment for containersHardware virtualizationRequired for the Linux container backendGit BashTerminal for executing commands8 GB RAM or moreDocker Desktop system prerequisite

Step 2.1: Install or enable WSL 2

Open PowerShell as Administrator and run:

wsl --install

Restart Windows if prompted.

After restarting, verify WSL:

wsl --version
wsl --status

If the Virtual Machine Platform feature is disabled, enable it:

Enable-WindowsOptionalFeature -Online -FeatureName VirtualMachinePlatform

Restart Windows again after changing the feature.

Make sure hardware virtualization is enabled in BIOS/UEFI if WSL reports that virtualization is unavailable.

Step 2.2: Install Docker Desktop

Download Docker Desktop from the official website:

https://www.docker.com/products/docker-desktop/

Run the installer, select the WSL 2 backend where applicable, and complete the installation.

Launch Docker Desktop and wait until the Docker Engine is running.

Ensure Docker Desktop is using Linux containers, not Windows containers.

Step 2.3: Verify Docker

Open Git Bash and execute:

docker --version
docker compose version
docker info

Then run a simple test:

docker run --rm hello-world

If Docker displays its successful installation message, the container runtime is functioning.

3. Create the Project Directory

We will maintain all database infrastructure configuration in a dedicated project directory.

Open Git Bash:

mkdir -p ~/docker/postgres18
cd ~/docker/postgres18

Create two files:

touch compose.yaml .env

Our initial structure is:

postgres18/
├── compose.yaml
└── .env

The compose.yaml file describes the containers, network, ports, and volumes.

The .env file holds configuration values such as database credentials and pgAdmin login details.

Keeping these separate makes the setup easier to maintain.

4. Configure Environment Variables

Open .env in your preferred editor, or use Git Bash to write the configuration.

cat > .env <<'EOF'
POSTGRES_USER=postgres
POSTGRES_PASSWORD=ReplaceWithStrongDatabasePassword
POSTGRES_DB=app_db
PGADMIN_DEFAULT_EMAIL=admin@example.com
PGADMIN_DEFAULT_PASSWORD=ReplaceWithStrongAdminPassword
EOF

Replace both example passwords with your own strong, distinct passwords before starting the containers.

The variables serve different purposes.

VariableDescriptionPOSTGRES_USERInitial PostgreSQL administrator usernamePOSTGRES_PASSWORDInitial PostgreSQL administrator passwordPOSTGRES_DBDatabase created during first initializationPGADMIN_DEFAULT_EMAILInitial pgAdmin login emailPGADMIN_DEFAULT_PASSWORDInitial pgAdmin login password

Important: PostgreSQL and pgAdmin use separate authentication systems.

The pgAdmin credentials allow you to sign in to the administration interface. PostgreSQL credentials authenticate the actual database connection.

The .env file should never be committed to a public Git repository.

Create a .gitignore file:

cat > .gitignore <<'EOF'
.env
*.backup
*.dump
backups/
EOF

For this local tutorial, Compose environment variables provide a convenient setup. For production deployments, use an appropriate secrets-management mechanism rather than relying on a plaintext .env file.

5. Create the Docker Compose Configuration

Now we will define PostgreSQL 18 and pgAdmin 4 as two services.

Open compose.yaml and insert:

name: postgres18-local
services:
postgres:
image: postgres:18
restart: unless-stopped
environment:
POSTGRES_USER: ${POSTGRES_USER:?Required}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?Required}
POSTGRES_DB: ${POSTGRES_DB:?Required}
ports:
- "127.0.0.1:5432:5432"
volumes:
- postgres-data:/var/lib/postgresql
networks:
- postgres-network
healthcheck:
test:
- CMD-SHELL
- pg_isready -U "$$POSTGRES_USER" -d "$$POSTGRES_DB"
interval: 10s
timeout: 5s
retries: 5
start_period: 20s
pgadmin:
image: dpage/pgadmin4:latest
restart: unless-stopped
environment:
PGADMIN_DEFAULT_EMAIL: ${PGADMIN_DEFAULT_EMAIL:?Required}
PGADMIN_DEFAULT_PASSWORD: ${PGADMIN_DEFAULT_PASSWORD:?Required}
ports:
- "127.0.0.1:5050:80"
volumes:
- pgadmin-data:/var/lib/pgadmin
networks:
- postgres-network
depends_on:
postgres:
condition: service_healthy
volumes:
postgres-data:
pgadmin-data:
networks:
postgres-network:
driver: bridge

This configuration creates two independently managed containers with persistent storage and a shared network.

For reproducible team environments, replace the floating latest pgAdmin tag and PostgreSQL major-version tag with tested, explicit image versions or digests. The tags above are convenient for a first local installation.

Understanding the PostgreSQL configuration

Image

image: postgres:18

This instructs Docker to use the official PostgreSQL 18 image.

Restart policy

restart: unless-stopped

Docker restarts the container after a daemon restart unless it was explicitly stopped.

Port mapping

ports:
- "127.0.0.1:5432:5432"

The first 5432 is the Windows host port, while the second is the container port.

Binding to 127.0.0.1 limits the published port to the local host rather than exposing it on every host network interface.

Persistent storage

volumes:
- postgres-data:/var/lib/postgresql

This deserves special attention.

Important PostgreSQL 18 storage change

PostgreSQL 18 introduced a significant change in the official Docker image’s default data-directory layout.

For PostgreSQL 18, the default data directory is:

/var/lib/postgresql/18/docker

The image’s volume location is:

/var/lib/postgresql

Older tutorials commonly mount PostgreSQL data at:

/var/lib/postgresql/data

That convention applies to PostgreSQL 17 and earlier official images, but it is not the recommended default mount for PostgreSQL 18.

Our configuration uses the PostgreSQL 18 layout correctly.

The named volume retains database files even when the PostgreSQL container is removed and recreated, provided the volume itself is preserved.

Understanding the pgAdmin configuration

ports:
- "127.0.0.1:5050:80"

pgAdmin listens on port 80 inside its container.

We publish it to port 5050 on Windows, allowing access through:

http://localhost:5050

Its persistent volume:

volumes:
- pgadmin-data:/var/lib/pgadmin

preserves pgAdmin’s configuration database, including registered server connections and user settings.

Finally:

depends_on:
postgres:
condition: service_healthy

tells Docker Compose to wait until PostgreSQL reports a healthy status before starting pgAdmin.

This improves startup ordering, although application-level database clients should still handle temporary connection failures.

6. Validate the Configuration

Before creating any containers, validate the Compose file.

Run:

docker compose config --quiet

A successful validation normally produces no output.

To inspect the fully resolved configuration:

docker compose config

Be aware that this command can display substituted passwords. Avoid sharing its output publicly without redacting secrets.

7. Download and Start the Containers

Download the required Docker images:

docker compose pull

Start the services:

docker compose up -d

The -d flag starts the containers in detached mode, allowing Git Bash to remain available.

Docker will create:

  • The PostgreSQL container
  • The pgAdmin container
  • A private bridge network
  • A persistent PostgreSQL volume
  • A persistent pgAdmin volume

Verify their status:

docker compose ps

You should see both services running, with PostgreSQL eventually reporting healthy.

To inspect PostgreSQL startup logs:

docker compose logs postgres

For pgAdmin:

docker compose logs pgadmin

To follow logs continuously:

docker compose logs -f

Press Ctrl+C to stop following the logs. This does not stop the containers.

8. Verify PostgreSQL 18

Before connecting pgAdmin, let’s confirm that PostgreSQL is accepting connections.

Execute:

docker compose exec postgres \
pg_isready -U postgres -d app_db

The expected result is similar to:

/var/run/postgresql:5432 - accepting connections

Now open the PostgreSQL command-line client:

docker compose exec postgres \
psql -U postgres -d app_db

The psql prompt should appear:

app_db=#

Execute:

SELECT version();

The result should identify PostgreSQL 18.

Check the connected database and user:

SELECT current_database(), current_user;

Exit the client:

\q

We have now confirmed that the PostgreSQL server is running and that the initial database exists.

9. Open pgAdmin 4

Open your browser and navigate to:

http://localhost:5050

<text>Log in using the credentials configured in .env:</text>

Email:    admin@example.com
Password: Your pgAdmin password

After successful authentication, pgAdmin displays its administration dashboard. At this stage, PostgreSQL is running, but we have not yet registered it inside pgAdmin.

10. Connect pgAdmin to PostgreSQL

This is one of the most important parts of the setup.

In the pgAdmin navigation panel:

  1. Right-click Servers.
  2. Select Register → Server.
  3. Open the General tab.
  4. Enter Local PostgreSQL 18 in the Name field. In my case I have kept postgres-server. You can put any name there.
  5. Open the Connection tab.

Enter the following connection details:

Get Paudeluddhav Er’s stories in your inbox

Join Medium for free to get updates from this writer.

Host name/addresspostgres

Port5432

Maintenance databasepostgres

Usernamepostgres

PasswordYour POSTGRES_PASSWORD

Save password: Optional

Click Save.

pgAdmin should establish a connection and display the PostgreSQL server in the navigation tree.

Press enter or click to view image in full size
Press enter or click to view image in full size

Why is the hostname postgres instead of localhost?

This is a common source of confusion when working with Docker.

When two containers share a user-defined Docker network, Docker provides DNS resolution using service names.

In our configuration:

services:
postgres:

The service name is postgres.

Therefore, pgAdmin can connect to the PostgreSQL container using:

postgres:5432

However, localhost inside the pgAdmin container refers to the pgAdmin container itself, not Windows and not the PostgreSQL container.

Understanding Docker Hostnames: When to Use postgres vs localhost

One important concept to understand is that localhost always refers to the environment where the database client is running.

For example, when pgAdmin runs inside a Docker container, localhost refers to the pgAdmin container itself - not your Windows computer or the PostgreSQL container.

The correct PostgreSQL hostname therefore depends on where your database client is running.

1. pgAdmin running inside Docker Compose

  • Hostname: postgres
  • Port: 5432

Both containers share the same Docker network. Docker automatically resolves the PostgreSQL service name (postgres) to its internal container address.

2. NestJS or DBeaver running directly on Windows

  • Hostname: localhost
  • Port: 5432

These applications connect through the PostgreSQL port published on your Windows host.

3. Another application container on the same Docker network

  • Hostname: postgres
  • Port: 5432

Containers on the same user-defined Docker network can communicate using service names or configured network aliases.

4. An application container on a different Docker network

The hostname postgres will not automatically resolve across isolated Docker networks.

The containers must share an appropriate Docker network, or the application must use another reachable endpoint.

Key takeaway

Use postgres when connecting from a container on the same Docker network.

Use localhost when connecting from an application running directly on Windows, provided PostgreSQL's port is published to the Windows host.

Remember: Docker container networking and Windows host networking are different environments.

Press enter or click to view image in full size

This distinction is fundamental to understanding Docker networking.

11. Explore the Database in pgAdmin

After registering the server, expand the navigation tree:

Servers
└── Local PostgreSQL 18
└── Databases
├── app_db
│ └── Schemas
│ └── public
│ └── Tables
└── postgres

Select app_db.

Right-click the database and choose Query Tool.

Execute:

SELECT
current_database() AS database_name,
current_user AS connected_user,
version() AS postgres_version;

Click the Execute button or press F5.

The query results should confirm that pgAdmin is successfully connected to PostgreSQL 18.

12. Create and Query Your First Table

Now let’s verify that the database supports ordinary SQL operations.

In pgAdmin’s Query Tool, execute:

CREATE TABLE customers (
id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
full_name VARCHAR(150) NOT NULL,
email VARCHAR(255) NOT NULL UNIQUE,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

This creates a simple customer table.

Insert sample data:

INSERT INTO customers (full_name, email)
VALUES
('Alice Johnson', 'alice@example.com'),
('Bob Smith', 'bob@example.com'),
('Charlie Brown', 'charlie@example.com');

Retrieve the records:

SELECT
id,
full_name,
email,
created_at
FROM customers
ORDER BY id;

The query should return three records.

Refresh the Tables node in pgAdmin to see the newly created table.

You have now successfully created, populated, and queried a PostgreSQL 18 database through pgAdmin.

13. Connect a Backend Application

A major advantage of this setup is that PostgreSQL can be accessed by applications running directly on Windows.

For example, a NestJS application using Prisma can connect with:

DATABASE_URL="postgresql://app_user:YourPassword@localhost:5432/app_db?schema=public"

Here, app_user represents a dedicated application database role that you must create and grant appropriate privileges before using this connection string.

For a quick local connectivity test, you could use the initialized postgres role, but using a separate least-privilege role is preferable for real application development.

If NestJS itself runs inside the same Docker network, the hostname changes from localhost to postgres.

The database port remains 5432.

The key principle is that the hostname depends on the network location of the application connecting to PostgreSQL.

14. Managing the Containers

Docker Compose makes everyday database management straightforward.

ActionCommandStart servicesdocker compose up -dView statusdocker compose psStop servicesdocker compose stopRestart servicesdocker compose restartView logsdocker compose logs -fStop and remove containersdocker compose downList volumesdocker volume ls

Understanding data persistence

Running:

docker compose down

removes the Compose-managed containers and network but preserves named volumes by default.

Your PostgreSQL data should remain available when you subsequently run:

docker compose up -d

However, the following command is destructive:

docker compose down -v

It removes Compose-managed named volumes as well as containers and networks.

Do not use down -v unless you intentionally want to delete the stored database and pgAdmin state.

Persistent volumes protect data from ordinary container recreation, but they are not a substitute for backups.

15. Back Up the PostgreSQL Database

Even in local development, it is useful to understand how PostgreSQL backups work.

Create a backup directory:

mkdir -p backups

Generate a PostgreSQL custom-format database backup:

docker compose exec -T postgres \
pg_dump -U postgres -d app_db -Fc \
> backups/app_db.dump

This writes the database dump to the Windows project directory.

The -T flag disables pseudo-terminal allocation, making the command suitable for redirecting binary output.

Verify that the file exists:

ls -lh backups/

Inspect the backup archive:

docker compose exec -T postgres \
pg_restore -l < backups/app_db.dump

This lists the archive contents without restoring them.

Restoring a backup

For an existing, empty target database, a custom-format dump can be restored using:

docker compose exec -T postgres \
pg_restore -U postgres -d app_db \
< backups/app_db.dump

For a populated database, restoring may produce object-exists errors or conflicts. A safer recovery exercise is to create a separate empty database and restore into it.

For example:

docker compose exec postgres \
createdb -U postgres app_db_restore

Then:

docker compose exec -T postgres \
pg_restore -U postgres -d app_db_restore \
< backups/app_db.dump

Verify the restored database in pgAdmin.

A successful restore test is more meaningful than merely having a backup file.

16. Troubleshooting Common Problems

Problem 1: pgAdmin cannot connect to PostgreSQL

Typical error:

could not translate host name

Check that both services are running:

docker compose ps

Verify that both services use the same Docker network.

If they are running under the Compose configuration from this tutorial, use:

Host: postgres
Port: 5432

Do not use localhost from inside the pgAdmin container.

Problem 2: Connection refused

Possible causes include PostgreSQL still starting, a stopped container, or an incorrect hostname or port.

Check:

docker compose logs postgres

And:

docker compose exec postgres \
pg_isready -U postgres -d app_db

Wait until PostgreSQL is ready before retrying.

Problem 3: Password authentication failed

Typical error:

FATAL: password authentication failed

Check the username and password used when registering the server in pgAdmin.

Remember that changing POSTGRES_PASSWORD in .env after PostgreSQL has initialized its data directory does not automatically change the password stored inside PostgreSQL.

The initialization variables apply when the database cluster is first created.

To change an existing role password, use PostgreSQL’s ALTER ROLE command rather than deleting the volume.

Problem 4: Port 5432 is already in use

You may already have PostgreSQL installed directly on Windows or another container using port 5432.

Change the host port mapping:

ports:
- "127.0.0.1:5433:5432"

Recreate the affected service:

docker compose up -d

Now:

  • Windows applications connect to localhost:5433.
  • pgAdmin inside Docker continues connecting to postgres:5432.

The internal container port has not changed.

Problem 5: pgAdmin port 5050 is unavailable

Change:

ports:
- "127.0.0.1:5051:80"

Then access pgAdmin at:

http://localhost:5051

Problem 6: Docker Desktop reports WSL or virtualization errors

Verify WSL:

wsl --status

Update WSL if necessary:

wsl --update

Ensure Virtual Machine Platform and hardware virtualization are enabled, and restart Windows when required.

17. Recommended Practices for Development

A working setup is only the beginning. A few additional practices make it more reliable and maintainable.

Use persistent volumes. Never rely on a container’s writable filesystem to preserve database data.

Keep credentials outside the Compose configuration. Use .env for local development and protect it from version control. For production, use a proper secret-management mechanism.

Bind development ports to localhost. Avoid exposing PostgreSQL or pgAdmin to your local network or the public internet without a deliberate security design.

Use dedicated database roles. Applications should not normally connect using the PostgreSQL superuser.

Back up databases regularly. Docker volumes preserve data during container recreation but do not protect against every form of data loss.

Pin tested image versions. Floating image tags can introduce unexpected changes when images are pulled again.

Treat major database upgrades separately. Moving from PostgreSQL 18 to a later major version requires a supported upgrade or migration process. Simply changing the image tag is not a database migration.

Separate development and production environments. This Compose configuration is designed for local development, not a production-grade high-availability PostgreSQL deployment.

18. Final Verification Checklist

Before considering the setup complete, confirm the following:

  • Docker Desktop is running with Linux containers.
  • PostgreSQL 18 and pgAdmin containers are running.
  • PostgreSQL reports a healthy status.
  • PostgreSQL uses a persistent named volume mounted at /var/lib/postgresql.
  • pgAdmin opens at http://localhost:5050.
  • pgAdmin connects using hostname postgres.
  • The app_db database is visible.
  • SQL queries execute successfully.
  • A sample table can be created and queried.
  • The database survives a normal docker compose down and up -d cycle.
  • A database backup can be generated and restored successfully.

Conclusion

We have successfully configured PostgreSQL 18 and pgAdmin 4 on Windows using Docker Desktop and Docker Compose.

The resulting environment provides a clean separation between database services and the Windows operating system, persistent database storage, predictable container networking, and a convenient graphical administration interface.

More importantly, we now understand why pgAdmin connects to postgres inside Docker while applications running directly on Windows connect to localhost.

We have also accounted for PostgreSQL 18’s updated data-directory layout, a detail that is easy to overlook when following older Docker tutorials.

This setup provides a practical foundation for developing database-driven applications with frameworks such as NestJS, Spring Boot, and Laravel.

Next step: Integrate PostgreSQL 18 with a NestJS backend using Prisma ORM, database migrations, and dedicated application credentials.

Official Documentation and References

  1. PostgreSQL Official Docker Image
  2. pgAdmin 4 Container Deployment Documentation
  3. Docker Desktop Installation on Windows
  4. Docker Compose Documentation
  5. PostgreSQL 18 Documentation

All commands in this tutorial are intended for local development on Windows using Docker Desktop, with Git Bash as the primary terminal.

--

--