Files
the-biergarten-app/docs/docker.md
2026-02-15 21:13:07 -05:00

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:

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 restarts
  • nuget-cache-dev - NuGet package cache (speeds up builds)

Testing:

  • sqlserverdata-test - Temporary, typically removed after tests

Production:

  • sqlserverdata-prod - Production database files
  • nuget-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 network
  • testnet - 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:

  1. SQL Server container is running:

    docker compose -f docker-compose.dev.yaml ps sqlserver
    
  2. Health check is passing:

    docker inspect dev-env-sqlserver | grep -A 10 Health
    
  3. Connection string is correct in .env file

  4. 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:

  1. Copy .csproj files first
  2. Run dotnet restore
  3. Copy source code
  4. 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

  1. Don't commit .env files - Use .env.example as template
  2. Use secrets management - For production credentials
  3. Run as non-root user - Configure USER in Dockerfile
  4. Scan images - Use docker scan or Trivy
  5. Keep base images updated - Regularly update FROM images
  6. Minimize installed packages - Only install what's needed
  7. Use specific tags - Avoid latest tag

Additional Resources