# Docker & Traefik - Detailed Reference

Detailed configurations và examples cho Docker và Traefik trong GoodGo.

## Table of Contents

1. [Dockerfile Patterns](#dockerfile-patterns)
2. [Docker Compose Configurations](#docker-compose-configurations)
3. [Traefik Configuration](#traefik-configuration)
4. [SSL/TLS Setup](#ssltls-setup)
5. [Load Balancing](#load-balancing)
6. [Middleware Examples](#middleware-examples)
7. [Development Environment](#development-environment)

---

## Dockerfile Patterns

### Optimized .NET Dockerfile

```dockerfile
# ===================================
# Stage 1: Build
# ===================================
FROM mcr.microsoft.com/dotnet/sdk:8.0-alpine AS build
WORKDIR /src

# EN: Copy only project files first for better caching
# VI: Copy chỉ project files trước để cache tốt hơn
COPY services/iam-service-net/*.slnx ./
COPY services/iam-service-net/src/IamService.API/*.csproj src/IamService.API/
COPY services/iam-service-net/src/IamService.Domain/*.csproj src/IamService.Domain/
COPY services/iam-service-net/src/IamService.Infrastructure/*.csproj src/IamService.Infrastructure/

# EN: Restore dependencies
# VI: Restore dependencies
RUN dotnet restore src/IamService.API

# EN: Copy remaining source files
# VI: Copy các source files còn lại
COPY services/iam-service-net/src/ src/

# EN: Build and publish
# VI: Build và publish
RUN dotnet publish src/IamService.API \
    -c Release \
    -o /app \
    --no-restore \
    /p:PublishTrimmed=false \
    /p:PublishSingleFile=false

# ===================================
# Stage 2: Runtime
# ===================================
FROM mcr.microsoft.com/dotnet/aspnet:8.0-alpine AS runtime

# EN: Install required packages
# VI: Cài đặt packages cần thiết
RUN apk add --no-cache \
    icu-libs \
    tzdata \
    && rm -rf /var/cache/apk/*

# EN: Set timezone
# VI: Đặt timezone
ENV TZ=Asia/Ho_Chi_Minh

# EN: Create non-root user
# VI: Tạo user không phải root
RUN addgroup -g 1000 -S appgroup && \
    adduser -u 1000 -S appuser -G appgroup

WORKDIR /app

# EN: Copy published files with correct ownership
# VI: Copy files đã publish với ownership đúng
COPY --from=build --chown=appuser:appgroup /app .

# EN: Switch to non-root user
# VI: Chuyển sang user không phải root
USER appuser

# EN: Environment configuration
# VI: Cấu hình môi trường
ENV ASPNETCORE_URLS=http://+:8080 \
    ASPNETCORE_ENVIRONMENT=Production \
    DOTNET_RUNNING_IN_CONTAINER=true \
    DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=false

EXPOSE 8080

# EN: Health check
# VI: Kiểm tra sức khỏe
HEALTHCHECK --interval=30s --timeout=3s --start-period=10s --retries=3 \
    CMD wget --no-verbose --tries=1 --spider http://localhost:8080/health/live || exit 1

ENTRYPOINT ["dotnet", "IamService.API.dll"]
```

### .dockerignore

```
# EN: Git / VI: Git
.git
.gitignore

# EN: Build outputs / VI: Build outputs
**/bin/
**/obj/
**/out/

# EN: IDE / VI: IDE
.vs/
.vscode/
.idea/
*.swp
*.user

# EN: Test results / VI: Kết quả test
**/TestResults/
**/coverage/

# EN: Docker / VI: Docker
**/Dockerfile*
**/docker-compose*
**/.dockerignore

# EN: Documentation / VI: Tài liệu
**/README.md
**/docs/

# EN: Secrets / VI: Secrets
**/*.env
**/appsettings.Development.json
**/appsettings.Local.json
```

---

## Docker Compose Configurations

### Complete Development Stack

```yaml
# deployments/local/docker-compose.yml
version: "3.8"

services:
  # ===================================
  # TRAEFIK - API Gateway
  # ===================================
  traefik:
    image: traefik:v3.0
    container_name: traefik
    restart: unless-stopped
    command:
      # EN: API and Dashboard
      - "--api.dashboard=true"
      - "--api.insecure=true"
      
      # EN: Docker provider
      - "--providers.docker=true"
      - "--providers.docker.exposedbydefault=false"
      - "--providers.docker.network=goodgo-network"
      
      # EN: Entrypoints
      - "--entrypoints.web.address=:80"
      - "--entrypoints.websecure.address=:443"
      
      # EN: Logging
      - "--log.level=INFO"
      - "--accesslog=true"
      - "--accesslog.format=json"
      
      # EN: Metrics
      - "--metrics.prometheus=true"
      - "--metrics.prometheus.buckets=0.1,0.3,1.2,5.0"
    ports:
      - "80:80"
      - "443:443"
      - "8080:8080"
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - ./traefik/certs:/certs:ro
    networks:
      - goodgo-network
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.traefik.rule=Host(`traefik.localhost`)"
      - "traefik.http.routers.traefik.service=api@internal"

  # ===================================
  # IAM SERVICE
  # ===================================
  iam-service-net:
    build:
      context: ../..
      dockerfile: services/iam-service-net/Dockerfile
    container_name: iam-service-net
    restart: unless-stopped
    environment:
      - ASPNETCORE_ENVIRONMENT=Development
      - ConnectionStrings__DefaultConnection=${IAM_DATABASE_URL}
      - Jwt__Authority=http://iam-service-net:8080
      - Jwt__Audience=goodgo-api
    labels:
      - "traefik.enable=true"
      # EN: HTTP Router
      - "traefik.http.routers.iam-service-net.rule=PathPrefix(`/api/v1/iam`)"
      - "traefik.http.routers.iam-service-net.entrypoints=web"
      - "traefik.http.routers.iam-service-net.middlewares=iam-headers"
      # EN: Service
      - "traefik.http.services.iam-service-net.loadbalancer.server.port=8080"
      - "traefik.http.services.iam-service-net.loadbalancer.healthcheck.path=/health/live"
      - "traefik.http.services.iam-service-net.loadbalancer.healthcheck.interval=10s"
      # EN: Response headers middleware
      - "traefik.http.middlewares.iam-headers.headers.customresponseheaders.X-Service-Name=iam-service"
    networks:
      - goodgo-network
    depends_on:
      postgres:
        condition: service_healthy

  # ===================================
  # STORAGE SERVICE
  # ===================================
  storage-service-net:
    build:
      context: ../..
      dockerfile: services/storage-service-net/Dockerfile
    container_name: storage-service-net
    restart: unless-stopped
    environment:
      - ASPNETCORE_ENVIRONMENT=Development
      - ConnectionStrings__DefaultConnection=${STORAGE_DATABASE_URL}
      - MinIO__Endpoint=minio:9000
      - MinIO__AccessKey=${MINIO_ACCESS_KEY}
      - MinIO__SecretKey=${MINIO_SECRET_KEY}
      - MinIO__UseSSL=false
      - IamService__BaseUrl=http://iam-service-net:8080
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.storage-service-net.rule=PathPrefix(`/api/v1/storage`)"
      - "traefik.http.routers.storage-service-net.entrypoints=web"
      - "traefik.http.services.storage-service-net.loadbalancer.server.port=8080"
      - "traefik.http.services.storage-service-net.loadbalancer.healthcheck.path=/health/live"
    networks:
      - goodgo-network
    depends_on:
      postgres:
        condition: service_healthy
      minio:
        condition: service_started

  # ===================================
  # POSTGRESQL
  # ===================================
  postgres:
    image: postgres:15-alpine
    container_name: postgres
    restart: unless-stopped
    environment:
      - POSTGRES_USER=postgres
      - POSTGRES_PASSWORD=postgres
      - POSTGRES_MULTIPLE_DATABASES=iam_db,storage_db,wallet_db
    volumes:
      - postgres_data:/var/lib/postgresql/data
      - ./scripts/create-multiple-dbs.sh:/docker-entrypoint-initdb.d/create-multiple-dbs.sh
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 5s
      timeout: 5s
      retries: 5
    networks:
      - goodgo-network

  # ===================================
  # REDIS
  # ===================================
  redis:
    image: redis:7-alpine
    container_name: redis
    restart: unless-stopped
    command: redis-server --appendonly yes
    volumes:
      - redis_data:/data
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 5s
      timeout: 3s
      retries: 5
    networks:
      - goodgo-network

  # ===================================
  # MINIO (S3-compatible)
  # ===================================
  minio:
    image: minio/minio:latest
    container_name: minio
    restart: unless-stopped
    command: server /data --console-address ":9001"
    environment:
      - MINIO_ROOT_USER=minioadmin
      - MINIO_ROOT_PASSWORD=minioadmin
    volumes:
      - minio_data:/data
    ports:
      - "9000:9000"
      - "9001:9001"
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"]
      interval: 30s
      timeout: 10s
      retries: 3
    networks:
      - goodgo-network
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.minio-console.rule=Host(`minio.localhost`)"
      - "traefik.http.routers.minio-console.service=minio-console"
      - "traefik.http.services.minio-console.loadbalancer.server.port=9001"

networks:
  goodgo-network:
    driver: bridge
    name: goodgo-network

volumes:
  postgres_data:
  redis_data:
  minio_data:
```

### Multi-Database Init Script

```bash
#!/bin/bash
# scripts/create-multiple-dbs.sh

set -e
set -u

function create_user_and_database() {
    local database=$1
    echo "  Creating database '$database'"
    psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" <<-EOSQL
        CREATE DATABASE $database;
        GRANT ALL PRIVILEGES ON DATABASE $database TO $POSTGRES_USER;
EOSQL
}

if [ -n "$POSTGRES_MULTIPLE_DATABASES" ]; then
    echo "Multiple database creation requested: $POSTGRES_MULTIPLE_DATABASES"
    for db in $(echo $POSTGRES_MULTIPLE_DATABASES | tr ',' ' '); do
        create_user_and_database $db
    done
    echo "Multiple databases created"
fi
```

---

## Traefik Configuration

### Static Configuration (traefik.yml)

```yaml
# infra/traefik/traefik.yml

# EN: API and Dashboard
# VI: API và Dashboard
api:
  dashboard: true
  insecure: false

# EN: Entrypoints
# VI: Các điểm vào
entryPoints:
  web:
    address: ":80"
    http:
      redirections:
        entryPoint:
          to: websecure
          scheme: https
  websecure:
    address: ":443"
    http:
      tls:
        certResolver: letsencrypt

# EN: Certificate resolvers
# VI: Bộ giải quyết chứng chỉ
certificatesResolvers:
  letsencrypt:
    acme:
      email: admin@goodgo.vn
      storage: /certs/acme.json
      httpChallenge:
        entryPoint: web

# EN: Providers
# VI: Providers
providers:
  docker:
    exposedByDefault: false
    network: goodgo-network
  file:
    directory: /etc/traefik/dynamic
    watch: true

# EN: Logging
# VI: Logging
log:
  level: INFO
  format: json

accessLog:
  format: json
  filters:
    statusCodes:
      - "400-599"

# EN: Metrics
# VI: Metrics
metrics:
  prometheus:
    buckets:
      - 0.1
      - 0.3
      - 1.2
      - 5.0
```

### Dynamic Configuration

```yaml
# infra/traefik/dynamic/middlewares.yml

http:
  middlewares:
    # EN: Security headers
    # VI: Headers bảo mật
    secure-headers:
      headers:
        frameDeny: true
        sslRedirect: true
        browserXssFilter: true
        contentTypeNosniff: true
        forceSTSHeader: true
        stsIncludeSubdomains: true
        stsPreload: true
        stsSeconds: 31536000
        customResponseHeaders:
          X-Robots-Tag: "noindex,nofollow"

    # EN: Rate limiting
    # VI: Giới hạn rate
    rate-limit:
      rateLimit:
        average: 100
        burst: 50
        period: 1m

    # EN: Compression
    # VI: Nén
    compress:
      compress: {}

    # EN: CORS
    # VI: CORS
    cors:
      headers:
        accessControlAllowMethods:
          - GET
          - POST
          - PUT
          - DELETE
          - OPTIONS
        accessControlAllowHeaders:
          - "*"
        accessControlAllowOriginList:
          - "https://app.goodgo.vn"
          - "http://localhost:3000"
        accessControlMaxAge: 100
        addVaryHeader: true
```

---

## SSL/TLS Setup

### Local Development with mkcert

```bash
# EN: Install mkcert / VI: Cài đặt mkcert
brew install mkcert
mkcert -install

# EN: Generate certificates / VI: Tạo certificates
cd infra/traefik/certs
mkcert "*.localhost" localhost 127.0.0.1 ::1
```

### Traefik with Local Certs

```yaml
# docker-compose.override.yml for local HTTPS
services:
  traefik:
    volumes:
      - ./infra/traefik/certs:/certs:ro
    labels:
      - "traefik.http.routers.traefik.tls=true"

# Dynamic config: infra/traefik/dynamic/tls.yml
tls:
  certificates:
    - certFile: /certs/_wildcard.localhost.pem
      keyFile: /certs/_wildcard.localhost-key.pem
  stores:
    default:
      defaultCertificate:
        certFile: /certs/_wildcard.localhost.pem
        keyFile: /certs/_wildcard.localhost-key.pem
```

---

## Load Balancing

### Multiple Instances

```yaml
services:
  iam-service-net:
    deploy:
      replicas: 3
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.iam.rule=PathPrefix(`/api/v1/iam`)"
      - "traefik.http.services.iam.loadbalancer.server.port=8080"
      # EN: Sticky sessions (optional)
      # VI: Sticky sessions (tùy chọn)
      - "traefik.http.services.iam.loadbalancer.sticky.cookie.name=iam_sticky"
      - "traefik.http.services.iam.loadbalancer.sticky.cookie.secure=true"
      # EN: Health check
      - "traefik.http.services.iam.loadbalancer.healthcheck.path=/health/live"
      - "traefik.http.services.iam.loadbalancer.healthcheck.interval=5s"
```

---

## Resources / Tài Nguyên

- [Traefik Documentation](https://doc.traefik.io/traefik/)
- [Docker Best Practices](https://docs.docker.com/develop/develop-images/dockerfile_best-practices/)
- [.NET Docker Images](https://hub.docker.com/_/microsoft-dotnet)
- [Docker Compose Specification](https://docs.docker.com/compose/compose-file/)
