a1f6701bac
- 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
940 lines
19 KiB
Markdown
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
|