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
This commit is contained in:
@@ -0,0 +1,939 @@
|
||||
# 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
|
||||
Reference in New Issue
Block a user