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

19 KiB

MiniMediaMetadataAPI - Deployment Analysis

Containerization

Dockerfile

Location: Dockerfile (project root)

Strategy: Multi-stage build

Full 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:

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:

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:

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:

{
  "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:

{
  "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:

{
  "DatabaseConfiguration": {
    "ConnectionString": "Host=${DB_HOST};Database=${DB_NAME};Username=${DB_USER};Password=${DB_PASSWORD};MinPoolSize=5;MaxPoolSize=100"
  }
}

Environment Variables:

export DB_HOST=postgres
export DB_NAME=minimediametadata
export DB_USER=postgres
export DB_PASSWORD=secure_password_here

Volume Mounts

Production Pattern:

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

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:

# 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:

# 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:

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:

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:

deploy:
  resources:
    limits:
      memory: 256M
    reservations:
      memory: 128M

OOM Risk: Low (usage below limit)

CPU

Limit: Not configured

Recommended:

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:

# 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

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:

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  }
}

Output: Docker logs

Viewing:

# 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

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

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:

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

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:

services:
  minimediametadataapi:
    image: musicmovearr/minimediametadataapi:latest
    deploy:
      replicas: 3
      resources:
        limits:
          memory: 256M

Kubernetes:

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:

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:

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:

# Deploy specific version
docker pull musicmovearr/minimediametadataapi:<git-sha>
docker-compose up -d

Database

Responsibility: Database administrator (not API)

Backup Strategy:

# 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:

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