Files
metadata-agregator/docs/research/minimediametadataapi/analysis/DEPLOYMENT.md
T
Alexander a1f6701bac feat: initial implementation of metadata aggregator
- gRPC service with MusicBrainz provider
- PostgreSQL schema with migrations
- Service layer with database-first caching
- Repository pattern for data access
- YAML configuration support
- Research documentation for 17 music metadata projects
2026-04-28 16:28:53 +02:00

940 lines
19 KiB
Markdown

# MiniMediaMetadataAPI - Deployment Analysis
## Containerization
### Dockerfile
**Location:** `Dockerfile` (project root)
**Strategy:** Multi-stage build
**Full Dockerfile:**
```dockerfile
# Stage 1: Base runtime image
FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base
WORKDIR /app
EXPOSE 8080
EXPOSE 8081
# Stage 2: Build environment
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
ARG BUILD_CONFIGURATION=Release
WORKDIR /src
# Copy project files
COPY ["MiniMediaMetadataAPI/MiniMediaMetadataAPI.csproj", "MiniMediaMetadataAPI/"]
COPY ["MiniMediaMetadataAPI.Application/MiniMediaMetadataAPI.Application.csproj", "MiniMediaMetadataAPI.Application/"]
# Restore dependencies
RUN dotnet restore "MiniMediaMetadataAPI/MiniMediaMetadataAPI.csproj"
# Copy source code
COPY . .
# Build project
WORKDIR "/src/MiniMediaMetadataAPI"
RUN dotnet build "MiniMediaMetadataAPI.csproj" -c $BUILD_CONFIGURATION -o /app/build
# Stage 3: Publish
FROM build AS publish
ARG BUILD_CONFIGURATION=Release
RUN dotnet publish "MiniMediaMetadataAPI.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false
# Stage 4: Final runtime image
FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
# Run as non-root user
USER $APP_UID
ENTRYPOINT ["dotnet", "MiniMediaMetadataAPI.dll"]
```
**Image Layers:**
1. **base:** ASP.NET Core 8.0 runtime (minimal)
2. **build:** .NET SDK 8.0 (includes build tools)
3. **publish:** Compiled application artifacts
4. **final:** Runtime + published app (smallest)
**Port Exposure:**
- **8080:** HTTP endpoint
- **8081:** HTTPS endpoint (unused, HTTPS disabled)
**Security Features:**
- Non-root user (`$APP_UID` from base image)
- Multi-stage build (no SDK in final image)
- Minimal attack surface
**Image Size:**
- Base image: ~200 MB (aspnet:8.0)
- Application layer: ~20 MB
- **Total:** ~220 MB
**Build Time:**
- Restore: 10-30 seconds
- Build: 20-40 seconds
- Publish: 5-10 seconds
- **Total:** ~1 minute
### Docker Compose (Development)
**Location:** `compose.yaml` (project root)
**Minimal Configuration:**
```yaml
services:
minimediametadataapi:
image: minimediametadataapi
build:
context: .
dockerfile: Dockerfile
```
**Features:**
- Build only (no runtime configuration)
- No port mapping
- No environment variables
- No volume mounts
- No network configuration
- No health checks
**Purpose:** Development build testing only
**Not Suitable For:** Running the application
### Docker Compose (Production)
**Location:** Not in repository (documented in README)
**Production Configuration:**
```yaml
version: '3.8'
services:
minimediametadataapi:
image: musicmovearr/minimediametadataapi:latest
container_name: minimediametadataapi
ports:
- "56232:8080"
volumes:
- ./appsettings.json:/app/appsettings.json:ro
environment:
- ASPNETCORE_ENVIRONMENT=Production
deploy:
resources:
limits:
memory: 256M
restart: unless-stopped
depends_on:
- postgres
networks:
- media-network
postgres:
image: postgres:16
container_name: minimediametadata-db
environment:
- POSTGRES_DB=minimediametadata
- POSTGRES_USER=postgres
- POSTGRES_PASSWORD=${DB_PASSWORD}
volumes:
- postgres-data:/var/lib/postgresql/data
networks:
- media-network
volumes:
postgres-data:
networks:
media-network:
driver: bridge
```
**Key Configuration:**
| Setting | Value | Purpose |
|---------|-------|---------|
| Port Mapping | 56232:8080 | External:Internal HTTP |
| Memory Limit | 256M | Resource constraint |
| Restart Policy | unless-stopped | Auto-restart on failure |
| Volume Mount | appsettings.json | Configuration override |
| Environment | Production | ASP.NET Core environment |
| Network | media-network | Isolated network |
**Dependencies:**
- PostgreSQL 16 (separate container)
- Shared network for database connectivity
**Missing:**
- Health checks
- Logging configuration
- Prometheus metrics port
- HTTPS configuration
- Resource CPU limits
## CI/CD Pipeline
### GitHub Actions
**Location:** `.github/workflows/docker-image.yml`
**Workflow:**
```yaml
name: Docker Image CI
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Build and push Docker image
uses: docker/build-push-action@v5
with:
context: .
file: ./Dockerfile
push: true
tags: |
musicmovearr/minimediametadataapi:latest
musicmovearr/minimediametadataapi:${{ github.sha }}
cache-from: type=registry,ref=musicmovearr/minimediametadataapi:buildcache
cache-to: type=registry,ref=musicmovearr/minimediametadataapi:buildcache,mode=max
```
**Triggers:**
- Push to `main` branch
- Pull request to `main` branch
**Steps:**
1. Checkout code
2. Set up Docker Buildx (multi-platform builds)
3. Log in to Docker Hub
4. Build Docker image
5. Push to Docker Hub with tags
**Tags:**
- `latest` - Most recent build
- `<git-sha>` - Specific commit (e.g., `abc123def456`)
**Caching:**
- Registry cache for faster builds
- Reuses layers from previous builds
**Secrets Required:**
- `DOCKER_USERNAME` - Docker Hub username
- `DOCKER_PASSWORD` - Docker Hub password/token
**Build Time:** 2-5 minutes (with cache)
**Missing Steps:**
- No test execution
- No code quality checks
- No security scanning
- No deployment automation
- No rollback mechanism
### Docker Hub
**Repository:** `musicmovearr/minimediametadataapi`
**Visibility:** Public
**Tags:**
- `latest` - Latest main branch build
- `<git-sha>` - Specific commit builds
**Image Pulls:** Unknown (public repository)
**Automated Builds:** Via GitHub Actions (not Docker Hub auto-build)
## Configuration Management
### appsettings.json
**Location:** `MiniMediaMetadataAPI/appsettings.json`
**Default Configuration:**
```json
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"DatabaseConfiguration": {
"ConnectionString": "Host=localhost;Database=minimediametadata;Username=postgres;Password=postgres;MinPoolSize=5;MaxPoolSize=100"
},
"Prometheus": {
"MetricsUrl": "/metrics"
}
}
```
**Environment-Specific Overrides:**
**appsettings.Development.json:**
```json
{
"Logging": {
"LogLevel": {
"Default": "Debug",
"Microsoft.AspNetCore": "Information"
}
}
}
```
**appsettings.Production.json:** Not included (use volume mount)
**Configuration Hierarchy:**
1. `appsettings.json` (base)
2. `appsettings.{Environment}.json` (override)
3. Environment variables (override)
4. Command-line arguments (override)
**Sensitive Data:**
- Database password in plain text (NOT SECURE)
- No secrets management
- No encryption
**Recommended Approach:**
```json
{
"DatabaseConfiguration": {
"ConnectionString": "Host=${DB_HOST};Database=${DB_NAME};Username=${DB_USER};Password=${DB_PASSWORD};MinPoolSize=5;MaxPoolSize=100"
}
}
```
**Environment Variables:**
```bash
export DB_HOST=postgres
export DB_NAME=minimediametadata
export DB_USER=postgres
export DB_PASSWORD=secure_password_here
```
### Volume Mounts
**Production Pattern:**
```yaml
volumes:
- ./appsettings.json:/app/appsettings.json:ro
```
**Benefits:**
- Configuration changes without rebuild
- Environment-specific settings
- Secrets outside image
**Limitations:**
- Requires file on host
- Manual synchronization
- No version control for production config
**Alternative: Environment Variables**
```yaml
environment:
- DatabaseConfiguration__ConnectionString=Host=postgres;Database=minimediametadata;Username=postgres;Password=${DB_PASSWORD}
- Prometheus__MetricsUrl=/metrics
```
**ASP.NET Core Syntax:** Double underscore (`__`) for nested properties.
## Deployment Environments
### Development
**Setup:**
```bash
# Clone repository
git clone https://github.com/MusicMoveArr/MiniMediaMetadataAPI.git
cd MiniMediaMetadataAPI
# Run with .NET CLI
dotnet run --project MiniMediaMetadataAPI
# Or with Docker
docker build -t minimediametadataapi .
docker run -p 8080:8080 minimediametadataapi
```
**Database:** Local PostgreSQL or Docker container
**Configuration:** `appsettings.Development.json`
**Logging:** Debug level
### Staging
**Not Documented:** No staging environment configuration
**Recommended Setup:**
- Separate Docker Compose file
- Staging database (copy of production schema)
- Production-like resource limits
- Monitoring and logging
### Production
**Deployment Method:** Docker Compose
**Steps:**
```bash
# Pull latest image
docker pull musicmovearr/minimediametadataapi:latest
# Create appsettings.json with production values
cat > appsettings.json <<EOF
{
"DatabaseConfiguration": {
"ConnectionString": "Host=postgres;Database=minimediametadata;Username=postgres;Password=${DB_PASSWORD};MinPoolSize=5;MaxPoolSize=100"
},
"Prometheus": {
"MetricsUrl": "/metrics"
}
}
EOF
# Start services
docker-compose up -d
# Verify health
curl http://localhost:56232/swagger
curl http://localhost:56232/metrics
```
**Database Setup:**
- Managed by MiniMediaScanner (separate deployment)
- Schema must exist before API starts
- No migrations run by API
**Monitoring:**
- Prometheus metrics at `/metrics`
- Docker logs: `docker logs minimediametadataapi`
- No APM (Application Performance Monitoring)
## Health Checks
### Docker Health Check
**Status:** Not configured
**Recommended Dockerfile Addition:**
```dockerfile
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD curl -f http://localhost:8080/health || exit 1
```
**Requires:** Health endpoint implementation in API
### Kubernetes Probes
**Status:** Not configured
**Recommended Deployment:**
```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: minimediametadataapi
spec:
replicas: 3
template:
spec:
containers:
- name: api
image: musicmovearr/minimediametadataapi:latest
ports:
- containerPort: 8080
livenessProbe:
httpGet:
path: /health/live
port: 8080
initialDelaySeconds: 10
periodSeconds: 30
readinessProbe:
httpGet:
path: /health/ready
port: 8080
initialDelaySeconds: 5
periodSeconds: 10
resources:
requests:
memory: "128Mi"
cpu: "100m"
limits:
memory: "256Mi"
cpu: "500m"
```
**Missing:** Health endpoint implementation
## Resource Management
### Memory
**Limit:** 256 MB (Docker Compose)
**Actual Usage:** <250 MB (documented)
**Breakdown:**
- .NET Runtime: ~50 MB
- Application Code: ~20 MB
- Connection Pool: ~50 MB (100 connections)
- Request Buffers: ~50 MB
- Overhead: ~80 MB
**Tuning:**
```yaml
deploy:
resources:
limits:
memory: 256M
reservations:
memory: 128M
```
**OOM Risk:** Low (usage below limit)
### CPU
**Limit:** Not configured
**Recommended:**
```yaml
deploy:
resources:
limits:
cpu: "1.0"
reservations:
cpu: "0.25"
```
**CPU Usage:**
- Idle: <5%
- Light load (10 req/s): 10-20%
- Heavy load (100 req/s): 50-80%
### Disk
**Image Size:** ~220 MB
**Runtime Disk Usage:**
- Logs: Variable (depends on retention)
- Temp files: Minimal
- No persistent storage needed
**Volume Mounts:**
- `appsettings.json`: <1 KB
- Logs (optional): Variable
## Networking
### Ports
| Port | Protocol | Purpose | Exposed |
|------|----------|---------|---------|
| 8080 | HTTP | API endpoints | Yes (56232) |
| 8081 | HTTPS | Secure API (unused) | No |
**Port Mapping:** `56232:8080` (host:container)
**Why 56232?** Arbitrary high port (avoids conflicts)
### Network Isolation
**Docker Network:** `media-network` (bridge)
**Connectivity:**
- API → PostgreSQL (internal network)
- External → API (port 56232)
- API → Internet (not needed)
**Firewall Rules:**
```bash
# Allow API port
ufw allow 56232/tcp
# Allow Prometheus scraping (if external)
ufw allow 9090/tcp
```
### Reverse Proxy
**Not Configured:** Direct port exposure
**Recommended: nginx**
```nginx
server {
listen 80;
server_name api.example.com;
location / {
proxy_pass http://localhost:56232;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
location /metrics {
deny all; # Restrict Prometheus endpoint
}
}
```
**Benefits:**
- HTTPS termination
- Load balancing
- Rate limiting
- Access control
## Logging
### Console Logging
**Default:** ASP.NET Core console logger
**Configuration:**
```json
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}
```
**Output:** Docker logs
**Viewing:**
```bash
# Real-time logs
docker logs -f minimediametadataapi
# Last 100 lines
docker logs --tail 100 minimediametadataapi
# Since timestamp
docker logs --since 2024-01-01T00:00:00 minimediametadataapi
```
### Log Aggregation
**Status:** Not configured
**Recommended: ELK Stack**
```yaml
services:
minimediametadataapi:
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
labels: "service=minimediametadataapi"
```
**Alternatives:**
- Loki + Grafana
- Splunk
- Datadog
- CloudWatch (AWS)
### Structured Logging
**Status:** Not implemented
**Recommended: Serilog**
```csharp
builder.Host.UseSerilog((context, configuration) =>
{
configuration
.ReadFrom.Configuration(context.Configuration)
.Enrich.FromLogContext()
.Enrich.WithProperty("Application", "MiniMediaMetadataAPI")
.WriteTo.Console(new JsonFormatter())
.WriteTo.File(
new JsonFormatter(),
"/app/logs/log-.json",
rollingInterval: RollingInterval.Day,
retainedFileCountLimit: 7);
});
```
## Monitoring
### Prometheus Metrics
**Endpoint:** `/metrics`
**Exposed Metrics:**
- `minimediametadataapi_request_total` (counter)
**Prometheus Configuration:**
```yaml
scrape_configs:
- job_name: 'minimediametadataapi'
static_configs:
- targets: ['minimediametadataapi:8080']
metrics_path: '/metrics'
scrape_interval: 15s
```
**Grafana Dashboard:** Not provided
**Recommended Metrics:**
- Request duration histogram
- Database query duration
- Error rate by provider
- Active requests gauge
- Connection pool usage
### Application Performance Monitoring
**Status:** Not configured
**Recommended: Application Insights**
```csharp
builder.Services.AddApplicationInsightsTelemetry(options =>
{
options.ConnectionString = builder.Configuration["ApplicationInsights:ConnectionString"];
});
```
**Alternatives:**
- New Relic
- Datadog APM
- Elastic APM
- Jaeger (distributed tracing)
## Scaling
### Horizontal Scaling
**Docker Compose:**
```yaml
services:
minimediametadataapi:
image: musicmovearr/minimediametadataapi:latest
deploy:
replicas: 3
resources:
limits:
memory: 256M
```
**Kubernetes:**
```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: minimediametadataapi
spec:
replicas: 3
selector:
matchLabels:
app: minimediametadataapi
template:
metadata:
labels:
app: minimediametadataapi
spec:
containers:
- name: api
image: musicmovearr/minimediametadataapi:latest
resources:
limits:
memory: 256M
cpu: 1
```
**Load Balancer:**
```yaml
apiVersion: v1
kind: Service
metadata:
name: minimediametadataapi
spec:
type: LoadBalancer
selector:
app: minimediametadataapi
ports:
- port: 80
targetPort: 8080
```
**Considerations:**
- Stateless design (scales easily)
- Database connection pool per instance
- No session affinity needed
- No distributed cache (yet)
### Vertical Scaling
**Current:** 256 MB memory, no CPU limit
**Scaling Up:**
```yaml
deploy:
resources:
limits:
memory: 512M
cpu: 2
```
**Diminishing Returns:** Beyond 512 MB, horizontal scaling more effective.
## Backup and Recovery
### Application
**Backup:** Not needed (stateless)
**Recovery:** Redeploy from Docker Hub
**Rollback:**
```bash
# Deploy specific version
docker pull musicmovearr/minimediametadataapi:<git-sha>
docker-compose up -d
```
### Database
**Responsibility:** Database administrator (not API)
**Backup Strategy:**
```bash
# Backup
docker exec minimediametadata-db pg_dump -U postgres minimediametadata > backup.sql
# Restore
docker exec -i minimediametadata-db psql -U postgres minimediametadata < backup.sql
```
**Automated Backups:**
```yaml
services:
postgres-backup:
image: prodrigestivill/postgres-backup-local
environment:
- POSTGRES_HOST=postgres
- POSTGRES_DB=minimediametadata
- POSTGRES_USER=postgres
- POSTGRES_PASSWORD=${DB_PASSWORD}
- SCHEDULE=@daily
- BACKUP_KEEP_DAYS=7
volumes:
- ./backups:/backups
```
## Security
### Image Security
**Base Image:** `mcr.microsoft.com/dotnet/aspnet:8.0`
**Vulnerabilities:** Check with `docker scan`
**Updates:** Monthly .NET patch releases
**Non-Root User:** Yes (`$APP_UID`)
**Secrets in Image:** No (configuration via volume mount)
### Network Security
**HTTPS:** Disabled (expects reverse proxy)
**Firewall:** Host-level (not container-level)
**Network Isolation:** Docker bridge network
**Recommendations:**
- Enable HTTPS in production
- Use secrets management (Docker secrets, Kubernetes secrets)
- Implement network policies (Kubernetes)
- Regular security scanning
## Deployment Checklist
**Pre-Deployment:**
- [ ] Database schema exists (via MiniMediaScanner)
- [ ] PostgreSQL accessible from API container
- [ ] `appsettings.json` configured with production values
- [ ] Secrets stored securely (not in image)
- [ ] Docker Hub credentials configured (CI/CD)
**Deployment:**
- [ ] Pull latest image
- [ ] Update `docker-compose.yml` if needed
- [ ] Start containers: `docker-compose up -d`
- [ ] Verify API responds: `curl http://localhost:56232/swagger`
- [ ] Check metrics: `curl http://localhost:56232/metrics`
- [ ] Review logs: `docker logs minimediametadataapi`
**Post-Deployment:**
- [ ] Configure Prometheus scraping
- [ ] Set up log aggregation
- [ ] Configure alerts (uptime, errors)
- [ ] Document deployment in runbook
- [ ] Test rollback procedure
## Deployment Evaluation
**Strengths:**
- Multi-stage Docker build (small image)
- Non-root user (security)
- CI/CD automation (GitHub Actions)
- Resource limits (memory)
- Restart policy (resilience)
**Weaknesses:**
- No health checks
- No staging environment
- No automated tests in CI/CD
- No security scanning
- No deployment automation (manual docker-compose)
- Secrets in plain text
- No HTTPS configuration
- No log aggregation
- No APM integration
**Production Readiness:** 6/10
**Recommendations:**
1. Implement health endpoints
2. Add health checks to Dockerfile
3. Configure HTTPS (reverse proxy or in-app)
4. Use secrets management
5. Add automated tests to CI/CD
6. Implement log aggregation
7. Set up APM monitoring
8. Create staging environment
9. Automate deployment (Kubernetes, Terraform)
10. Regular security scanning