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

511 lines
11 KiB
Markdown

# 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](diagrams/pdf/deployment.pdf) 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**:
```yaml
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**:
```bash
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**:
```bash
# 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**:
```yaml
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**:
```bash
# 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**:
```yaml
sqlserver # Production SQL Server
database.migrations # Schema updates only
api.core # Production API
```
**Deploy Production**:
```bash
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:
```mermaid
sqlserver (health check)
database.migrations (completes successfully)
database.seed (completes successfully)
api.core / tests (start when ready)
```
**Health Check Example** (SQL Server):
```yaml
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**:
```yaml
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**:
```yaml
volumes:
- ./test-results:/app/test-results
```
Test results are written to host filesystem for CI/CD integration.
**Code Volumes** (development only):
```yaml
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:
```yaml
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](environment-variables.md).
## Common Commands
### View Services
```bash
# Running services
docker compose -f docker-compose.dev.yaml ps
# All containers (including stopped)
docker ps -a
```
### View Logs
```bash
# 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
```bash
# 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
```bash
# 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
```bash
# 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
```bash
# 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:
```dockerfile
# 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**:
```bash
# 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**:
```bash
# 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:
```bash
docker compose -f docker-compose.dev.yaml ps sqlserver
```
2. Health check is passing:
```bash
docker inspect dev-env-sqlserver | grep -A 10 Health
```
3. Connection string is correct in `.env` file
4. SQL Server is accepting connections:
```bash
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**:
```bash
# 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**:
```bash
# 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**:
```bash
# 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**:
```bash
# 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:
```yaml
volumes:
- nuget-cache-dev:/root/.nuget/packages
```
### Resource Limits
Set memory/CPU limits for containers:
```yaml
deploy:
resources:
limits:
memory: 2GB
cpus: '1.0'
```
## CI/CD Integration
### GitHub Actions Example
```yaml
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
- [Docker Compose Documentation](https://docs.docker.com/compose/)
- [.NET Docker Images](https://hub.docker.com/_/microsoft-dotnet)
- [SQL Server Docker Images](https://hub.docker.com/_/microsoft-mssql-server)