Postgres in Docker: connect via Unix sockets (Docker Desktop friendly)

Run Postgres in Docker and connect to it via a Unix domain socket from another container (macOS + Docker Desktop friendly).

~2 min read

Unix sockets let containers on the same Compose stack talk to Postgres without opening a TCP port.

On macOS, Docker Desktop runs Docker inside a Linux VM. Your host is not that VM. So:

  • container → container via socket: works.
  • host → container via socket: the socket file lives in the VM. File sharing does not turn it into a usable socket on the host.
  • host → container via TCP: publish a port.

This post covers the first case: containers sharing a socket directory.

Why sockets here

For Postgres internal to a Compose stack:

  • No need to publish 5432 to the host.
  • No networking setup (host.docker.internal, listen addresses, interfaces).
  • Fits sidecars: migrations, admin scripts, benchmarks, test runners.

Tradeoffs:

  • Host tools on Docker Desktop still need TCP.
  • Containers are coupled by a path (volume + mountpoint) instead of a hostname.
  • Postgres treats local (socket) and host (TCP) differently in pg_hba.conf.
  • Performance inside a single Docker VM is not the point. Reduced surface area is.

Sockets inside the stack, TCP outside.

How Postgres exposes the socket

Postgres creates a socket file named .s.PGSQL.5432 inside the directory configured by unix_socket_directories.

Clients point host at the directory, not the file.

Docker Compose: Postgres + psql over a shared socket volume

yaml
services:
  postgres:
    image: postgres:17-alpine
    environment:
      POSTGRES_USER: app
      POSTGRES_PASSWORD: app
      POSTGRES_DB: app
    command:
      - postgres
      - -c
      - unix_socket_directories=/var/run/postgresql
    volumes:
      - pg-data:/var/lib/postgresql/data
      - pg-socket:/var/run/postgresql
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U app -d app -h /var/run/postgresql"]
      interval: 2s
      timeout: 3s
      retries: 30

  psql:
    image: postgres:16
    depends_on:
      postgres:
        condition: service_healthy
    volumes:
      - pg-socket:/var/run/postgresql
    environment:
      PGHOST: /var/run/postgresql
      PGPORT: "5432"
      PGUSER: app
      PGPASSWORD: app
      PGDATABASE: app
    entrypoint: ["bash", "-lc"]
    command: >
      psql -c "select 'Hello World from unix socket!'";


volumes:
  pg-data:
  pg-socket:

The Compose file:

  • Runs Postgres.
  • Sets the socket directory to /var/run/postgresql.
  • Shares that directory through a named volume.
  • Adds a psql container that connects over the socket.

Run it:

bash
docker compose up --abort-on-container-exit --remove-orphans

The psql container prints the greeting and exits.

To run the Python client too:

bash
docker compose up --build --abort-on-container-exit --remove-orphans

Verify the socket exists

From a container that has the volume mounted:

bash
docker compose run --rm psql bash -lc "ls -la /var/run/postgresql"

The socket file is .s.PGSQL.5432.

Connect from Python (asyncpg)

In asyncpg, host is the socket directory:

python
import asyncpg

conn = await asyncpg.connect(
    host="/var/run/postgresql",  # directory containing .s.PGSQL.5432
    port=5432,
    user="app",
    password="app",
    database="app",
)

host="localhost" is TCP. host="/var/run/postgresql" is socket.

Troubleshooting

psql: error: connection to server on socket ... failed

  • Confirm Postgres has unix_socket_directories=/var/run/postgresql.
  • Confirm both containers mount the same named volume at the same path.
  • Confirm the socket file exists: ls -la /var/run/postgresql.

psql uses TCP when you expected a socket

Pass -h /path/to/socketdir. Without -h, psql falls back to TCP based on defaults.

Permissions on the socket directory

Postgres needs to create the socket file. A named volume mounted at /var/run/postgresql avoids permission conflicts in Compose.

Newsletter

Keep reading.

One email when something new lands. No spam.

RSS