Docker Compose for Local .NET Development
Most .NET applications depend on external services — a database, a cache, a message broker. Docker Compose lets you define these dependencies as code and spin up a complete local environment with a single command.
A Basic Compose File
Here's a docker-compose.yml for an ASP.NET Core API that depends on PostgreSQL and Redis:
services:
api:
build:
context: .
dockerfile: src/MyApi/Dockerfile
ports:
- "5000:8080"
environment:
- ConnectionStrings__Default=Host=postgres;Database=myapp;Username=postgres;Password=devpass
- Redis__ConnectionString=redis:6379
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_started
postgres:
image: postgres:17
environment:
POSTGRES_DB: myapp
POSTGRES_PASSWORD: devpass
ports:
- "5432:5432"
volumes:
- pgdata:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 5s
timeout: 5s
retries: 5
redis:
image: redis:7-alpine
ports:
- "6379:6379"
volumes:
pgdata:
Run docker compose up and your entire stack comes up. The depends_on with condition: service_healthy ensures the API waits for PostgreSQL to be ready before starting.
Development Without Containerising Your App
Often you want the infrastructure in containers but your .NET app running locally with hot reload. Use a compose file for just the dependencies:
services:
postgres:
image: postgres:17
environment:
POSTGRES_DB: myapp
POSTGRES_PASSWORD: devpass
ports:
- "5432:5432"
volumes:
- pgdata:/var/lib/postgresql/data
redis:
image: redis:7-alpine
ports:
- "6379:6379"
seq:
image: datalust/seq:latest
environment:
ACCEPT_EULA: "Y"
ports:
- "5341:5341"
- "8081:80"
volumes:
pgdata:
Start the infrastructure with docker compose -f docker-compose.infra.yml up -d, then run your API with dotnet watch:
dotnet watch run --project src/MyApi
This gives you the best of both worlds: containerised dependencies with full hot reload and debugger support for your .NET code.
Using Compose Profiles
If some services are only needed occasionally, use profiles:
services:
postgres:
image: postgres:17
environment:
POSTGRES_DB: myapp
POSTGRES_PASSWORD: devpass
ports:
- "5432:5432"
mailhog:
image: mailhog/mailhog
profiles: ["email"]
ports:
- "1025:1025"
- "8025:8025"
jaeger:
image: jaegertracing/all-in-one:latest
profiles: ["tracing"]
ports:
- "16686:16686"
- "4317:4317"
PostgreSQL starts with every docker compose up. MailHog only starts when you explicitly request it: docker compose --profile email up.
Environment Files
Keep credentials out of your compose file with .env:
POSTGRES_PASSWORD=devpass
POSTGRES_DB=myapp
Reference them in docker-compose.yml:
services:
postgres:
image: postgres:17
environment:
POSTGRES_DB: ${POSTGRES_DB}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
Database Initialisation
You can seed your database by mounting SQL scripts:
postgres:
image: postgres:17
environment:
POSTGRES_DB: myapp
POSTGRES_PASSWORD: devpass
volumes:
- ./scripts/init.sql:/docker-entrypoint-initdb.d/init.sql
- pgdata:/var/lib/postgresql/data
PostgreSQL automatically executes any .sql files in /docker-entrypoint-initdb.d/ when the database is first created.
Running EF Core Migrations
You can run Entity Framework Core migrations against your containerised database:
# Start just the database
docker compose up postgres -d
# Run migrations from your project
dotnet ef database update --project src/MyApi
Or automate it with an init service in compose:
migrate:
build:
context: .
dockerfile: src/MyApi/Dockerfile
target: build
command: dotnet ef database update --project src/MyApi
depends_on:
postgres:
condition: service_healthy
profiles: ["migrate"]
Run migrations on demand with docker compose --profile migrate run migrate.
Compose Watch
Docker Compose 2.22+ supports watch mode for automatic rebuilds:
services:
api:
build:
context: .
develop:
watch:
- action: rebuild
path: ./src
target: /src
Run docker compose watch and your container rebuilds automatically when source files change. This is useful when you need your app running inside a container (for networking or platform-specific testing) but still want a fast feedback loop.
Summary
Docker Compose is the simplest way to manage local development dependencies. The key insight is that you don't have to containerise everything — run your infrastructure in containers and your .NET code locally for the best development experience. Use profiles for optional services, health checks for startup ordering, and volumes for data persistence.