Installing Postgres and Redis natively works, but you end up with whatever
version your package manager shipped, running as a background service you
have to remember exists. Running them in containers instead means each
project pins its own versions, and docker compose down cleanly stops
everything with nothing left running in the background. This builds on the
Docker setup guide — have Docker
running before you start.
The compose file
# docker-compose.yml
services:
postgres:
image: postgres:16
restart: unless-stopped
environment:
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRES_DB: ${POSTGRES_DB}
ports:
- "5432:5432"
volumes:
- pg-data:/var/lib/postgresql/data
redis:
image: redis:7
restart: unless-stopped
ports:
- "6379:6379"
volumes:
- redis-data:/data
command: redis-server --appendonly yes
volumes:
pg-data:
redis-data:Keep credentials out of the compose file
# .env
POSTGRES_USER=devuser
POSTGRES_PASSWORD=devpassword
POSTGRES_DB=myapp_devDocker Compose reads a .env file in the same directory automatically —
that's what ${POSTGRES_USER} above resolves from. Add it to
.gitignore immediately:
echo ".env" >> .gitignoreCommit a .env.example with the same keys and placeholder values instead,
so teammates know what to fill in without ever seeing real credentials in
Git history.
Start it
docker compose up -d
docker compose psBoth containers should show as running. Data written to either one
persists across restarts because of the named volumes — docker compose down (without -v) stops the containers but keeps the volumes intact.
Connect to Postgres
From your host machine, if you have psql installed:
psql -h localhost -U devuser -d myapp_devOr skip installing a client entirely and use the one already inside the container:
docker compose exec postgres psql -U devuser -d myapp_devFrom application code, the connection string is just:
postgresql://devuser:devpassword@localhost:5432/myapp_dev
Connect to Redis
docker compose exec redis redis-cli127.0.0.1:6379> set greeting "hello"
OK
127.0.0.1:6379> get greeting
"hello"
From application code: redis://localhost:6379.
A GUI, if you want one
Command-line clients are fine for quick checks; for browsing data
regularly, TablePlus, DBeaver
(Postgres), and RedisInsight (Redis) all
connect to localhost on the ports above with zero extra config.
Common gotchas
"port is already allocated" on 5432 or 6379. You likely have a native
Postgres or Redis install already running on the default port. Either stop
that service, or remap the container's port — "5433:5432" in the compose
file, then connect on 5433 instead.
Data disappeared after docker compose down. If you ran docker compose down -v, the -v flag deletes volumes along with containers —
that's the one that wipes data. Plain docker compose down does not.
Add a health check so dependent services (like an app container with
depends_on) actually wait for Postgres to be ready, not just started:
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER}"]
interval: 5s
timeout: 5s
retries: 5With both services defined in one file next to your app's code, "set up
the database" for a new contributor becomes docker compose up -d —
nothing to install, nothing to remember to start manually.