Multi-container .NET applications with Docker Compose, container orchestration, and development environments
From dotnet-blazornpx claudepluginhub markus41/claude --plugin dotnet-blazorThis skill is limited to using the following tools:
Guides Next.js Cache Components and Partial Prerendering (PPR) with cacheComponents enabled. Implements 'use cache', cacheLife(), cacheTag(), revalidateTag(), static/dynamic optimization, and cache debugging.
Migrates code, prompts, and API calls from Claude Sonnet 4.0/4.5 or Opus 4.1 to Opus 4.5, updating model strings on Anthropic, AWS, GCP, Azure platforms.
Analyzes BMad project state from catalog CSV, configs, artifacts, and query to recommend next skills or answer questions. Useful for help requests, 'what next', or starting BMad.
# Build stage
FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build
WORKDIR /src
# Copy solution and project files for layer caching
COPY *.sln .
COPY Directory.Build.props Directory.Packages.props ./
COPY src/MyApp.Web/MyApp.Web.csproj src/MyApp.Web/
COPY src/MyApp.Api/MyApp.Api.csproj src/MyApp.Api/
COPY src/MyApp.Domain/MyApp.Domain.csproj src/MyApp.Domain/
COPY src/MyApp.Infrastructure/MyApp.Infrastructure.csproj src/MyApp.Infrastructure/
RUN dotnet restore
# Copy source and publish
COPY . .
RUN dotnet publish src/MyApp.Web/MyApp.Web.csproj -c Release -o /app/publish --no-restore
# Runtime stage (minimal image)
FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS runtime
WORKDIR /app
EXPOSE 8080
ENV ASPNETCORE_URLS=http://+:8080
ENV DOTNET_RUNNING_IN_CONTAINER=true
# Non-root user for security
USER $APP_UID
COPY --from=build /app/publish .
ENTRYPOINT ["dotnet", "MyApp.Web.dll"]
services:
# Blazor Web App
webapp:
build:
context: .
dockerfile: src/MyApp.Web/Dockerfile
ports:
- "5000:8080"
environment:
- ASPNETCORE_ENVIRONMENT=Development
- ConnectionStrings__Default=Host=postgres;Database=myapp;Username=postgres;Password=devpass
- ConnectionStrings__Redis=redis:6379
- RabbitMQ__Host=rabbitmq
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_started
volumes:
- ~/.aspnet/https:/https:ro # Dev HTTPS certs
networks:
- backend
# API Service
api:
build:
context: .
dockerfile: src/MyApp.Api/Dockerfile
ports:
- "5001:8080"
environment:
- ASPNETCORE_ENVIRONMENT=Development
- ConnectionStrings__Default=Host=postgres;Database=myapp;Username=postgres;Password=devpass
- ConnectionStrings__Redis=redis:6379
depends_on:
postgres:
condition: service_healthy
networks:
- backend
# Worker Service
worker:
build:
context: .
dockerfile: src/MyApp.Worker/Dockerfile
environment:
- ConnectionStrings__Default=Host=postgres;Database=myapp;Username=postgres;Password=devpass
- RabbitMQ__Host=rabbitmq
depends_on:
postgres:
condition: service_healthy
rabbitmq:
condition: service_healthy
networks:
- backend
# PostgreSQL
postgres:
image: postgres:16-alpine
environment:
POSTGRES_DB: myapp
POSTGRES_USER: postgres
POSTGRES_PASSWORD: devpass
ports:
- "5432:5432"
volumes:
- postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 5s
timeout: 5s
retries: 5
networks:
- backend
# Redis
redis:
image: redis:7-alpine
ports:
- "6379:6379"
volumes:
- redis_data:/data
networks:
- backend
# RabbitMQ
rabbitmq:
image: rabbitmq:3-management-alpine
ports:
- "5672:5672" # AMQP
- "15672:15672" # Management UI
environment:
RABBITMQ_DEFAULT_USER: guest
RABBITMQ_DEFAULT_PASS: guest
healthcheck:
test: ["CMD", "rabbitmq-diagnostics", "check_port_connectivity"]
interval: 10s
timeout: 5s
retries: 5
networks:
- backend
# SQL Server (alternative to PostgreSQL)
# sqlserver:
# image: mcr.microsoft.com/mssql/server:2022-latest
# environment:
# ACCEPT_EULA: "Y"
# MSSQL_SA_PASSWORD: "YourStrong!Password"
# ports:
# - "1433:1433"
volumes:
postgres_data:
redis_data:
networks:
backend:
driver: bridge
// Integration event (crosses service boundaries)
public sealed record OrderPaymentSucceededIntegrationEvent(
int OrderId, DateTime PaymentDate) : IntegrationEvent;
// Event bus interface
public interface IEventBus
{
Task PublishAsync<T>(T @event, CancellationToken ct = default) where T : IntegrationEvent;
void Subscribe<T, THandler>() where T : IntegrationEvent where THandler : IIntegrationEventHandler<T>;
}
// Integration event handler
public sealed class OrderPaymentSucceededHandler(
IOrderRepository orderRepo,
ILogger<OrderPaymentSucceededHandler> logger)
: IIntegrationEventHandler<OrderPaymentSucceededIntegrationEvent>
{
public async Task Handle(OrderPaymentSucceededIntegrationEvent @event, CancellationToken ct)
{
logger.LogInformation("Payment succeeded for order {OrderId}", @event.OrderId);
var order = await orderRepo.GetAsync(@event.OrderId, ct);
order?.SetPaidStatus();
await orderRepo.UnitOfWork.SaveEntitiesAsync(ct);
}
}
// Using YARP (Yet Another Reverse Proxy) - Microsoft's recommended gateway
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddReverseProxy()
.LoadFromConfig(builder.Configuration.GetSection("ReverseProxy"));
var app = builder.Build();
app.MapReverseProxy();
app.Run();
// appsettings.json for YARP
{
"ReverseProxy": {
"Routes": {
"catalog-route": {
"ClusterId": "catalog-cluster",
"Match": { "Path": "/api/catalog/{**catch-all}" }
},
"ordering-route": {
"ClusterId": "ordering-cluster",
"Match": { "Path": "/api/orders/{**catch-all}" }
}
},
"Clusters": {
"catalog-cluster": {
"Destinations": {
"destination1": { "Address": "http://catalog-api:8080/" }
}
},
"ordering-cluster": {
"Destinations": {
"destination1": { "Address": "http://ordering-api:8080/" }
}
}
}
}
}
| Image | Use for | Size |
|---|---|---|
mcr.microsoft.com/dotnet/sdk:10.0 | Build stage only | ~800MB |
mcr.microsoft.com/dotnet/aspnet:10.0 | Web apps, APIs | ~220MB |
mcr.microsoft.com/dotnet/runtime:10.0 | Console/worker apps | ~190MB |
mcr.microsoft.com/dotnet/runtime-deps:10.0 | Self-contained AOT | ~110MB |
.dockerignore to exclude bin/, obj/, .git/:latest alone in production)