
Docker Compose for Local Development: My Honest Review After Two Years
Two years ago, I switched my entire local development setup to Docker Compose. No more "it works on my machine" arguments. No more spending half a day setting up a new laptop. Just clone, run `docker-compose up`, and start working.
At least, that was the promise. Here's what actually happened after 24 months of real-world usage across multiple projects, team changes, and enough debugging sessions to make me question my life choices.
The Good Parts: Why I'm Still Using It
Onboarding is Now Actually Pleasant
Onboarding new team members is so much easier. Our README used to be three pages of installation instructions covering Node.js versions, Python virtual environments, database setup, Redis configuration, and the inevitable "oh, you also need to install this obscure system dependency." Now it's: install Docker, run this command, wait five minutes. Everyone has the exact same environment.
I've watched senior developers get our full stack running in under 10 minutes on their first day. Compare that to the old process where we'd lose half a day to environment setup, only to discover someone's using a different version of PostgreSQL that doesn't support our latest migrations.
Environment Parity That Actually Works
Testing against different database versions? Just change a version number in your `docker-compose.yml`:
services:
postgres:
image: postgres:14 # Change to postgres:15 to test compatibility
environment:
POSTGRES_DB: myapp
POSTGRES_USER: dev
POSTGRES_PASSWORD: passwordNeed to add Redis for caching? Add five lines to a file:
redis:
image: redis:7-alpine
ports:
"6379:6379"Switching between projects that need different versions of everything? No conflicts, because nothing is installed on your actual machine. I regularly work on a legacy project using Node 14 and a newer one using Node 18, plus another that needs Python 3.9. Before Docker Compose, this meant constant version switching with tools like `nvm` or `pyenv`.
Reproducible Issues and Easier Debugging
When someone reports a bug, I can recreate their exact environment. No more "I can't reproduce this" because of subtle differences in local setups. Our production and staging environments mirror the Docker setup, so environmental bugs are caught early.
The consistency extends to CI/CD pipelines too. Our GitHub Actions runners use the same Docker images as our local development, eliminating those frustrating "tests pass locally but fail in CI" situations.
The Annoying Parts: The Real Talk
Performance Gotchas That Will Slow You Down
File watching can be slow, especially on Mac. Your code changes and then you wait a few seconds for the container to notice. Hot reload isn't as hot as it could be. On my MacBook Pro, file system events can take 2-3 seconds to propagate into containers, which breaks the development flow when you're used to instant feedback.
There are workarounds, but they add complexity:
services:
app:
build: .
volumes:
.:/app
node_modules:/app/node_modules # Anonymous volume for better performance
environment:
CHOKIDAR_USEPOLLING=true # Force polling for file changesVolume mounting strategies become crucial. Bind mounts are convenient but slow on macOS. Named volumes are faster but make file editing awkward. I've settled on a hybrid approach: bind mount source code, but use named volumes for dependencies like `node_modules`.
The Docker Desktop License Drama
Docker Desktop licensing changed, which caused some headaches at bigger companies. If you're at an enterprise with 250+ employees, you now need a paid license. This led to our team exploring alternatives like Podman or Rancher Desktop, adding unexpected complexity to what should be a simple setup.
Debugging: More Layers, More Problems
Debugging inside containers is more steps than debugging locally. Attaching debuggers, reading logs from the right place, checking what's happening inside - it all takes getting used to.
For Node.js debugging, you need to expose the debug port and configure your IDE:
services:
app:
build: .
ports:
"3000:3000"
"9229:9229" # Debug port
command: ["node", "--inspect=0.0.0.0:9229", "server.js"]Want to check what's installed inside a container? You're running `docker exec -it container_name bash` instead of just looking at your local system. It's not hard, but it's an extra step for everything.
Common Pitfalls I Wish Someone Had Warned Me About
Docker Compose Files Get Unwieldy Fast
Start simple, but plan for complexity. My first `docker-compose.yml` was 20 lines. Two years later, it's 150+ lines with overrides for different environments. Use multiple compose files for different scenarios:
Development
docker-compose up
Development with debugging
docker-compose -f docker-compose.yml -f docker-compose.debug.yml up
Testing
docker-compose -f docker-compose.yml -f docker-compose.test.yml upThe Networking Learning Curve
Understanding Docker networks became essential. Services communicate by service name, not localhost. Your app connects to `postgres:5432`, not `localhost:5432`. This trips up new team members who try to use database GUI tools with localhost connections.
Data Persistence Surprises
Volumes need explicit management. I've lost development data more times than I care to admit by running `docker-compose down -v` instead of just `docker-compose down`. Now we have clear documentation about data persistence and backup strategies for local development databases.
Best Practices I've Learned the Hard Way
Multi-Stage Builds for Development vs Production
Use different targets for development and production:
FROM node:18 AS base
WORKDIR /app
COPY package*.json ./
FROM base AS development
RUN npm install
COPY . .
CMD ["npm", "run", "dev"]
FROM base AS production
RUN npm ci --only=production
COPY . .
CMD ["npm", "start"]Environment Variable Management
Use `.env` files but never commit secrets:
services:
app:
build: .
env_file:
.env.local # Gitignored
.env.shared # Committed, no secretsHealth Checks Save Debugging Time
Add health checks to catch startup issues early:
services:
postgres:
image: postgres:14
healthcheck:
test: ["CMD-SHELL", "pg_isready -U $$POSTGRES_USER"]
interval: 30s
timeout: 10s
retries: 3Would I Recommend It?
Yes, but know what you're getting into. For teams, especially teams with complex setups or frequent onboarding, the benefits are huge. For solo projects or simple apps, it might be more setup than it's worth.
Start simple. Get your main app and database running first. Add services as you need them. Don't try to containerize everything on day one.
The learning curve is real, but the payoff in team productivity and deployment consistency makes it worthwhile. After two years, I can't imagine going back to the chaos of manual environment management.
Bottom line: Docker Compose transformed how our team works. The initial investment in learning and setup pays dividends every time we onboard someone new, switch between projects, or deploy to a new environment. The rough edges exist, but they're manageable once you know what to expect.
If you're on the fence, start with a simple side project. Get comfortable with the basics before committing your main development workflow. Once you experience the "it just works everywhere" feeling, you'll understand why so many teams are making the switch.