Containerization in ConnectSoft Microservice Template¶
Purpose & Overview¶
Containerization in the ConnectSoft Microservice Template enables consistent, portable, and scalable deployments using Docker containers. The template provides a production-ready containerization strategy with multi-stage builds, optimized image sizes, secure configurations, and comprehensive orchestration via Docker Compose.
Containerization provides:
- Consistency: Same application behavior across development, staging, and production
- Isolation: Application and dependencies packaged together
- Portability: Run anywhere Docker is supported (local, cloud, Kubernetes)
- Scalability: Easy horizontal scaling and orchestration
- Reproducibility: Versioned images ensure consistent deployments
- Security: Minimal attack surface with optimized base images
Containerization Philosophy
The ConnectSoft template treats containers as first-class deployment artifacts. Every microservice is containerized with optimized multi-stage builds, security best practices, and production-ready configurations. Containers are immutable, versioned, and designed for orchestration platforms.
Architecture Overview¶
Containerization Layers¶
Application Container
├── Base Runtime Image (mcr.microsoft.com/dotnet/aspnet:9.0)
│ ├── ASP.NET Core Runtime
│ └── .NET Runtime
├── Published Application
│ ├── Application DLLs
│ ├── Configuration Files
│ └── Dependencies
└── Entry Point
└── dotnet ConnectSoft.MicroserviceTemplate.Application.dll
Multi-Container Architecture¶
Docker Compose Stack
├── Application Container
│ └── ConnectSoft.MicroserviceTemplate.Application
├── Database Containers
│ ├── SQL Server (NHibernate)
│ ├── MongoDB (MongoDB Persistence)
│ └── Redis (Caching, SignalR Backplane)
├── Messaging Containers
│ └── RabbitMQ (MassTransit)
├── Observability Containers
│ ├── OpenTelemetry Collector
│ ├── Prometheus (Metrics)
│ ├── Grafana (Visualization)
│ ├── Jaeger (Tracing)
│ ├── Elasticsearch (Logs)
│ └── Kibana (Log Analysis)
└── Infrastructure Containers
├── Certificate Generator
└── MCP Inspector (if enabled)
Dockerfile¶
Multi-Stage Build Strategy¶
The Dockerfile uses a multi-stage build to minimize final image size and optimize build caching:
#See https://aka.ms/customizecontainer to learn how to customize your debug container and how Visual Studio uses this Dockerfile to build your images for faster debugging.
# Stage 1: Base runtime image
FROM mcr.microsoft.com/dotnet/aspnet:9.0@sha256:b4bea3a52a0a77317fa93c5bbdb076623f81e3e2f201078d89914da71318b5d8 as base
WORKDIR /app
EXPOSE 8081 # HTTP port
EXPOSE 7279 # HTTPS port
# Stage 2: Build stage
FROM mcr.microsoft.com/dotnet/sdk:9.0@sha256:3fcf6f1e809c0553f9feb222369f58749af314af6f063f389cbd2f913b4ad556 AS build
ARG BUILD_CONFIGURATION=Release
WORKDIR app
# Copy dependency files first (optimizes layer caching)
COPY ["Directory.Packages.props", "."]
COPY ["Directory.Build.props", "."]
COPY ["Directory.Packages.props", "./ConnectSoft.MicroserviceTemplate.Application/"]
COPY ["Directory.Build.props", "./ConnectSoft.MicroserviceTemplate.Application/"]
COPY ["nuget.config", "."]
COPY ["Scripts/.", "./Scripts/"]
COPY ["ConnectSoft.MicroserviceTemplate.slnx", "."]
# Copy all .csproj files (enables dependency restore caching)
COPY ["ConnectSoft.MicroserviceTemplate/*.csproj", "./ConnectSoft.MicroserviceTemplate/"]
COPY ["ConnectSoft.MicroserviceTemplate.Application/*.csproj", "./ConnectSoft.MicroserviceTemplate.Application/"]
COPY ["ConnectSoft.MicroserviceTemplate.ApplicationModel/*.csproj", "./ConnectSoft.MicroserviceTemplate.ApplicationModel/"]
# ... copy all other project files ...
# Restore NuGet packages (cached if project files unchanged)
RUN dotnet restore ./ConnectSoft.MicroserviceTemplate.Application/ConnectSoft.MicroserviceTemplate.Application.csproj \
--configfile "nuget.config"
# Copy source code
COPY ["ConnectSoft.MicroserviceTemplate/.", "./ConnectSoft.MicroserviceTemplate/"]
COPY ["ConnectSoft.MicroserviceTemplate.Application/.", "./ConnectSoft.MicroserviceTemplate.Application/"]
# ... copy all source files ...
# Stage 3: Publish stage
FROM build AS publish
RUN dotnet publish "/app/ConnectSoft.MicroserviceTemplate.Application/ConnectSoft.MicroserviceTemplate.Application.csproj" \
-c $BUILD_CONFIGURATION \
-o /app/publish \
--configfile "/app/nuget.config" \
--no-restore
# Stage 4: Final runtime image
FROM base AS final
WORKDIR /app
# Copy published application from publish stage
COPY --from=publish /app/publish .
# Set entry point
ENTRYPOINT ["dotnet", "ConnectSoft.MicroserviceTemplate.Application.dll"]
Build Stage Breakdown¶
Stage 1: Base Runtime Image¶
Purpose: - Minimal runtime image with only ASP.NET Core runtime - No SDK tools (reduces image size) - Pinned to specific SHA256 digest for reproducibility - Exposes HTTP (8081) and HTTPS (7279) ports
Stage 2: Build Stage¶
Purpose: - Full SDK for building and restoring packages - Copies dependency files first (optimizes Docker layer caching) - Restores NuGet packages (cached if project files unchanged) - Copies source code and builds application
Layer Caching Strategy:
1. Copy dependency files (Directory.Packages.props, nuget.config, .csproj files)
2. Restore packages (this layer is cached if dependency files unchanged)
3. Copy source code (this layer invalidates on code changes)
4. Build application
Stage 3: Publish Stage¶
Purpose:
- Publishes application with optimizations
- Uses --no-restore for faster builds (packages already restored)
- Outputs to /app/publish directory
Stage 4: Final Runtime Image¶
FROM base AS final
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "ConnectSoft.MicroserviceTemplate.Application.dll"]
Purpose: - Minimal final image (only runtime, not SDK) - Copies only published output - Sets entry point to run application
Benefits of Multi-Stage Builds¶
- Smaller Image Size: Final image contains only runtime, not SDK (~200MB vs ~800MB)
- Better Caching: Dependency files copied separately from source code
- Security: No build tools in production image
- Reproducibility: Pinned base image SHA256 digests
Docker Compose¶
docker-compose.yml¶
The main Docker Compose file defines the complete application stack:
services:
# Application Service
connectsoft.microservicetemplate.application:
image: ${DOCKER_REGISTRY-}connectsoft.microservicetemplate.application
container_name: connectsoft.microservicetemplate.application
build:
context: .
dockerfile: ../ConnectSoft.MicroserviceTemplate.Application/Dockerfile
depends_on:
- sql
- redis
- mongo
- otel-collector
- rabbitmq
- prometheus
- grafana
- elasticsearch
- kibana
networks:
- backend
# Certificate Generator
certgen:
image: mcr.microsoft.com/dotnet/sdk:8.0
container_name: certgen
entrypoint: ["bash","-lc","dotnet dev-certs https -ep /out/aspnetapp.pfx -p 123456"]
volumes:
- httpscerts:/out
restart: "no"
# SQL Server
sql:
image: "mcr.microsoft.com/mssql/server:2022-latest"
container_name: sql
ports:
- "1433:1433"
environment:
- ACCEPT_EULA=y
- MSSQL_SA_PASSWORD=ConnectSoft123!
- MSSQL_MEMORY_LIMIT_MB=4096
- MSSQL_PID=Developer
networks:
- backend
# Redis
redis:
image: redis
container_name: redis
ports:
- "60003:60002"
networks:
- backend
# MongoDB
mongo:
image: mongo:latest
container_name: mongo
ports:
- "60001:27017"
networks:
- backend
# RabbitMQ
rabbitmq:
image: rabbitmq:3-management
container_name: rabbitmq
command: ["bash","-lc","rabbitmq-plugins enable --offline rabbitmq_prometheus && docker-entrypoint.sh rabbitmq-server"]
ports:
- "5672:5672" # AMQP port
- "15672:15672" # Management UI
- "15692:15692" # Prometheus metrics
environment:
- RABBITMQ_DEFAULT_USER=dimatest
- RABBITMQ_DEFAULT_PASS=testDima34
- RABBITMQ_DEFAULT_VHOST=myhost
networks:
- backend
# OpenTelemetry Collector
otel-collector:
image: otel/opentelemetry-collector-contrib:latest
container_name: otel-collector
command: ["--config=/etc/otel-collector-config.yaml"]
volumes:
- ../Scripts/otel-collector/otel-collector-config.yaml:/etc/otel-collector-config.yaml
ports:
- "8888:8888" # Prometheus metrics
- "8889:8889" # Prometheus exporter metrics
- "13133:13133" # Health check
- "4317:4317" # OTLP gRPC receiver
- "4318:4318" # OTLP HTTP receiver
- "55679:55679" # zpages extension
depends_on:
- prometheus
- jaeger
- elasticsearch
networks:
- backend
# Jaeger (Tracing)
jaeger:
image: jaegertracing/jaeger:2.10.0
container_name: "jaeger"
environment:
- COLLECTOR_OTLP_ENABLED=true
ports:
- "16686:16686" # Jaeger UI
- "14250:14250" # gRPC
- "14268:14268" # HTTP collector
networks:
- backend
# Prometheus (Metrics)
prometheus:
container_name: prometheus
build:
context: ../Scripts/prometheus
ports:
- "9091:9090"
networks:
- backend
# Grafana (Visualization)
grafana:
container_name: grafana
build:
context: ../Scripts/grafana
environment:
- GF_AUTH_ANONYMOUS_ENABLED=true
- GF_AUTH_ANONYMOUS_ORG_ROLE=Admin
- GF_AUTH_DISABLE_LOGIN_FORM=true
depends_on:
- prometheus
- otel-collector
- jaeger
- elasticsearch
ports:
- 3000:3000
networks:
- backend
# Elasticsearch (Logs)
elasticsearch:
image: docker.elastic.co/elasticsearch/elasticsearch:8.16.1
container_name: elasticsearch
hostname: elasticsearch
environment:
- discovery.type=single-node
- bootstrap.memory_lock=true
- xpack.security.enabled=false
ports:
- "9200:9200"
networks:
- backend
volumes:
- esdata:/usr/share/elasticsearch/data
# Kibana (Log Analysis)
kibana:
image: docker.elastic.co/kibana/kibana:8.16.1
container_name: kibana
hostname: kibana
ports:
- "5601:5601"
environment:
- ELASTICSEARCH_HOSTS=http://elasticsearch:9200
- XPACK_SECURITY_ENABLED=false
depends_on:
- elasticsearch
networks:
- backend
volumes:
- esconfig:/usr/share/elasticsearch/config
networks:
backend:
driver: bridge
name: connectsoft.microservicetemplate.application-network
volumes:
esdata:
driver: local
esconfig:
driver: local
httpscerts: {}
docker-compose.override.yml¶
Environment-specific overrides for development:
version: '3.8'
services:
connectsoft.microservicetemplate.application:
environment:
- ASPNETCORE_ENVIRONMENT=Docker
- ASPNETCORE_URLS=http://+:8081;https://+:7279
- ASPNETCORE_Kestrel__Certificates__Default__Password=123456
- ASPNETCORE_Kestrel__Certificates__Default__Path=/https/aspnetapp.pfx
# Wait until the cert is present, then start the app
entrypoint:
- /bin/sh
- -lc
- |
until [ -f /https/aspnetapp.pfx ]; do
echo "waiting for /https/aspnetapp.pfx...";
sleep 0.5;
done;
exec dotnet ConnectSoft.MicroserviceTemplate.Application.dll
ports:
- "8081:8081" # HTTP
- "7279:7279" # HTTPS
healthcheck:
test: ["CMD-SHELL", "curl -f https://127.0.0.1:7279/alive || exit 1"]
interval: 10s
timeout: 5s
retries: 3
start_period: 20s
depends_on:
- certgen
volumes:
- httpscerts:/https:ro
Key Features:
- Environment Variables: Sets ASPNETCORE_ENVIRONMENT=Docker
- Port Mapping: Exposes HTTP (8081) and HTTPS (7279) ports
- Certificate Wait: Waits for certificate to be generated before starting
- Health Check: Configures container health check using /alive endpoint
- Volume Mount: Mounts certificate volume as read-only
.dockerignore¶
The .dockerignore file excludes unnecessary files from the Docker build context:
**/.classpath
**/.dockerignore
**/.env
**/.git
**/.gitignore
**/.project
**/.settings
**/.toolstarget
**/.vs
**/.vscode
**/*.*proj.user
**/*.dbmdl
**/*.jfm
**/azds.yaml
**/bin
**/charts
**/docker-compose*
**/Dockerfile*
**/node_modules
**/npm-debug.log
**/obj
**/secrets.dev.yaml
**/values.dev.yaml
LICENSE
README.md
Benefits:
- Faster Builds: Smaller build context transferred to Docker daemon
- Security: Prevents sensitive files (.env, secrets.dev.yaml) from being included
- Smaller Images: Build artifacts (bin/, obj/) excluded
Container Configuration¶
Environment Detection¶
The application detects containerized environments via:
- Environment Variable:
ASPNETCORE_ENVIRONMENT=Docker - Container Detection:
DOTNET_RUNNING_IN_CONTAINER(automatically set by .NET) - Configuration File:
appsettings.Docker.jsonloaded when environment isDocker
appsettings.Docker.json¶
Container-specific configuration:
{
"Microservice": {
"MicroserviceName": "ConnectSoft.MicroserviceTemplate",
"StartupWarmupSeconds": 20
},
"Kestrel": {
"Endpoints": {
"Http": {
"Url": "http://+:8081"
},
"Https": {
"Url": "https://+:7279",
"Certificate": {
"Path": "/https/aspnetapp.pfx",
"Password": "123456"
}
}
}
},
"ConnectionStrings": {
"ConnectSoft.MicroserviceTemplateSqlServer": "Server=sql,1433;Database=MICROSERVICE_DATABASE;User Id=sa;Password=ConnectSoft123!;MultipleActiveResultSets=true;Encrypt=false;TrustServerCertificate=true;",
"ConnectSoft.MicroserviceTemplateMongoDb": "mongodb://mongo:27017?uuidRepresentation=standard",
"ConnectSoft.MicroserviceTemplateRedisCache": "redis:6379,ConnectRetry=3,KeepAlive=180,name=ConnectSoft.MicroserviceTemplateRedisCache"
},
"Serilog": {
"WriteTo": [
{
"Name": "Seq",
"Args": {
"serverUrl": "http://host.docker.internal:5341"
}
},
{
"Name": "OpenTelemetry",
"Args": {
"endpoint": "http://otel-collector:4317/"
}
}
]
}
}
Key Container-Specific Settings:
- Service Names: Uses container names (e.g., sql, redis, mongo) instead of localhost
- Certificate Path: Points to mounted certificate volume (/https/aspnetapp.pfx)
- Logging: Uses container names for observability services (otel-collector, host.docker.internal)
HTTPS Certificates¶
Certificate Generation¶
Certificates are generated in a separate container and shared via volume:
certgen:
image: mcr.microsoft.com/dotnet/sdk:8.0
container_name: certgen
entrypoint: ["bash","-lc","dotnet dev-certs https -ep /out/aspnetapp.pfx -p 123456"]
volumes:
- httpscerts:/out
restart: "no"
Process:
1. certgen container generates development HTTPS certificate
2. Certificate saved to httpscerts volume at /out/aspnetapp.pfx
3. Application container mounts volume as read-only at /https
Certificate Mounting¶
connectsoft.microservicetemplate.application:
volumes:
- httpscerts:/https:ro
entrypoint:
- /bin/sh
- -lc
- |
until [ -f /https/aspnetapp.pfx ]; do
echo "waiting for /https/aspnetapp.pfx...";
sleep 0.5;
done;
exec dotnet ConnectSoft.MicroserviceTemplate.Application.dll
Features:
- Read-Only Mount: Certificate volume mounted as read-only (:ro)
- Wait Logic: Application waits for certificate before starting
- Kestrel Configuration: Certificate path configured in appsettings.Docker.json
Kestrel Certificate Configuration¶
{
"Kestrel": {
"Endpoints": {
"Https": {
"Url": "https://+:7279",
"Certificate": {
"Path": "/https/aspnetapp.pfx",
"Password": "123456"
}
}
}
}
}
Health Checks¶
Container Health Check¶
Docker Compose health check configuration:
healthcheck:
test: ["CMD-SHELL", "curl -f https://127.0.0.1:7279/alive || exit 1"]
interval: 10s
timeout: 5s
retries: 3
start_period: 20s
Configuration:
- Test: Uses curl to check /alive endpoint (liveness probe)
- Interval: Check every 10 seconds
- Timeout: 5 seconds per check
- Retries: 3 consecutive failures mark container unhealthy
- Start Period: 20 seconds grace period before health checks start
Health Check Endpoints¶
The application exposes health check endpoints:
/health: Overall health check (readiness probe)/live: Liveness probe (container is running)/ready: Readiness probe (application is ready to accept traffic)
See Health Checks for detailed documentation.
Networking¶
Docker Network¶
All services run on a shared bridge network:
Benefits: - Service Discovery: Containers can communicate using service names - Isolation: Services isolated from other Docker networks - DNS Resolution: Automatic DNS resolution for service names
Service Communication¶
Containers communicate using service names:
ConnectionStrings:
ConnectSoft.MicroserviceTemplateSqlServer: "Server=sql,1433;..."
ConnectSoft.MicroserviceTemplateMongoDb: "mongodb://mongo:27017..."
ConnectSoft.MicroserviceTemplateRedisCache: "redis:6379,..."
Service Name Resolution:
- sql → SQL Server container
- mongo → MongoDB container
- redis → Redis container
- rabbitmq → RabbitMQ container
- otel-collector → OpenTelemetry Collector container
Volumes¶
Persistent Volumes¶
Volume Types:
- Named Volumes: esdata, esconfig for persistent data
- Anonymous Volumes: httpscerts for temporary certificate storage
Use Cases: - Elasticsearch Data: Persistent storage for Elasticsearch indices - Certificates: Shared certificate storage between containers - Configuration: Shared configuration files (if needed)
Building and Running¶
Building the Image¶
# Build from Dockerfile
docker build -f ConnectSoft.MicroserviceTemplate.Application/Dockerfile -t connectsoft.microservicetemplate.application .
# Build using Docker Compose
docker-compose build
# Build without cache
docker-compose build --no-cache
Running with Docker Compose¶
# Start all services
docker-compose up
# Start in detached mode
docker-compose up -d
# Start specific services
docker-compose up sql redis mongo
# Stop all services
docker-compose down
# Stop and remove volumes
docker-compose down -v
Viewing Logs¶
# View all logs
docker-compose logs
# View logs for specific service
docker-compose logs connectsoft.microservicetemplate.application
# Follow logs
docker-compose logs -f
# View last 100 lines
docker-compose logs --tail=100
Container Management¶
# List running containers
docker-compose ps
# Execute command in container
docker-compose exec connectsoft.microservicetemplate.application bash
# Inspect container
docker inspect connectsoft.microservicetemplate.application
# View container logs
docker logs connectsoft.microservicetemplate.application
# Stop container
docker-compose stop connectsoft.microservicetemplate.application
# Restart container
docker-compose restart connectsoft.microservicetemplate.application
Best Practices¶
Do's¶
-
Use Multi-Stage Builds
-
Pin Base Image Versions
-
Optimize Layer Caching
-
Use .dockerignore
-
Configure Health Checks
-
Use Named Networks
Don'ts¶
-
Don't Include Secrets in Images
-
Don't Use Latest Tags in Production
-
Don't Run as Root
-
Don't Expose Unnecessary Ports
-
Don't Copy Everything
Troubleshooting¶
Issue: Container Fails to Start¶
Symptom: Container exits immediately after starting.
Solutions:
1. Check container logs: docker logs <container-name>
2. Verify environment variables: docker inspect <container-name>
3. Check health check: docker inspect <container-name> | grep -A 10 Health
4. Verify dependencies: Ensure dependent services are running
Issue: Cannot Connect to Database¶
Symptom: Application cannot connect to SQL Server/MongoDB/Redis.
Solutions:
1. Verify service names match connection strings
2. Check network connectivity: docker-compose exec <service> ping <target-service>
3. Verify ports are exposed: docker-compose ps
4. Check service logs: docker-compose logs <service>
Issue: Certificate Not Found¶
Symptom: HTTPS endpoint fails with certificate error.
Solutions:
1. Verify certificate generation: docker-compose logs certgen
2. Check volume mount: docker inspect <container> | grep -A 5 Mounts
3. Verify certificate path in appsettings.Docker.json
4. Check wait logic in entrypoint script
Issue: Build Takes Too Long¶
Symptom: Docker build is slow.
Solutions:
1. Use .dockerignore to exclude unnecessary files
2. Optimize layer caching (copy dependency files first)
3. Use BuildKit: DOCKER_BUILDKIT=1 docker build ...
4. Use multi-stage builds to reduce final image size
Issue: Out of Memory¶
Symptom: Containers are killed or fail with memory errors.
Solutions:
1. Set memory limits: docker-compose.yml → deploy.resources.limits.memory
2. Optimize image size (use multi-stage builds)
3. Check service memory usage: docker stats
4. Adjust service memory limits individually
Security Considerations¶
Image Security¶
- Base Image Scanning: Regularly scan base images for vulnerabilities
- Minimal Base Images: Use minimal runtime images (not SDK)
- No Secrets in Images: Never include secrets, passwords, or API keys
- Regular Updates: Keep base images updated with security patches
Container Security¶
- Read-Only Volumes: Mount volumes as read-only when possible (
:ro) - Non-Root User: Run containers as non-root user (if base image supports)
- Resource Limits: Set CPU and memory limits
- Network Isolation: Use Docker networks to isolate services
Secrets Management¶
- Environment Variables: Use Docker secrets or environment variables
- Secret Managers: Integrate with Azure Key Vault, HashiCorp Vault
- Never Commit Secrets: Secrets should never be in source control
- Rotation: Regularly rotate secrets and certificates
Production Deployment¶
Image Tagging¶
Tag images with version numbers:
docker build -t connectsoft.microservicetemplate.application:1.0.0 .
docker tag connectsoft.microservicetemplate.application:1.0.0 \
registry.example.com/connectsoft.microservicetemplate.application:1.0.0
Container Registry¶
Push images to container registry:
Kubernetes Deployment¶
For Kubernetes deployments, see Kubernetes documentation. The containerized application is ready for Kubernetes deployment.
Summary¶
The ConnectSoft Microservice Template provides:
- ✅ Multi-Stage Dockerfile: Optimized builds with minimal final image size
- ✅ Docker Compose Orchestration: Complete application stack with dependencies
- ✅ Health Checks: Container health monitoring
- ✅ HTTPS Support: Certificate generation and mounting
- ✅ Service Discovery: Docker network-based service communication
- ✅ Environment Configuration: Container-specific settings via
appsettings.Docker.json - ✅ Security Best Practices: Minimal images, no secrets, network isolation
- ✅ Production Ready: Optimized for production deployments
By following these patterns, teams can:
- Build Efficiently: Optimized layer caching and multi-stage builds
- Deploy Consistently: Same containerized application across environments
- Scale Easily: Container orchestration with Kubernetes or Docker Swarm
- Monitor Effectively: Health checks and observability integration
- Secure Properly: Security best practices and secrets management
The containerization strategy enables teams to build, ship, and run microservices consistently across any environment that supports Docker.