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) andhost(TCP) differently inpg_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
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
psqlcontainer that connects over the socket.
Run it:
docker compose up --abort-on-container-exit --remove-orphans
The psql container prints the greeting and exits.
To run the Python client too:
docker compose up --build --abort-on-container-exit --remove-orphans
Verify the socket exists
From a container that has the volume mounted:
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:
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.