# Lidarr Metadata API - Deployment ## Deployment Overview The Lidarr Metadata API is designed for containerized deployment using Docker and Docker Compose. The architecture consists of 8+ containers working together to provide a complete metadata aggregation service. ## Container Architecture ### Container Inventory | Container | Image | Purpose | CPU | Memory | Storage | |-----------|-------|---------|-----|--------|---------| | **musicbrainz** | ghcr.io/lidarr/mb-postgres:1.0.10 | MusicBrainz database | 2+ cores | 4GB+ | 100GB+ | | **solr** | ghcr.io/lidarr/mb-solr:3.3.1.9 | Search index | 2 cores | 2GB | 8GB | | **redis** | redis:6-alpine | Cache + rate limiting | 1 core | 512MB | None | | **rabbitmq** | rabbitmq:3-management | Message queue | 1 core | 1GB | 1GB | | **indexer** | ghcr.io/lidarr/mb-sir:latest | Search index rebuilder | 1 core | 512MB | None | | **api-v0.3** | ghcr.io/lidarr/lidarrapi.metadata:v0.3 | Stable API version | 2 cores | 1GB | None | | **api-testing** | ghcr.io/lidarr/lidarrapi.metadata:testing | Development API version | 2 cores | 1GB | None | | **crawler** | ghcr.io/lidarr/lidarrapi.metadata:v0.3 | Background cache warmer | 1 core | 512MB | None | **Total minimum resources**: 12 CPU cores, 11.5GB RAM, 110GB storage ### Docker Compose Files The project uses multiple compose files for different environments: #### 1. docker-compose.yml (Base) **Purpose**: Core infrastructure services **Services**: - `musicbrainz`: PostgreSQL with MusicBrainz schema - `solr`: Search index - `redis`: Cache - `rabbitmq`: Message queue **File location**: `docker-compose.yml` **Key configuration**: ```yaml version: '3.8' services: musicbrainz: image: ghcr.io/lidarr/mb-postgres:1.0.10 container_name: musicbrainz environment: POSTGRES_USER: abc POSTGRES_PASSWORD: abc POSTGRES_DB: musicbrainz_db volumes: - musicbrainz-data:/var/lib/postgresql/data ports: - "5432:5432" restart: unless-stopped shm_size: 2gb solr: image: ghcr.io/lidarr/mb-solr:3.3.1.9 container_name: solr environment: SOLR_HEAP: 2g volumes: - solr-data:/var/solr ports: - "8983:8983" restart: unless-stopped redis: image: redis:6-alpine container_name: redis command: redis-server --maxmemory 512mb --maxmemory-policy allkeys-lfu ports: - "6379:6379" restart: unless-stopped rabbitmq: image: rabbitmq:3-management container_name: rabbitmq environment: RABBITMQ_DEFAULT_USER: abc RABBITMQ_DEFAULT_PASS: abc ports: - "5672:5672" - "15672:15672" restart: unless-stopped volumes: musicbrainz-data: solr-data: ``` #### 2. docker-compose.dev.yml (Development) **Purpose**: Development environment with exposed ports **Extends**: `docker-compose.yml` **Additional configuration**: ```yaml version: '3.8' services: api: image: ghcr.io/lidarr/lidarrapi.metadata:testing container_name: lidarr-metadata-api environment: LIDARR_METADATA_CONFIG: lidarrmetadata.config.DevelopmentConfig DATABASE__HOST: musicbrainz CACHE__REDIS_URL: redis://redis:6379/0 SOLR__URL: http://solr:8983/solr RABBITMQ__HOST: rabbitmq ports: - "5001:5001" depends_on: - musicbrainz - solr - redis - rabbitmq restart: unless-stopped indexer: image: ghcr.io/lidarr/mb-sir:latest container_name: search-indexer environment: DATABASE_HOST: musicbrainz SOLR_URL: http://solr:8983/solr RABBITMQ_HOST: rabbitmq depends_on: - musicbrainz - solr - rabbitmq restart: unless-stopped ``` **Usage**: ```bash docker-compose -f docker-compose.yml -f docker-compose.dev.yml up -d ``` #### 3. docker-compose.prod.yml (Production) **Purpose**: Production deployment with dual API versions **Extends**: `docker-compose.yml` **Additional configuration**: ```yaml version: '3.8' services: api-v0.3: image: ghcr.io/lidarr/lidarrapi.metadata:v0.3 container_name: lidarr-metadata-api-v0.3 environment: LIDARR_METADATA_CONFIG: lidarrmetadata.config.ProductionConfig DATABASE__HOST: musicbrainz CACHE__REDIS_URL: redis://redis:6379/0 SOLR__URL: http://solr:8983/solr RABBITMQ__HOST: rabbitmq SENTRY_DSN: ${SENTRY_DSN} CLOUDFLARE_ZONE_ID: ${CLOUDFLARE_ZONE_ID} CLOUDFLARE_API_TOKEN: ${CLOUDFLARE_API_TOKEN} FANART_API_KEY: ${FANART_API_KEY} SPOTIFY_CLIENT_ID: ${SPOTIFY_CLIENT_ID} SPOTIFY_CLIENT_SECRET: ${SPOTIFY_CLIENT_SECRET} LASTFM_API_KEY: ${LASTFM_API_KEY} deploy: replicas: 2 resources: limits: cpus: '2' memory: 1G reservations: cpus: '1' memory: 512M depends_on: - musicbrainz - solr - redis - rabbitmq restart: unless-stopped api-testing: image: ghcr.io/lidarr/lidarrapi.metadata:testing container_name: lidarr-metadata-api-testing environment: LIDARR_METADATA_CONFIG: lidarrmetadata.config.ProductionConfig DATABASE__HOST: musicbrainz CACHE__REDIS_URL: redis://redis:6379/0 SOLR__URL: http://solr:8983/solr RABBITMQ__HOST: rabbitmq SENTRY_DSN: ${SENTRY_DSN} CLOUDFLARE_ZONE_ID: ${CLOUDFLARE_ZONE_ID} CLOUDFLARE_API_TOKEN: ${CLOUDFLARE_API_TOKEN} FANART_API_KEY: ${FANART_API_KEY} SPOTIFY_CLIENT_ID: ${SPOTIFY_CLIENT_ID} SPOTIFY_CLIENT_SECRET: ${SPOTIFY_CLIENT_SECRET} LASTFM_API_KEY: ${LASTFM_API_KEY} deploy: replicas: 1 resources: limits: cpus: '2' memory: 1G depends_on: - musicbrainz - solr - redis - rabbitmq restart: unless-stopped indexer: image: ghcr.io/lidarr/mb-sir:latest container_name: search-indexer environment: DATABASE_HOST: musicbrainz SOLR_URL: http://solr:8983/solr RABBITMQ_HOST: rabbitmq depends_on: - musicbrainz - solr - rabbitmq restart: unless-stopped ``` **Usage**: ```bash docker-compose -f docker-compose.yml -f docker-compose.prod.yml up -d ``` #### 4. docker-compose.crawler.yml (Crawler) **Purpose**: Background cache warming **Extends**: `docker-compose.yml` **Additional configuration**: ```yaml version: '3.8' services: crawler: image: ghcr.io/lidarr/lidarrapi.metadata:v0.3 container_name: lidarr-metadata-crawler command: lidarr-metadata-crawler environment: LIDARR_METADATA_CONFIG: lidarrmetadata.config.ProductionConfig DATABASE__HOST: musicbrainz CACHE__REDIS_URL: redis://redis:6379/0 SOLR__URL: http://solr:8983/solr FANART_API_KEY: ${FANART_API_KEY} SPOTIFY_CLIENT_ID: ${SPOTIFY_CLIENT_ID} SPOTIFY_CLIENT_SECRET: ${SPOTIFY_CLIENT_SECRET} CRAWLER_INTERVAL: 3600 CRAWLER_BATCH_SIZE: 100 depends_on: - musicbrainz - redis restart: unless-stopped ``` **Usage**: ```bash docker-compose -f docker-compose.yml -f docker-compose.crawler.yml up -d ``` ## Dockerfile ### Multi-Stage Build **File location**: `Dockerfile` **Build stages**: #### Stage 1: Dependencies ```dockerfile FROM python:3.9-alpine AS builder # Install build dependencies RUN apk add --no-cache \ gcc \ musl-dev \ postgresql-dev \ libffi-dev \ openssl-dev # Install Poetry RUN pip install poetry==1.1.13 # Copy dependency files WORKDIR /app COPY pyproject.toml poetry.lock ./ # Install dependencies RUN poetry config virtualenvs.create false \ && poetry install --no-dev --no-interaction --no-ansi ``` #### Stage 2: Runtime ```dockerfile FROM python:3.9-alpine # Install runtime dependencies RUN apk add --no-cache \ postgresql-libs \ libffi \ openssl # Create non-root user RUN addgroup -g 1000 lidarr \ && adduser -D -u 1000 -G lidarr lidarr # Copy dependencies from builder COPY --from=builder /usr/local/lib/python3.9/site-packages /usr/local/lib/python3.9/site-packages COPY --from=builder /usr/local/bin /usr/local/bin # Copy application code WORKDIR /app COPY --chown=lidarr:lidarr . . # Switch to non-root user USER lidarr # Expose port EXPOSE 5001 # Default command CMD ["gunicorn", "-w", "1", "-k", "uvicorn.workers.UvicornWorker", "--bind", "0.0.0.0:5001", "--access-logfile", "-", "lidarrmetadata.server:app"] ``` ### Build Arguments ```dockerfile ARG VERSION=unknown ARG GIT_SHA=unknown ARG BUILD_DATE=unknown LABEL org.opencontainers.image.version="${VERSION}" LABEL org.opencontainers.image.revision="${GIT_SHA}" LABEL org.opencontainers.image.created="${BUILD_DATE}" LABEL org.opencontainers.image.source="https://github.com/Lidarr/LidarrAPI.Metadata" ``` ### Build Command ```bash docker build \ --build-arg VERSION=10.0.0.0 \ --build-arg GIT_SHA=$(git rev-parse HEAD) \ --build-arg BUILD_DATE=$(date -u +"%Y-%m-%dT%H:%M:%SZ") \ -t ghcr.io/lidarr/lidarrapi.metadata:v0.3 \ . ``` ## Initialization Process ### 10-Step Initialization The deployment requires a specific initialization sequence: #### Step 1: Start PostgreSQL ```bash docker-compose up -d musicbrainz ``` **Wait for**: PostgreSQL ready to accept connections **Verification**: ```bash docker-compose exec musicbrainz pg_isready -U abc ``` #### Step 2: Fetch MusicBrainz Dump **Options**: **Option A: Full dump** (recommended for production): ```bash docker-compose exec musicbrainz /scripts/fetch-dump.sh ``` **Duration**: 4-8 hours (depends on network speed) **Size**: ~30GB compressed, ~100GB uncompressed **Option B: Sample dump** (for development): ```bash docker-compose exec musicbrainz /scripts/fetch-sample-dump.sh ``` **Duration**: 30-60 minutes **Size**: ~1GB compressed, ~5GB uncompressed #### Step 3: Setup AMQP Triggers ```bash docker-compose exec musicbrainz /scripts/setup-amqp.sh ``` **Purpose**: Create database triggers that publish changes to RabbitMQ **Verification**: ```sql SELECT * FROM pg_trigger WHERE tgname LIKE 'amqp_%'; ``` #### Step 4: Create Custom Indices ```bash docker-compose exec musicbrainz psql -U abc -d musicbrainz_db -f /scripts/create-indices.sql ``` **Indices created**: - `idx_artist_last_updated` - `idx_release_group_last_updated` - `idx_release_last_updated` - `idx_l_artist_url_last_updated` - `idx_cover_art_date_updated` **Duration**: 30-60 minutes #### Step 5: Start RabbitMQ ```bash docker-compose up -d rabbitmq ``` **Wait for**: RabbitMQ management interface available **Verification**: ```bash curl http://localhost:15672/api/overview -u abc:abc ``` #### Step 6: Start Solr ```bash docker-compose up -d solr ``` **Wait for**: Solr cores created **Verification**: ```bash curl http://localhost:8983/solr/admin/cores?action=STATUS ``` #### Step 7: Load Search Indices ```bash docker-compose run indexer rebuild-artist docker-compose run indexer rebuild-album ``` **Duration**: 4-8 hours for full dataset **Verification**: ```bash curl "http://localhost:8983/solr/artist/select?q=*:*&rows=0" | jq '.response.numFound' ``` #### Step 8: Enable Replication ```bash docker-compose exec musicbrainz /scripts/enable-replication.sh ``` **Purpose**: Start hourly replication from MusicBrainz master **Verification**: ```sql SELECT * FROM replication_control; ``` #### Step 9: Start Search Indexer ```bash docker-compose up -d indexer ``` **Purpose**: Real-time search index updates via RabbitMQ **Verification**: ```bash docker-compose logs -f indexer ``` #### Step 10: Start API and Crawler ```bash docker-compose -f docker-compose.yml -f docker-compose.prod.yml up -d api-v0.3 api-testing docker-compose -f docker-compose.yml -f docker-compose.crawler.yml up -d crawler ``` **Verification**: ```bash curl http://localhost:5001/ ``` ### Initialization Script **Complete initialization script**: ```bash #!/bin/bash set -e echo "Step 1: Starting PostgreSQL..." docker-compose up -d musicbrainz sleep 30 echo "Step 2: Fetching MusicBrainz dump..." docker-compose exec musicbrainz /scripts/fetch-dump.sh echo "Step 3: Setting up AMQP triggers..." docker-compose exec musicbrainz /scripts/setup-amqp.sh echo "Step 4: Creating custom indices..." docker-compose exec musicbrainz psql -U abc -d musicbrainz_db -f /scripts/create-indices.sql echo "Step 5: Starting RabbitMQ..." docker-compose up -d rabbitmq sleep 10 echo "Step 6: Starting Solr..." docker-compose up -d solr sleep 10 echo "Step 7: Loading search indices..." docker-compose run indexer rebuild-artist docker-compose run indexer rebuild-album echo "Step 8: Enabling replication..." docker-compose exec musicbrainz /scripts/enable-replication.sh echo "Step 9: Starting search indexer..." docker-compose up -d indexer echo "Step 10: Starting API and crawler..." docker-compose -f docker-compose.yml -f docker-compose.prod.yml up -d api-v0.3 api-testing docker-compose -f docker-compose.yml -f docker-compose.crawler.yml up -d crawler echo "Initialization complete!" ``` ## CI/CD Pipeline ### Azure Pipelines **File location**: `azure-pipelines.yml` **Trigger**: Commits to `master` or `develop` branches **Stages**: #### 1. Build and Test ```yaml stages: - stage: Build jobs: - job: BuildAndTest pool: vmImage: 'ubuntu-latest' steps: - task: UsePythonVersion@0 inputs: versionSpec: '3.9' - script: | pip install poetry poetry install displayName: 'Install dependencies' # Tests commented out # - script: | # poetry run pytest tests/ # displayName: 'Run tests' - task: SonarCloudPrepare@1 inputs: SonarCloud: 'SonarCloud' organization: 'lidarr' scannerMode: 'CLI' configMode: 'manual' cliProjectKey: 'Lidarr_LidarrAPI.Metadata' cliProjectName: 'LidarrAPI.Metadata' - task: SonarCloudAnalyze@1 - task: SonarCloudPublish@1 inputs: pollingTimeoutSec: '300' ``` #### 2. Docker Build and Push ```yaml - stage: Docker dependsOn: Build jobs: - job: BuildAndPush pool: vmImage: 'ubuntu-latest' steps: - task: Docker@2 inputs: containerRegistry: 'GitHub Container Registry' repository: 'lidarr/lidarrapi.metadata' command: 'buildAndPush' Dockerfile: 'Dockerfile' tags: | $(Build.SourceBranchName) $(Build.SourceVersion) latest buildArgs: | VERSION=10.0.0.0 GIT_SHA=$(Build.SourceVersion) BUILD_DATE=$(date -u +"%Y-%m-%dT%H:%M:%SZ") ``` #### 3. Sentry Release ```yaml - stage: Sentry dependsOn: Docker jobs: - job: CreateRelease pool: vmImage: 'ubuntu-latest' steps: - script: | curl -sL https://sentry.io/get-cli/ | bash sentry-cli releases new "lidarr-metadata@$(Build.SourceVersion)" sentry-cli releases set-commits "lidarr-metadata@$(Build.SourceVersion)" --auto sentry-cli releases finalize "lidarr-metadata@$(Build.SourceVersion)" env: SENTRY_AUTH_TOKEN: $(SENTRY_AUTH_TOKEN) SENTRY_ORG: lidarr SENTRY_PROJECT: lidarr-metadata displayName: 'Create Sentry release' ``` ### GitHub Actions (Alternative) **File location**: `.github/workflows/build.yml` ```yaml name: Build and Deploy on: push: branches: [master, develop] pull_request: branches: [master] jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Set up Python uses: actions/setup-python@v4 with: python-version: '3.9' - name: Install dependencies run: | pip install poetry poetry install - name: Run tests run: poetry run pytest tests/ - name: SonarCloud Scan uses: SonarSource/sonarcloud-github-action@master env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} - name: Build Docker image run: | docker build \ --build-arg VERSION=10.0.0.0 \ --build-arg GIT_SHA=${{ github.sha }} \ --build-arg BUILD_DATE=$(date -u +"%Y-%m-%dT%H:%M:%SZ") \ -t ghcr.io/lidarr/lidarrapi.metadata:${{ github.ref_name }} \ . - name: Push to GitHub Container Registry run: | echo ${{ secrets.GITHUB_TOKEN }} | docker login ghcr.io -u ${{ github.actor }} --password-stdin docker push ghcr.io/lidarr/lidarrapi.metadata:${{ github.ref_name }} - name: Create Sentry release run: | curl -sL https://sentry.io/get-cli/ | bash sentry-cli releases new "lidarr-metadata@${{ github.sha }}" sentry-cli releases set-commits "lidarr-metadata@${{ github.sha }}" --auto sentry-cli releases finalize "lidarr-metadata@${{ github.sha }}" env: SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} SENTRY_ORG: lidarr SENTRY_PROJECT: lidarr-metadata ``` ## Infrastructure Requirements ### Minimum Requirements | Component | CPU | Memory | Storage | Network | |-----------|-----|--------|---------|---------| | **MusicBrainz DB** | 2 cores | 4GB | 100GB SSD | 1Gbps | | **Solr** | 2 cores | 2GB | 8GB SSD | 1Gbps | | **Redis** | 1 core | 512MB | None | 1Gbps | | **RabbitMQ** | 1 core | 1GB | 1GB SSD | 1Gbps | | **API (x2)** | 4 cores | 2GB | None | 1Gbps | | **Indexer** | 1 core | 512MB | None | 1Gbps | | **Crawler** | 1 core | 512MB | None | 1Gbps | | **Total** | **12 cores** | **11.5GB** | **110GB** | **1Gbps** | ### Recommended Production Requirements | Component | CPU | Memory | Storage | Network | |-----------|-----|--------|---------|---------| | **MusicBrainz DB** | 4 cores | 8GB | 200GB NVMe | 10Gbps | | **Solr** | 4 cores | 4GB | 16GB NVMe | 10Gbps | | **Redis** | 2 cores | 1GB | None | 10Gbps | | **RabbitMQ** | 2 cores | 2GB | 2GB SSD | 10Gbps | | **API (x4)** | 8 cores | 4GB | None | 10Gbps | | **Indexer** | 2 cores | 1GB | None | 10Gbps | | **Crawler** | 2 cores | 1GB | None | 10Gbps | | **Total** | **24 cores** | **21GB** | **220GB** | **10Gbps** | ### Cloud Provider Recommendations #### AWS **Instance types**: - **Database**: r6g.xlarge (4 vCPU, 32GB RAM) - **Solr**: c6g.xlarge (4 vCPU, 8GB RAM) - **API**: c6g.large (2 vCPU, 4GB RAM) x 2 - **Other**: t4g.small (2 vCPU, 2GB RAM) x 3 **Storage**: - **Database**: gp3 200GB (3000 IOPS, 125 MB/s) - **Solr**: gp3 20GB (3000 IOPS) **Estimated cost**: $400-600/month #### Google Cloud **Instance types**: - **Database**: n2-highmem-4 (4 vCPU, 32GB RAM) - **Solr**: n2-standard-4 (4 vCPU, 16GB RAM) - **API**: n2-standard-2 (2 vCPU, 8GB RAM) x 2 - **Other**: e2-small (2 vCPU, 2GB RAM) x 3 **Storage**: - **Database**: pd-ssd 200GB - **Solr**: pd-ssd 20GB **Estimated cost**: $450-650/month #### DigitalOcean **Droplets**: - **Database**: Memory-Optimized 8GB ($80/month) - **Solr**: General Purpose 4GB ($48/month) - **API**: General Purpose 2GB ($24/month) x 2 - **Other**: Basic 2GB ($18/month) x 3 **Volumes**: - **Database**: 200GB SSD ($20/month) - **Solr**: 20GB SSD ($2/month) **Estimated cost**: $250-350/month ## Monitoring and Logging ### Health Checks **Docker Compose health checks**: ```yaml services: api-v0.3: healthcheck: test: ["CMD", "curl", "-f", "http://localhost:5001/"] interval: 30s timeout: 10s retries: 3 start_period: 40s musicbrainz: healthcheck: test: ["CMD", "pg_isready", "-U", "abc"] interval: 30s timeout: 5s retries: 3 solr: healthcheck: test: ["CMD", "curl", "-f", "http://localhost:8983/solr/admin/ping"] interval: 30s timeout: 5s retries: 3 redis: healthcheck: test: ["CMD", "redis-cli", "ping"] interval: 30s timeout: 3s retries: 3 ``` ### Logging **Centralized logging with Docker**: ```yaml services: api-v0.3: logging: driver: "json-file" options: max-size: "10m" max-file: "3" ``` **Log aggregation with Loki** (optional): ```yaml services: loki: image: grafana/loki:latest ports: - "3100:3100" volumes: - loki-data:/loki promtail: image: grafana/promtail:latest volumes: - /var/lib/docker/containers:/var/lib/docker/containers:ro - ./promtail-config.yml:/etc/promtail/config.yml command: -config.file=/etc/promtail/config.yml ``` ### Metrics **Prometheus scraping**: ```yaml services: prometheus: image: prom/prometheus:latest ports: - "9090:9090" volumes: - ./prometheus.yml:/etc/prometheus/prometheus.yml - prometheus-data:/prometheus grafana: image: grafana/grafana:latest ports: - "3000:3000" volumes: - grafana-data:/var/lib/grafana environment: GF_SECURITY_ADMIN_PASSWORD: admin ``` ## Backup and Recovery ### Database Backup **PostgreSQL backup script**: ```bash #!/bin/bash BACKUP_DIR=/backups DATE=$(date +%Y%m%d_%H%M%S) # Backup MusicBrainz database docker-compose exec -T musicbrainz pg_dump -U abc musicbrainz_db | gzip > $BACKUP_DIR/musicbrainz_$DATE.sql.gz # Backup cache database docker-compose exec -T musicbrainz pg_dump -U abc lm_cache_db | gzip > $BACKUP_DIR/cache_$DATE.sql.gz # Cleanup old backups (keep 7 days) find $BACKUP_DIR -name "*.sql.gz" -mtime +7 -delete ``` **Cron schedule**: ```cron 0 2 * * * /scripts/backup.sh ``` ### Solr Backup **Solr snapshot**: ```bash curl "http://localhost:8983/solr/artist/replication?command=backup&location=/var/solr/backups&name=artist_$(date +%Y%m%d)" curl "http://localhost:8983/solr/release-group/replication?command=backup&location=/var/solr/backups&name=album_$(date +%Y%m%d)" ``` ### Recovery **Database restore**: ```bash # Stop services docker-compose down # Restore database gunzip -c /backups/musicbrainz_20250428.sql.gz | docker-compose exec -T musicbrainz psql -U abc musicbrainz_db # Restart services docker-compose up -d ``` ## Security Hardening ### Production Security Checklist - [ ] Change default credentials (abc/abc) - [ ] Use strong passwords for all services - [ ] Enable TLS for PostgreSQL connections - [ ] Enable TLS for Redis connections - [ ] Use secrets management (Docker Secrets, Vault) - [ ] Restrict network access (firewall rules) - [ ] Enable API authentication - [ ] Use read-only database user for API - [ ] Enable Sentry error tracking - [ ] Configure rate limiting - [ ] Use HTTPS reverse proxy (Nginx, Traefik) - [ ] Enable CORS restrictions - [ ] Implement API key rotation - [ ] Regular security updates - [ ] Monitor for vulnerabilities (Snyk, Dependabot) ### Secrets Management **Docker Secrets** (Swarm mode): ```yaml services: api-v0.3: secrets: - db_password - redis_password - fanart_api_key - spotify_client_secret environment: DATABASE__PASSWORD_FILE: /run/secrets/db_password CACHE__REDIS_PASSWORD_FILE: /run/secrets/redis_password FANART_API_KEY_FILE: /run/secrets/fanart_api_key SPOTIFY_CLIENT_SECRET_FILE: /run/secrets/spotify_client_secret secrets: db_password: external: true redis_password: external: true fanart_api_key: external: true spotify_client_secret: external: true ``` ## Scaling Strategies ### Horizontal Scaling **API instances**: ```yaml services: api-v0.3: deploy: replicas: 4 update_config: parallelism: 2 delay: 10s restart_policy: condition: on-failure ``` **Load balancing** (Nginx): ```nginx upstream lidarr_metadata_api { least_conn; server api-v0.3-1:5001; server api-v0.3-2:5001; server api-v0.3-3:5001; server api-v0.3-4:5001; } server { listen 80; server_name api.lidarr.audio; location / { proxy_pass http://lidarr_metadata_api; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } } ``` ### Vertical Scaling **Increase worker count**: ```yaml services: api-v0.3: command: gunicorn -w 4 -k uvicorn.workers.UvicornWorker --bind 0.0.0.0:5001 lidarrmetadata.server:app ``` **Increase database connections**: ```python DATABASE = { 'min_pool_size': 20, 'max_pool_size': 100 } ``` ## Conclusion The deployment architecture demonstrates production-ready containerization with: - **10-step initialization** for complex setup - **Multi-environment support** (dev, prod, crawler) - **Dual-version deployment** for safe rollouts - **CI/CD integration** with Azure Pipelines - **Comprehensive monitoring** with health checks and metrics - **Backup and recovery** procedures - **Security hardening** recommendations - **Horizontal and vertical scaling** strategies The Docker Compose approach makes deployment straightforward while maintaining flexibility for different environments.