# Container Specialist

Especialista em containerização com Docker e deploy para Azure Container Apps.

## Responsabilidades

1. **Criar Dockerfiles** otimizados para .NET
2. **Configurar Container Apps** no Azure
3. **Gerenciar registries** (ACR, GHCR)
4. **Otimizar imagens** para tamanho e performance

## Triggers

Keywords: `docker`, `container`, `containerize`, `container apps`, `acr`, `registry`, `image`

## Docker para .NET

### Dockerfile Multi-stage Otimizado

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

# Copy csproj files first (cache de restore)
COPY ["src/Web/Web.csproj", "src/Web/"]
COPY ["src/Application/Application.csproj", "src/Application/"]
COPY ["src/Domain/Domain.csproj", "src/Domain/"]
COPY ["src/Infrastructure/Infrastructure.csproj", "src/Infrastructure/"]

# Restore
RUN dotnet restore "src/Web/Web.csproj"

# Copy everything
COPY . .

# Build
WORKDIR "/src/src/Web"
RUN dotnet build "Web.csproj" -c Release -o /app/build

# Stage 2: Publish
FROM build AS publish
RUN dotnet publish "Web.csproj" -c Release -o /app/publish \
    /p:UseAppHost=false \
    /p:PublishTrimmed=false

# Stage 3: Runtime
FROM mcr.microsoft.com/dotnet/aspnet:9.0-alpine AS final
WORKDIR /app

# Security: non-root user
RUN addgroup -g 1000 appgroup && \
    adduser -u 1000 -G appgroup -D appuser

# Copy published app
COPY --from=publish /app/publish .

# Set ownership
RUN chown -R appuser:appgroup /app

USER appuser

# Expose port
EXPOSE 8080

# Health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
    CMD wget --quiet --tries=1 --spider http://localhost:8080/health || exit 1

# Entry point
ENTRYPOINT ["dotnet", "Web.dll"]
```

### .dockerignore

```
# .dockerignore
**/.git
**/.vs
**/.vscode
**/bin
**/obj
**/node_modules
**/.idea
*.md
*.txt
Dockerfile*
docker-compose*
.dockerignore
.gitignore
.editorconfig
**/*.user
**/*.suo
**/TestResults
```

### Docker Compose para Dev

```yaml
# docker-compose.yml
services:
  web:
    build:
      context: .
      dockerfile: Dockerfile
    ports:
      - "8080:8080"
    environment:
      - ASPNETCORE_ENVIRONMENT=Development
      - ConnectionStrings__DefaultConnection=Server=db;Database=App;User=sa;Password=YourStrong!Passw0rd;TrustServerCertificate=true
    depends_on:
      db:
        condition: service_healthy
    networks:
      - app-network

  db:
    image: mcr.microsoft.com/mssql/server:2022-latest
    environment:
      - ACCEPT_EULA=Y
      - SA_PASSWORD=YourStrong!Passw0rd
    ports:
      - "1433:1433"
    volumes:
      - sqldata:/var/opt/mssql
    healthcheck:
      test: /opt/mssql-tools18/bin/sqlcmd -S localhost -U sa -P "YourStrong!Passw0rd" -Q "SELECT 1" -C
      interval: 10s
      timeout: 3s
      retries: 10
    networks:
      - app-network

  redis:
    image: redis:7-alpine
    ports:
      - "6379:6379"
    networks:
      - app-network

networks:
  app-network:
    driver: bridge

volumes:
  sqldata:
```

## Azure Container Registry (ACR)

### Criar ACR via Bicep

```bicep
// modules/acr.bicep
@description('ACR name')
param name string

@description('Location')
param location string

@description('Tags')
param tags object = {}

@description('SKU')
@allowed(['Basic', 'Standard', 'Premium'])
param sku string = 'Basic'

resource acr 'Microsoft.ContainerRegistry/registries@2023-07-01' = {
  name: name
  location: location
  tags: tags
  sku: {
    name: sku
  }
  properties: {
    adminUserEnabled: true
    publicNetworkAccess: 'Enabled'
  }
}

output loginServer string = acr.properties.loginServer
output name string = acr.name
```

### Commands ACR

```bash
# Criar ACR
az acr create --resource-group rg-myapp --name myappacr --sku Basic

# Login
az acr login --name myappacr

# Build e push
az acr build --registry myappacr --image myapp:v1 .

# Listar imagens
az acr repository list --name myappacr

# Listar tags
az acr repository show-tags --name myappacr --repository myapp
```

## Azure Container Apps

### Deploy via Bicep

```bicep
// modules/container-app.bicep
@description('App name')
param name string

@description('Location')
param location string

@description('Tags')
param tags object = {}

@description('Container App Environment ID')
param environmentId string

@description('Container image')
param containerImage string

@description('Registry server')
param registryServer string

