Check whether Postgres is running on a Mac

Ask the server, then ask Homebrew, then ask the port, then ask Docker. Each one can say yes while the next says no.

"Running" has four different meanings here: a process exists, launchd thinks it is loaded, something is listening on 5432, and a client can actually connect. Check them in this order.

Ask the server

pg_isready -h 127.0.0.1 -p 5432

accepting connections is the only answer that means yes. no response means nothing is listening on that port; rejecting connections means a server is there but still starting up, shutting down, or in recovery. If pg_isready is not on your PATH, Homebrew keeps it at /opt/homebrew/opt/postgresql@17/bin/pg_isready (/usr/local/opt on an Intel Mac).

Without -h, pg_isready and psql use the Unix socket in /tmp, which is a different question from "can my app connect over TCP". Ask both if they disagree.

Ask Homebrew

brew services list
brew services info postgresql@17

brew services list shows every formula with a service and its launchd state: started, none, or error with an exit code. info adds the PID, the user, and the plist path. Note what it does not check: whether the process it started is still alive and answering. A crashed Postgres can show as started until launchd notices, and one that is up but rejecting connections always does. See brew services says started but nothing is answering.

Ask the port

lsof -nP -iTCP:5432 -sTCP:LISTEN

This shows the process that owns the port, whichever way it was started. The COMMAND column is the tell: postgres is a native server (Homebrew, Postgres.app, or a binary you ran); com.docker.backend or another runtime process means a container has the port. Nothing listed means nothing is listening, however many processes are named postgres.

ps -o pid,etime,args -p $(pgrep -x postgres | head -1)

The arguments give you the data directory (-D) and the executable path tells you who installed it.

Ask Docker

docker ps --filter publish=5432
docker ps -a --filter ancestor=postgres

The first lists running containers that publish 5432 to the host. The second lists every container built from the postgres image, including exited ones, which is where a Compose database goes after docker compose stop. A container in Up state can still be red for a client: the server inside may still be running initdb, or the port may be published on a different host port than the one you are trying.

Why the answers disagree

Postgres accepts TCP connections only after recovery finishes, so there is a window at startup where the port is open and every connection is rejected. Homebrew's started is launchd's view of the job, not the server's. And two servers can exist at once, a Homebrew one on the socket and a Docker one on the port, so psql and your app are talking to different databases; see two Postgres instances on the same port.

Postgres.app

pgrep -fl Postgres.app
ls /tmp/.s.PGSQL.*

Postgres.app runs its servers from /Applications/Postgres.app/Contents/Versions/17/bin/postgres and defaults to 5432 too, so it competes with Homebrew for both the port and the socket file. The second command lists socket files; a native server of either kind leaves one there, a container never does.

What each check proves

CheckYes meansDoes not prove
pg_isreadyA server accepts connections on that addressWhich server it is
brew services listlaunchd loaded the jobThe process is alive or answering
lsofA process holds the portThe server has finished starting
docker psThe container is upThe port is published to the host

The easier way

ServeMon runs the first check for you, continuously: it finds every Postgres on the Mac, whether Homebrew, Docker, Postgres.app, or a bare process, sends each one the real startup handshake, and shows a green, yellow, or red dot with the latency, version, port, and data directory next to it.

The ServeMon window with PostgreSQL 17 selected: a green health pill with latency, the Homebrew badge, and the Overview grid showing version, port, PID, uptime, data directory, config file, and log file.

Download ServeMon