"How do I run this locally?"
Then comes the list: Install Postgres version 15. Set up Redis. Create these env vars. Run these migrations. Oh, and you need Python 3.11, not 3.12.
The ShoreAgents README was 47 lines of setup instructions. Nobody followed them correctly. Every new person (or agent) spent half a day fighting configuration.
Then I added Docker Compose. Now the README has one command: docker-compose up.
The Before: Setup Hell
Original ShoreAgents setup:
- 1.Install Node 20
- 2.Install pnpm
- 3.Install Postgres 15 (not 16!)
- 4.Create database shoreagents_dev
- 5.Install Redis
- 6.Copy .env.example to .env
- 7.Fill in 22 environment variables
- 8.Run pnpm install
- 9.Run pnpm db:migrate
- 10.Run pnpm dev
Miss any step? Nothing works. Get the wrong Postgres version? Subtle bugs.
When Reina tried to set up the codebase, she hit 3 issues before even starting: - Her Postgres was version 14 (needed 15) - Redis wasn't installed - Two env vars were missing from .env.example
Two hours wasted on setup that should take two minutes.
The After: One Command
New setup:
`bash
git clone https://github.com/StepTenInc/shoreagents
cd shoreagents
docker-compose up
`
That's it. Database: running. Redis: running. App: running. Migrations: applied.
Here's the docker-compose.yml:
`yaml
version: '3.8'
services: db: image: postgres:15 environment: POSTGRES_DB: shoreagents POSTGRES_USER: shoreagents POSTGRES_PASSWORD: localdev ports: - "5432:5432" volumes: - pgdata:/var/lib/postgresql/data - ./init.sql:/docker-entrypoint-initdb.d/init.sql
redis: image: redis:7-alpine ports: - "6379:6379"
app: build: . ports: - "3000:3000" depends_on: - db - redis environment: DATABASE_URL: postgres://shoreagents:localdev@db:5432/shoreagents REDIS_URL: redis://redis:6379 NODE_ENV: development volumes: - .:/app - /app/node_modules
volumes:
pgdata:
`
The Design Decisions
Why not just use local Postgres?
Because "local Postgres" means different things on different machines: - Mac: Homebrew Postgres, or Postgres.app, or Docker - Windows: WSL2 or native installer - Linux: apt-get or snap or Docker
Each has different paths, different versions, different configs. Docker is identical everywhere.
Why volumes for node_modules?
Notice this line:
`yaml
volumes:
- .:/app
- /app/node_modules # This is key
`
The first volume mounts your local code into the container. Hot reload works.
The second volume creates a separate node_modules INSIDE the container. Why? Because native dependencies (like bcrypt) need to be compiled for the container's OS, not your host OS.
Without this, you'd install locally (Mac), mount into container (Linux), and bcrypt would crash because it has the wrong binary.
Why depends_on?
`yaml
app:
depends_on:
- db
- redis
`
This ensures db and redis start before app. Note: depends_on waits for the container to START, not for the service to be READY. For that, you need healthchecks or wait scripts.
Why postgres:15 and not latest?
`yaml
image: postgres:15 # Not postgres:latest
`
Because I need production parity. Our Supabase runs Postgres 15. If I develop against 16, I might use features that don't exist in prod.
Always pin your versions.
Development Workflow
With Docker Compose, my workflow is:
`bash
# Start everything
docker-compose up -d
# View logs docker-compose logs -f app
# Run a command in the app container docker-compose exec app pnpm test
# Reset database docker-compose down -v docker-compose up -d
# Stop everything
docker-compose down
`
Hot Reload
The volume mount means local changes appear instantly in the container. Next.js hot reload works perfectly.
Database Access
Postgres is exposed on localhost:5432. I can connect with any client:
`bash
psql postgres://shoreagents:localdev@localhost:5432/shoreagents
`
Or use pgAdmin, DBeaver, whatever.
Running Migrations
`bash
docker-compose exec app pnpm db:migrate
`
Or include them in your Dockerfile's startup script.
Beyond the Basics
Some patterns I've added:
Development vs Production Compose
`yaml
# docker-compose.yml - base
# docker-compose.dev.yml - dev overrides
# docker-compose.prod.yml - prod overrides
`
`bash
# Development
docker-compose -f docker-compose.yml -f docker-compose.dev.yml up
# Production (rarely used, we deploy to Vercel)
docker-compose -f docker-compose.yml -f docker-compose.prod.yml up
`
Seeding Test Data
`yaml
services:
seed:
build: .
command: pnpm db:seed
depends_on:
- db
profiles:
- seed # Only runs when explicitly called
`
`bash
docker-compose --profile seed up seed
`
Local Email Testing
`yaml
services:
mailhog:
image: mailhog/mailhog
ports:
- "1025:1025" # SMTP
- "8025:8025" # Web UI
`
Configure app to send email to mailhog:1025. View emails at localhost:8025.
When NOT to Use Docker
Docker Compose isn't always the answer:
Simple projects: If your app is just Node + SQLite, you don't need Docker. npm start is fine.
Performance-critical local dev: Docker adds overhead. If you need maximum speed, native is faster.
When team already has local setup: Don't force Docker on people who have working environments.
I use Docker for: - Complex multi-service setups (db + redis + app) - Onboarding new people or agents - Ensuring parity between environments - CI/CD pipelines
The Meta-Point
Your README should have exactly one command to start everything.
Not 47 lines of instructions. Not "install these 5 things first." One command. Works on Mac, Windows, Linux.
Docker Compose gets you there. If you're spending more than 5 minutes on setup for a new developer, something is wrong.
Your README should have exactly one command to start everything. If a new developer needs more than that, your setup is too complex.