@description('Registry username')
param registryUsername string

@description('Registry password')
@secure()
param registryPassword string

@description('Environment variables')
param envVars array = []

resource containerApp 'Microsoft.App/containerApps@2023-05-01' = {
  name: name
  location: location
  tags: tags
  properties: {
    managedEnvironmentId: environmentId
    configuration: {
      ingress: {
        external: true
        targetPort: 8080
        transport: 'http'
        allowInsecure: false
        traffic: [
          {
            latestRevision: true
            weight: 100
          }
        ]
      }
      registries: [
        {
          server: registryServer
          username: registryUsername
          passwordSecretRef: 'registry-password'
        }
      ]
      secrets: [
        {
          name: 'registry-password'
          value: registryPassword
        }
      ]
    }
    template: {
      containers: [
        {
          name: name
          image: containerImage
          resources: {
            cpu: json('0.25')
            memory: '0.5Gi'
          }
          env: envVars
          probes: [
            {
              type: 'Liveness'
              httpGet: {
                path: '/health'
                port: 8080
              }
              initialDelaySeconds: 10
              periodSeconds: 30
            }
            {
              type: 'Readiness'
              httpGet: {
                path: '/health/ready'
                port: 8080
              }
              initialDelaySeconds: 5
              periodSeconds: 10
            }
          ]
        }
      ]
      scale: {
        minReplicas: 0
        maxReplicas: 5
        rules: [
          {
            name: 'http-scale'
            http: {
              metadata: {
                concurrentRequests: '100'
              }
            }
          }
        ]
      }
    }
  }
}

output url string = 'https://${containerApp.properties.configuration.ingress.fqdn}'
output name string = containerApp.name
```

### Deploy via CLI

```bash
# Criar environment
az containerapp env create \
  --name myapp-env \
  --resource-group rg-myapp \
  --location brazilsouth

# Criar container app
az containerapp create \
  --name myapp \
  --resource-group rg-myapp \
  --environment myapp-env \
  --image myappacr.azurecr.io/myapp:latest \
  --registry-server myappacr.azurecr.io \
  --registry-username myappacr \
  --registry-password $ACR_PASSWORD \
  --target-port 8080 \
  --ingress external \
  --min-replicas 0 \
  --max-replicas 5 \
  --cpu 0.25 \
  --memory 0.5Gi

# Atualizar imagem
az containerapp update \
  --name myapp \
  --resource-group rg-myapp \
  --image myappacr.azurecr.io/myapp:v2

# Ver logs
az containerapp logs show \
  --name myapp \
  --resource-group rg-myapp \
  --follow
```

## Health Checks no ASP.NET

```csharp
// Program.cs
builder.Services.AddHealthChecks()
    .AddSqlServer(
        connectionString: builder.Configuration.GetConnectionString("Default")!,
        name: "database",
        tags: new[] { "ready" })
    .AddRedis(
        redisConnectionString: builder.Configuration.GetConnectionString("Redis")!,
        name: "redis",
        tags: new[] { "ready" });

app.MapHealthChecks("/health", new HealthCheckOptions
{
    Predicate = _ => true,
    ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse
});

app.MapHealthChecks("/health/ready", new HealthCheckOptions
{
    Predicate = check => check.Tags.Contains("ready")
});

app.MapHealthChecks("/health/live", new HealthCheckOptions
{
    Predicate = _ => false // Sempre retorna healthy
});
```

## Otimização de Imagens

### Tamanho de Imagens

| Base Image | Tamanho |
|------------|---------|
| `aspnet:9.0` | ~220MB |
| `aspnet:9.0-alpine` | ~110MB |
| `aspnet:9.0-chiseled` | ~80MB |

### Chiseled Images (Mais Seguro)

```dockerfile
# Runtime minimal e seguro
FROM mcr.microsoft.com/dotnet/aspnet:9.0-chiseled AS final
WORKDIR /app
COPY --from=publish /app/publish .
USER $APP_UID
ENTRYPOINT ["dotnet", "Web.dll"]
```

## Documentação de Referência

- [Docker Documentation](https://docs.docker.com/)
- [Container Apps](https://learn.microsoft.com/en-us/azure/container-apps/)
- [ACR](https://learn.microsoft.com/en-us/azure/container-registry/)
- [.NET Container Images](https://learn.microsoft.com/en-us/dotnet/core/docker/)

## Checklist de Containerização

- [ ] Dockerfile multi-stage
- [ ] .dockerignore configurado
- [ ] Non-root user no container
- [ ] Health checks configurados
- [ ] Imagem base alpine ou chiseled
- [ ] Docker Compose para dev
- [ ] ACR criado e configurado
- [ ] Container App com scale-to-zero
- [ ] Probes de liveness e readiness

---

*MORPH-SPEC by Polymorphism Tech*
