The Problem with "It Works Locally"
Modern applications rarely consist of a single service. A typical web app needs a backend API, a database, a cache, maybe a background worker. Getting all of these running consistently across a team is painful without the right tooling. Docker Compose solves this — it lets you define and run multi-container applications with a single docker compose up.
What Is Docker Compose?
Docker Compose uses a YAML file (docker-compose.yml) to define your application's services, networks, and volumes. With one command, it starts all services in the correct order, connected to the same network, with persistent volumes where needed.
Installing Docker Compose
Docker Compose v2 ships with Docker Desktop. On Linux, install the plugin:
sudo apt-get install docker-compose-plugin
docker compose versionA Real-World Example: Node.js + PostgreSQL + Redis
Let's build a docker-compose.yml for a backend API that uses PostgreSQL for storage and Redis for caching.
First, the Dockerfile for the API:
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
EXPOSE 3000
CMD ["npm", "run", "dev"]Now the docker-compose.yml:
version: '3.9'
services:
api:
build: .
ports:
- "3000:3000"
environment:
DATABASE_URL: postgres://postgres:secret@db:5432/myapp
REDIS_URL: redis://cache:6379
volumes:
- .:/app
- /app/node_modules
depends_on:
db:
condition: service_healthy
cache:
condition: service_started
db:
image: postgres:16-alpine
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: secret
POSTGRES_DB: myapp
volumes:
- db_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 5s
timeout: 3s
retries: 5
cache:
image: redis:7-alpine
volumes:
- cache_data:/data
volumes:
db_data:
cache_data:Key Concepts Explained
depends_on with health checks — depends_on: condition: service_healthy waits for PostgreSQL to be ready before starting the API. Without this, the API often crashes on startup because the database isn't accepting connections yet.
Named volumes (db_data, cache_data) — Data persists between docker compose down and docker compose up restarts. Without named volumes, your database is wiped every time you stop the stack.
Source code bind mount (. :/app) — Maps your local project directory into the container. Code changes are reflected immediately without rebuilding the image, enabling hot-reload development.
/app/node_modules anonymous volume — Prevents the host's node_modules from overwriting the container's. Essential on macOS/Windows where file system differences cause issues.
Common Commands
docker compose up # start all services (foreground)
docker compose up -d # start in detached (background) mode
docker compose logs -f api # stream logs from the api service
docker compose exec api sh # open a shell in the running api container
docker compose down # stop and remove containers
docker compose down -v # also remove named volumes (wipes data)
docker compose build # rebuild images without starting
docker compose ps # list running services and their portsAdding a Worker Service
Extend the same stack with a background worker:
worker:
build: .
command: node worker.js
environment:
DATABASE_URL: postgres://postgres:secret@db:5432/myapp
REDIS_URL: redis://cache:6379
depends_on:
- db
- cacheThe worker shares the same network as the API and database automatically — no extra configuration needed.
From Local to Production with PandaStack
Docker Compose is a local development tool — it's not designed for production. When you're ready to go live, PandaStack handles each service separately: deploy your API and worker as Docker container deployments, provision a managed PostgreSQL or Redis database from the platform, and connect them via environment variables.
Install the CLI:
npm install -g @pandastack/cli
panda deploy --type container --repo your-org/api-repoManaged databases on PandaStack (PostgreSQL, MySQL, Redis, MongoDB) are production-ready with automated backups and connection pooling. See [docs.pandastack.io](https://docs.pandastack.io) or log in at [dashboard.pandastack.io](https://dashboard.pandastack.io) to get started.