Overview
Docker Compose enables you to define and run multi-container applications. This example shows how to build container images and manage Docker Compose deployments with ANDO.
Project Structure
my-app/
├── src/
│ ├── Api/
│ │ ├── Api.csproj
│ │ └── Dockerfile
│ └── Worker/
│ ├── Worker.csproj
│ └── Dockerfile
├── docker-compose.yml
├── docker-compose.prod.yml # Production overrides
└── build.csando
Basic Multi-Container Build
Build multiple images and run with Docker Compose:
var api = Dotnet.Project("./src/Api/Api.csproj");
var worker = Dotnet.Project("./src/Worker/Worker.csproj");
// Build .NET projects
Dotnet.SdkInstall();
Dotnet.Restore(api);
Dotnet.Restore(worker);
Dotnet.Build(api);
Dotnet.Build(worker);
// Build Docker images
Docker.Build("./src/Api", "myapp-api:latest");
Docker.Build("./src/Worker", "myapp-worker:latest");
// Run with Docker Compose
Docker.ComposeUp();
Log.Info("Application started with Docker Compose");
Build and Push to Registry
Build images and push to a container registry:
var deploy = DefineProfile("deploy");
var registry = "ghcr.io/myorg";
var version = Git.GetVersion();
// Build images with registry prefix
Docker.Build("./src/Api", $"{registry}/myapp-api:{version}");
Docker.Build("./src/Worker", $"{registry}/myapp-worker:{version}");
// Also tag as latest
Docker.Tag($"{registry}/myapp-api:{version}", $"{registry}/myapp-api:latest");
Docker.Tag($"{registry}/myapp-worker:{version}", $"{registry}/myapp-worker:latest");
if (deploy)
{
// Push to registry
GitHub.EnsureAuthenticated();
Docker.Push($"{registry}/myapp-api:{version}");
Docker.Push($"{registry}/myapp-api:latest");
Docker.Push($"{registry}/myapp-worker:{version}");
Docker.Push($"{registry}/myapp-worker:latest");
Log.Info($"Pushed images with version {version}");
}
Environment-Specific Compose Files
Use different compose files for different environments:
var deploy = DefineProfile("deploy");
var production = DefineProfile("production");
// Build images
Docker.Build("./src/Api", "myapp-api:latest");
Docker.Build("./src/Worker", "myapp-worker:latest");
if (deploy)
{
if (production)
{
// Production: use override file
Docker.ComposeUp(o => o
.WithFile("docker-compose.yml")
.WithFile("docker-compose.prod.yml")
.WithDetach());
}
else
{
// Development/staging
Docker.ComposeUp(o => o
.WithFile("docker-compose.yml")
.WithDetach());
}
Log.Info("Compose stack deployed");
}
Usage:
# Deploy to staging
ando -p deploy
# Deploy to production
ando -p deploy,production
With Database and Dependencies
Full stack with database, cache, and application services:
var deploy = DefineProfile("deploy");
// Build application images
Docker.Build("./src/Api", "myapp-api:latest");
Docker.Build("./src/Worker", "myapp-worker:latest");
if (deploy)
{
// Start infrastructure first
Docker.ComposeUp(o => o
.WithServices("postgres", "redis")
.WithDetach()
.WithWait());
// Wait for database to be ready
Docker.ComposeExec("postgres", "pg_isready -U postgres", o => o
.WithRetry(30, 1000));
// Run migrations
Docker.ComposeRun("api", "dotnet ef database update");
// Start application services
Docker.ComposeUp(o => o
.WithServices("api", "worker")
.WithDetach());
Log.Info("Full stack deployed");
}
Health Checks and Rollback
Deploy with health checks and automatic rollback:
var deploy = DefineProfile("deploy");
var version = Git.GetVersion();
Docker.Build("./src/Api", $"myapp-api:{version}");
if (deploy)
{
// Pull current image as backup
Docker.Pull("myapp-api:current");
Docker.Tag("myapp-api:current", "myapp-api:rollback");
// Tag new version as current
Docker.Tag($"myapp-api:{version}", "myapp-api:current");
// Deploy
Docker.ComposeUp(o => o.WithDetach().WithRemoveOrphans());
// Health check
var healthy = Docker.ComposeHealthCheck("api", timeout: 60);
if (!healthy)
{
Log.Error("Health check failed, rolling back...");
Docker.Tag("myapp-api:rollback", "myapp-api:current");
Docker.ComposeUp(o => o.WithDetach());
throw new Exception("Deployment failed health check");
}
Log.Info($"Deployed version {version} successfully");
}
Compose for Testing
Run integration tests with Docker Compose:
var test = DefineProfile("test");
// Build test dependencies
Docker.Build("./src/Api", "myapp-api:test");
if (test)
{
// Start test infrastructure
Docker.ComposeUp(o => o
.WithFile("docker-compose.test.yml")
.WithDetach()
.WithWait());
// Run tests against compose services
Dotnet.Test(Dotnet.Project("./tests/Integration.Tests/Integration.Tests.csproj"), o => o
.WithEnvironment("API_URL", "http://localhost:5000"));
// Cleanup
Docker.ComposeDown(o => o
.WithFile("docker-compose.test.yml")
.WithVolumes());
Log.Info("Integration tests complete");
}
Example docker-compose.yml
version: '3.8'
services:
api:
image: myapp-api:latest
ports:
- "5000:80"
environment:
- ConnectionStrings__Default=Host=postgres;Database=myapp;Username=postgres;Password=secret
- Redis__Host=redis
depends_on:
- postgres
- redis
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost/health"]
interval: 10s
timeout: 5s
retries: 3
worker:
image: myapp-worker:latest
environment:
- ConnectionStrings__Default=Host=postgres;Database=myapp;Username=postgres;Password=secret
- Redis__Host=redis
depends_on:
- postgres
- redis
postgres:
image: postgres:15
environment:
- POSTGRES_PASSWORD=secret
- POSTGRES_DB=myapp
volumes:
- postgres_data:/var/lib/postgresql/data
redis:
image: redis:7-alpine
volumes:
postgres_data:
Key Operations
| Operation | Purpose |
|---|---|
| Docker.Build() | Build a Docker image |
| Docker.ComposeUp() | Start compose services |
| Docker.ComposeDown() | Stop compose services |
| Docker.ComposeExec() | Run command in service |
| Docker.Push() | Push image to registry |
Tips
- Build context - Keep Dockerfiles close to their source code
- Multi-stage builds - Use multi-stage Dockerfiles for smaller images
- Named volumes - Use named volumes for persistent data
- Health checks - Always define health checks for production
See Also
- Docker Provider - Full API reference
- Container Registry - Push to registries
- Playwright E2E Tests - Testing with containers