- 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
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:
- base: ASP.NET Core 8.0 runtime (minimal)
- build: .NET SDK 8.0 (includes build tools)
- publish: Compiled application artifacts
- final: Runtime + published app (smallest)
Port Exposure:
- 8080: HTTP endpoint
- 8081: HTTPS endpoint (unused, HTTPS disabled)
Security Features:
- Non-root user (
$APP_UIDfrom 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
mainbranch - Pull request to
mainbranch
Steps:
- Checkout code
- Set up Docker Buildx (multi-platform builds)
- Log in to Docker Hub
- Build Docker image
- 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 usernameDOCKER_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:
appsettings.json(base)appsettings.{Environment}.json(override)- Environment variables (override)
- 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.jsonconfigured with production values- Secrets stored securely (not in image)
- Docker Hub credentials configured (CI/CD)
Deployment:
- Pull latest image
- Update
docker-compose.ymlif 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:
- Implement health endpoints
- Add health checks to Dockerfile
- Configure HTTPS (reverse proxy or in-app)
- Use secrets management
- Add automated tests to CI/CD
- Implement log aggregation
- Set up APM monitoring
- Create staging environment
- Automate deployment (Kubernetes, Terraform)
- Regular security scanning