services:
api:
build: .
ports: ["3000:3000"]
environment:
DATABASE_URL: postgres://postgres:${DB_PASSWORD}@db:5432/postgres
REDIS_URL: redis://redis:6379
depends_on: [db, redis]
db:
image: postgres:16
environment: { POSTGRES_PASSWORD: "${DB_PASSWORD}" }
volumes: ["pgdata:/var/lib/postgresql/data"]
redis:
image: redis:7
volumes: { pgdata: {} }
The API reaches each service by its service name, and Postgres data lives in the named volume pgdata.
docker compose up does three things:
- It builds the
apiimage. - It pulls the
postgresandredisimages. - It creates one project network, and all three services join it.
On that network, each service is reachable by its name. So the API connects to the hosts db and redis, using their normal container ports.
Only the API publishes a port. So the database and the cache are not exposed on the host.
Compose replaces ${DB_PASSWORD} with a value from the shell environment or from a .env file in the project folder. This keeps the password out of the Compose file.
Postgres data lives in the named volume pgdata. So the data survives docker compose down and survives when the container is recreated. Redis has no volume here, so its data is lost when its container is removed. Note: plain depends_on only controls the start order. It does not wait until a service is ready.
- Compose creates one shared network for the project.
- Services reach each other by service name.
- Only publish ports that need access from outside.
- Compose fills in variables from the shell or a
.envfile. - A named volume keeps the Postgres data.