11 KiB
Docker Guide
This document covers Docker deployment, configuration, and troubleshooting for The Biergarten App.
Overview
The project uses Docker Compose to orchestrate multiple services:
- SQL Server 2022 database
- Database migrations runner (DbUp)
- Database seeder
- .NET API
- Test runners
See the deployment diagram for visual representation.
Docker Compose Environments
1. Development (docker-compose.dev.yaml)
Purpose: Local development with persistent data
Features:
- Persistent SQL Server volume
- Hot reload support
- Swagger UI enabled
- Seed data included
CLEAR_DATABASE=true(drops and recreates schema)
Services:
sqlserver # SQL Server 2022 (port 1433)
database.migrations # DbUp migrations
database.seed # Seed initial data
api.core # Web API (ports 8080, 8081)
Start Development Environment:
docker compose -f docker-compose.dev.yaml up -d
Access:
- API Swagger: http://localhost:8080/swagger
- Health Check: http://localhost:8080/health
- SQL Server: localhost:1433 (sa credentials from .env.dev)
Stop Environment:
# Stop services (keep volumes)
docker compose -f docker-compose.dev.yaml down
# Stop and remove volumes (fresh start)
docker compose -f docker-compose.dev.yaml down -v
2. Testing (docker-compose.test.yaml)
Purpose: Automated CI/CD testing in isolated environment
Features:
- Fresh database each run
- All test suites execute in parallel
- Test results exported to
./test-results/ - Containers auto-exit after completion
- Fully isolated testnet network
Services:
sqlserver # Test database
database.migrations # Fresh schema
database.seed # Test data
api.specs # Reqnroll BDD tests
repository.tests # Repository unit tests
service.auth.tests # Service unit tests
Run Tests:
# Run all tests
docker compose -f docker-compose.test.yaml up --abort-on-container-exit
# View results
ls -la test-results/
cat test-results/api-specs/results.trx
cat test-results/repository-tests/results.trx
cat test-results/service-auth-tests/results.trx
# Clean up
docker compose -f docker-compose.test.yaml down -v
3. Production (docker-compose.prod.yaml)
Purpose: Production-ready deployment
Features:
- Production logging levels
- No database clearing
- Optimized build configurations
- Health checks enabled
- Restart policies (unless-stopped)
- Security hardening
Services:
sqlserver # Production SQL Server
database.migrations # Schema updates only
api.core # Production API
Deploy Production:
docker compose -f docker-compose.prod.yaml up -d
Note: In real production, use orchestration platforms (Kubernetes, ECS, etc.).
Service Dependencies
Docker Compose manages startup order using health checks:
sqlserver (health check)
↓
database.migrations (completes successfully)
↓
database.seed (completes successfully)
↓
api.core / tests (start when ready)
Health Check Example (SQL Server):
healthcheck:
test: ["CMD-SHELL", "sqlcmd -S localhost -U sa -P '${DB_PASSWORD}' -C -Q 'SELECT 1'"]
interval: 10s
timeout: 5s
retries: 12
start_period: 30s
Dependency Configuration:
api.core:
depends_on:
database.seed:
condition: service_completed_successfully
Volumes
Persistent Volumes
Development:
sqlserverdata-dev- Database files persist between restartsnuget-cache-dev- NuGet package cache (speeds up builds)
Testing:
sqlserverdata-test- Temporary, typically removed after tests
Production:
sqlserverdata-prod- Production database filesnuget-cache-prod- Production NuGet cache
Mounted Volumes
Test Results:
volumes:
- ./test-results:/app/test-results
Test results are written to host filesystem for CI/CD integration.
Code Volumes (development only):
volumes:
- ./src:/app/src # Hot reload for development
Networks
Each environment uses isolated bridge networks:
devnet- Development networktestnet- Testing network (fully isolated)prodnet- Production network
This prevents cross-environment communication and improves security.
Environment Variables
All containers are configured via environment variables from .env files:
env_file: ".env.dev" # or .env.test, .env.prod
environment:
ASPNETCORE_ENVIRONMENT: "Development"
DOTNET_RUNNING_IN_CONTAINER: "true"
DB_SERVER: "${DB_SERVER}"
DB_NAME: "${DB_NAME}"
DB_USER: "${DB_USER}"
DB_PASSWORD: "${DB_PASSWORD}"
JWT_SECRET: "${JWT_SECRET}"
For complete list, see Environment Variables.
Common Commands
View Services
# Running services
docker compose -f docker-compose.dev.yaml ps
# All containers (including stopped)
docker ps -a
View Logs
# All services
docker compose -f docker-compose.dev.yaml logs -f
# Specific service
docker compose -f docker-compose.dev.yaml logs -f api.core
# Last 100 lines
docker compose -f docker-compose.dev.yaml logs --tail=100 api.core
Execute Commands in Container
# Interactive shell
docker exec -it dev-env-api-core bash
# Run command
docker exec dev-env-sqlserver /opt/mssql-tools18/bin/sqlcmd -S localhost -U sa -P 'password' -C
Restart Services
# Restart all services
docker compose -f docker-compose.dev.yaml restart
# Restart specific service
docker compose -f docker-compose.dev.yaml restart api.core
# Rebuild and restart
docker compose -f docker-compose.dev.yaml up -d --build api.core
Build Images
# Build all images
docker compose -f docker-compose.dev.yaml build
# Build specific service
docker compose -f docker-compose.dev.yaml build api.core
# Build without cache
docker compose -f docker-compose.dev.yaml build --no-cache
Clean Up
# Stop and remove containers
docker compose -f docker-compose.dev.yaml down
# Remove containers and volumes
docker compose -f docker-compose.dev.yaml down -v
# Remove containers, volumes, and images
docker compose -f docker-compose.dev.yaml down -v --rmi all
# System-wide cleanup
docker system prune -af --volumes
Dockerfile Structure
Multi-Stage Build
All service Dockerfiles use multi-stage builds:
# Stage 1: Build
FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build
WORKDIR /src
COPY ["Project/Project.csproj", "Project/"]
RUN dotnet restore
COPY . .
RUN dotnet build -c Release
# Stage 2: Runtime
FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS final
WORKDIR /app
COPY --from=build /app/build .
ENTRYPOINT ["dotnet", "Project.dll"]
Benefits:
- Smaller final images (no SDK)
- Cached layers speed up builds
- Separation of build and runtime dependencies
Troubleshooting
Port Already in Use
Problem: Port 8080 or 1433 already bound
Solution:
# Find process using port
lsof -ti:8080
lsof -ti:1433
# Kill process
kill -9 $(lsof -ti:8080)
# Or change port in docker-compose.yaml
ports:
- "8081:8080" # Map to different host port
Container Won't Start
Problem: Container exits immediately
Solution:
# View logs
docker compose -f docker-compose.dev.yaml logs <service-name>
# Check container status
docker compose -f docker-compose.dev.yaml ps
# Inspect container
docker inspect <container-name>
Database Connection Failed
Problem: API can't connect to SQL Server
Check:
-
SQL Server container is running:
docker compose -f docker-compose.dev.yaml ps sqlserver -
Health check is passing:
docker inspect dev-env-sqlserver | grep -A 10 Health -
Connection string is correct in
.envfile -
SQL Server is accepting connections:
docker exec dev-env-sqlserver /opt/mssql-tools18/bin/sqlcmd -S localhost -U sa -P 'password' -C -Q "SELECT 1"
Out of Disk Space
Problem: No space left on device
Solution:
# Check Docker disk usage
docker system df
# Remove unused data
docker system prune -af --volumes
# Remove specific volumes
docker volume ls
docker volume rm <volume-name>
SQL Server Container Unhealthy
Problem: Health check failing
Reasons:
- Incorrect password
- Container still starting up
- Insufficient memory
Solution:
# Check logs
docker compose -f docker-compose.dev.yaml logs sqlserver
# Verify password matches .env file
grep DB_PASSWORD .env.dev
# Increase memory in Docker Desktop settings (min 4GB recommended)
Build Failures
Problem: Docker build fails
Common Causes:
- Missing project references
- Incorrect COPY paths
- Network issues during restore
Solution:
# Build without cache
docker compose -f docker-compose.dev.yaml build --no-cache
# Check Dockerfile COPY paths match project structure
# Ensure all .csproj files are in correct locations
Test Results Not Appearing
Problem: test-results/ folder is empty
Solution:
# Ensure folder has write permissions
chmod -R 755 test-results/
# Check test container logs
docker compose -f docker-compose.test.yaml logs api.specs
# Verify volume mount
docker compose -f docker-compose.test.yaml config | grep -A 5 volumes
Performance Optimization
Build Cache
Leverage Docker layer caching:
- Copy
.csprojfiles first - Run
dotnet restore - Copy source code
- Build application
This allows dependency layer caching when only source changes.
Volume Mounts for Development
Use NuGet cache volume for faster rebuilds:
volumes:
- nuget-cache-dev:/root/.nuget/packages
Resource Limits
Set memory/CPU limits for containers:
deploy:
resources:
limits:
memory: 2GB
cpus: '1.0'
CI/CD Integration
GitHub Actions Example
name: Run Tests
on: [push]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Run tests
run: |
docker compose -f docker-compose.test.yaml build
docker compose -f docker-compose.test.yaml up --abort-on-container-exit
- name: Upload test results
uses: actions/upload-artifact@v3
with:
name: test-results
path: test-results/
Security Best Practices
- Don't commit
.envfiles - Use.env.exampleas template - Use secrets management - For production credentials
- Run as non-root user - Configure USER in Dockerfile
- Scan images - Use
docker scanor Trivy - Keep base images updated - Regularly update FROM images
- Minimize installed packages - Only install what's needed
- Use specific tags - Avoid
latesttag