Files
metadata-agregator/docs/research/bedrock-api/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

23 KiB

Bedrock-API Deployment

Containerization

Dockerfile

File: Dockerfile
Strategy: Multi-stage build (builder + runtime)

# Builder stage
FROM golang:1.23-alpine AS builder

WORKDIR /app

# Install git (required for submodules)
RUN apk add --no-cache git

# Copy go mod files
COPY go.mod go.sum ./
RUN go mod download

# Copy source code
COPY . .

# Initialize submodules
RUN git submodule update --init --recursive

# Build binary
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags="-w -s" -o bedrock-server ./bedrock_server

# Runtime stage
FROM alpine:latest

# Install ca-certificates (required for HTTPS requests to provider APIs)
RUN apk --no-cache add ca-certificates

WORKDIR /root/

# Copy binary from builder
COPY --from=builder /app/bedrock-server .

# Copy migrations (if needed)
COPY --from=builder /app/db/migrations ./db/migrations

# Expose ports
EXPOSE 50052 8080

# Run server
CMD ["./bedrock-server"]

Build Stages:

  1. Builder (golang:1.23-alpine):

    • Installs git for submodule support
    • Downloads Go dependencies
    • Initializes spotapi-go submodule
    • Compiles binary with optimizations (-ldflags="-w -s")
    • CGO disabled for static binary
  2. Runtime (alpine:latest):

    • Minimal image (~5 MB base)
    • Installs ca-certificates for HTTPS
    • Copies binary from builder
    • Exposes gRPC (50052) and HTTP (8080) ports

Image Size: ~20 MB (builder stage discarded)

Version Mismatch: Dockerfile uses Go 1.23, but go.mod specifies 1.25

Fix:

FROM golang:1.25-alpine AS builder

Docker Build

Build Command:

docker build -t bedrock-api:latest .

Build Arguments (not implemented):

ARG GO_VERSION=1.25
FROM golang:${GO_VERSION}-alpine AS builder

Build Time: ~2-3 minutes (first build), ~30 seconds (cached)

Docker Run

Run Command:

docker run -d \
  --name bedrock-api \
  -p 50052:50052 \
  -p 8080:8080 \
  -e DATABASE_URL=postgresql://user:pass@host:5432/bedrock \
  -e JWT_SECRET=your-secret \
  -e SPOTIFY_CLIENT_ID=your-id \
  -e SPOTIFY_CLIENT_SECRET=your-secret \
  -e SOUNDCLOUD_CLIENT_IDS=id1,id2,id3 \
  -e GENIUS_ACCESS_TOKEN=your-token \
  bedrock-api:latest

Environment Variables: Passed via -e flags (no .env file in container)

Port Mapping:

  • 50052:50052 - gRPC server
  • 8080:8080 - HTTP proxy

No Volume Mounts: Binary is stateless (no local file storage)

Docker Compose

Compose File

File: docker-compose.yml

version: '3.8'

services:
  postgres:
    image: postgres:15-alpine
    container_name: bedrock-postgres
    environment:
      POSTGRES_USER: bedrock
      POSTGRES_PASSWORD: bedrock
      POSTGRES_DB: bedrock
    ports:
      - "5432:5432"
    volumes:
      - postgres_data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U bedrock"]
      interval: 10s
      timeout: 5s
      retries: 5
    networks:
      - bedrock-network

volumes:
  postgres_data:
    driver: local

networks:
  bedrock-network:
    driver: bridge

Services: PostgreSQL only (application not included)

Missing Services:

  • No application service (must be added or run separately)
  • No Redis (planned for caching)
  • No reverse proxy (nginx, Caddy)
  • No monitoring (Prometheus, Grafana)
version: '3.8'

services:
  postgres:
    image: postgres:15-alpine
    container_name: bedrock-postgres
    environment:
      POSTGRES_USER: bedrock
      POSTGRES_PASSWORD: bedrock
      POSTGRES_DB: bedrock
    ports:
      - "5432:5432"
    volumes:
      - postgres_data:/var/lib/postgresql/data
      - ./db/migrations:/docker-entrypoint-initdb.d
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U bedrock"]
      interval: 10s
      timeout: 5s
      retries: 5
    networks:
      - bedrock-network

  bedrock-api:
    build: .
    container_name: bedrock-api
    depends_on:
      postgres:
        condition: service_healthy
    environment:
      DATABASE_URL: postgresql://bedrock:bedrock@postgres:5432/bedrock?sslmode=disable
      JWT_SECRET: ${JWT_SECRET}
      SPOTIFY_CLIENT_ID: ${SPOTIFY_CLIENT_ID}
      SPOTIFY_CLIENT_SECRET: ${SPOTIFY_CLIENT_SECRET}
      SOUNDCLOUD_CLIENT_IDS: ${SOUNDCLOUD_CLIENT_IDS}
      GENIUS_ACCESS_TOKEN: ${GENIUS_ACCESS_TOKEN}
      YOUTUBE_COOKIES: ${YOUTUBE_COOKIES}
    ports:
      - "50052:50052"
      - "8080:8080"
    networks:
      - bedrock-network
    restart: unless-stopped

volumes:
  postgres_data:

networks:
  bedrock-network:

Improvements:

  • Application service added
  • Health check dependency (waits for PostgreSQL)
  • Environment variables from .env file
  • Automatic restart policy
  • Migration initialization via volume mount

Compose Commands

Start Services:

docker-compose up -d

View Logs:

docker-compose logs -f bedrock-api

Stop Services:

docker-compose down

Rebuild:

docker-compose up -d --build

Clean Volumes:

docker-compose down -v

Local Development

Prerequisites

  • Go 1.25+
  • PostgreSQL 15+
  • Git (for submodules)

Setup Steps

1. Clone Repository:

git clone https://github.com/feralbureau/bedrock-api
cd bedrock-api

2. Initialize Submodules:

git submodule update --init --recursive

3. Install Dependencies:

go mod download

4. Setup Database:

# Start PostgreSQL (Docker)
docker run -d \
  --name bedrock-postgres \
  -e POSTGRES_USER=bedrock \
  -e POSTGRES_PASSWORD=bedrock \
  -e POSTGRES_DB=bedrock \
  -p 5432:5432 \
  postgres:15-alpine

# Run migrations
psql postgresql://bedrock:bedrock@localhost:5432/bedrock -f db/migrations/001_create_users_table.up.sql

5. Configure Environment:

cp .env.example .env
# Edit .env with your credentials

Example .env:

DATABASE_URL=postgresql://bedrock:bedrock@localhost:5432/bedrock?sslmode=disable
JWT_SECRET=your-secret-key-change-this-in-production

SPOTIFY_CLIENT_ID=your_spotify_client_id
SPOTIFY_CLIENT_SECRET=your_spotify_client_secret

SOUNDCLOUD_CLIENT_IDS=client_id_1,client_id_2,client_id_3

DEEZER_APP_ID=your_deezer_app_id

YOUTUBE_COOKIES=your_youtube_cookies

GENIUS_ACCESS_TOKEN=your_genius_access_token

6. Run Server:

go run ./bedrock_server

7. Verify:

# gRPC health check (requires grpcurl)
grpcurl -plaintext localhost:50052 bedrock.BedrockService/GetServiceStatus

# HTTP proxy check
curl http://localhost:8080/stream/soundcloud/1234567890

Development Workflow

Hot Reload (not configured):

# Install air
go install github.com/cosmtrek/air@latest

# Run with hot reload
air

Example .air.toml:

root = "."
tmp_dir = "tmp"

[build]
  cmd = "go build -o ./tmp/main ./bedrock_server"
  bin = "tmp/main"
  include_ext = ["go", "proto"]
  exclude_dir = ["tmp", "vendor"]
  delay = 1000

Testing

Run Tests:

go test ./...

Integration Tests (requires provider credentials):

export SPOTIFY_CLIENT_ID=your_id
export SPOTIFY_CLIENT_SECRET=your_secret
export SOUNDCLOUD_CLIENT_IDS=your_ids
export GENIUS_ACCESS_TOKEN=your_token
export BEDROCK_TEST_ADDR=localhost:50052

go test -v ./tests/

Test Coverage:

go test -cover ./...

CI/CD Pipeline

GitHub Actions

Workflows:

  • test.yml - Integration tests
  • lint.yml - Code linting

Test Workflow

File: .github/workflows/test.yml

name: Tests

on:
  push:
    branches: [ main, develop ]
  pull_request:
    branches: [ main, develop ]

jobs:
  test:
    runs-on: ubuntu-latest
    
    services:
      postgres:
        image: postgres:15-alpine
        env:
          POSTGRES_USER: bedrock
          POSTGRES_PASSWORD: bedrock
          POSTGRES_DB: bedrock
        ports:
          - 5432:5432
        options: >-
          --health-cmd pg_isready
          --health-interval 10s
          --health-timeout 5s
          --health-retries 5
    
    steps:
      - name: Checkout code
        uses: actions/checkout@v3
        with:
          submodules: recursive
      
      - name: Setup Go
        uses: actions/setup-go@v4
        with:
          go-version: '1.24'
      
      - name: Download dependencies
        run: go mod download
      
      - name: Run migrations
        run: |
          psql postgresql://bedrock:bedrock@localhost:5432/bedrock -f db/migrations/001_create_users_table.up.sql
      
      - name: Run tests
        env:
          DATABASE_URL: postgresql://bedrock:bedrock@localhost:5432/bedrock?sslmode=disable
          JWT_SECRET: test-secret
          SPOTIFY_CLIENT_ID: ${{ secrets.SPOTIFY_CLIENT_ID }}
          SPOTIFY_CLIENT_SECRET: ${{ secrets.SPOTIFY_CLIENT_SECRET }}
          SOUNDCLOUD_CLIENT_IDS: ${{ secrets.SOUNDCLOUD_CLIENT_IDS }}
          GENIUS_ACCESS_TOKEN: ${{ secrets.GENIUS_ACCESS_TOKEN }}
          YOUTUBE_COOKIES: ${{ secrets.YOUTUBE_COOKIES }}
        run: go test -v -timeout 120s ./tests/

Features:

  • PostgreSQL service container
  • Submodule initialization
  • Go 1.24 (should be 1.25 to match go.mod)
  • Migration execution
  • Integration tests with provider secrets
  • 120 second timeout

Required Secrets:

  • SPOTIFY_CLIENT_ID
  • SPOTIFY_CLIENT_SECRET
  • SOUNDCLOUD_CLIENT_IDS
  • GENIUS_ACCESS_TOKEN
  • YOUTUBE_COOKIES

Lint Workflow

File: .github/workflows/lint.yml

name: Lint

on:
  push:
    branches: [ main, develop ]
  pull_request:
    branches: [ main, develop ]

jobs:
  golangci-lint:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@v3
        with:
          submodules: recursive
      
      - name: Setup Go
        uses: actions/setup-go@v4
        with:
          go-version: '1.24'
      
      - name: Run golangci-lint
        uses: golangci/golangci-lint-action@v3
        with:
          version: latest
  
  comment-lint:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@v3
      
      - name: Check for decorative comments
        run: |
          # Fail if decorative comments found (e.g., // ========)
          if grep -r "^[[:space:]]*//[[:space:]]*[=\-*#]\{3,\}" --include="*.go" .; then
            echo "Decorative comments found"
            exit 1
          fi
      
      - name: Check for uppercase-leading comments
        run: |
          # Fail if comments start with uppercase (except TODO, FIXME, NOTE)
          if grep -r "^[[:space:]]*//[[:space:]]*[A-Z]" --include="*.go" . | grep -v "TODO\|FIXME\|NOTE"; then
            echo "Uppercase-leading comments found"
            exit 1
          fi

Linters:

  • golangci-lint - Standard Go linting (gofmt, govet, staticcheck, etc.)
  • Custom comment linter - Enforces comment style (no decorative comments, no uppercase-leading)

Comment Rules:

  • No decorative comments (// ========, // --------, etc.)
  • No uppercase-leading comments (except TODO, FIXME, NOTE)

Production Deployment

Reverse Proxy (TLS Termination)

No Built-in TLS: Application must be deployed behind reverse proxy

Nginx Example:

upstream bedrock_grpc {
    server localhost:50052;
}

upstream bedrock_http {
    server localhost:8080;
}

server {
    listen 443 ssl http2;
    server_name api.example.com;
    
    ssl_certificate /etc/letsencrypt/live/api.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/api.example.com/privkey.pem;
    
    # gRPC endpoint
    location /bedrock.BedrockService/ {
        grpc_pass grpc://bedrock_grpc;
        grpc_set_header X-Real-IP $remote_addr;
        grpc_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
    
    # HTTP proxy endpoints
    location /stream/ {
        proxy_pass http://bedrock_http;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_buffering off;
    }
    
    location /cover/ {
        proxy_pass http://bedrock_http;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }
}

Caddy Example (simpler):

api.example.com {
    reverse_proxy /bedrock.BedrockService/* h2c://localhost:50052
    reverse_proxy /stream/* localhost:8080
    reverse_proxy /cover/* localhost:8080
}

Systemd Service

File: /etc/systemd/system/bedrock-api.service

[Unit]
Description=Bedrock API Server
After=network.target postgresql.service
Requires=postgresql.service

[Service]
Type=simple
User=bedrock
Group=bedrock
WorkingDirectory=/opt/bedrock-api
EnvironmentFile=/opt/bedrock-api/.env
ExecStart=/opt/bedrock-api/bedrock-server
Restart=always
RestartSec=10
StandardOutput=journal
StandardError=journal
SyslogIdentifier=bedrock-api

# Security hardening
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=true
ReadWritePaths=/opt/bedrock-api/logs

[Install]
WantedBy=multi-user.target

Commands:

# Enable service
sudo systemctl enable bedrock-api

# Start service
sudo systemctl start bedrock-api

# Check status
sudo systemctl status bedrock-api

# View logs
sudo journalctl -u bedrock-api -f

Environment Variables (Production)

Secure Storage: Use secrets management (not .env file)

AWS Secrets Manager:

aws secretsmanager get-secret-value --secret-id bedrock-api/production --query SecretString --output text > /tmp/secrets.env
source /tmp/secrets.env
rm /tmp/secrets.env

HashiCorp Vault:

vault kv get -format=json secret/bedrock-api/production | jq -r '.data.data | to_entries[] | "\(.key)=\(.value)"' > /tmp/secrets.env
source /tmp/secrets.env
rm /tmp/secrets.env

Kubernetes Secrets:

apiVersion: v1
kind: Secret
metadata:
  name: bedrock-api-secrets
type: Opaque
stringData:
  DATABASE_URL: postgresql://user:pass@postgres:5432/bedrock
  JWT_SECRET: your-secret
  SPOTIFY_CLIENT_ID: your-id
  SPOTIFY_CLIENT_SECRET: your-secret
  SOUNDCLOUD_CLIENT_IDS: id1,id2,id3
  GENIUS_ACCESS_TOKEN: your-token

Database Migrations (Production)

Manual Execution (current):

psql $DATABASE_URL -f db/migrations/001_create_users_table.up.sql

Automated with golang-migrate (recommended):

# Install migrate
curl -L https://github.com/golang-migrate/migrate/releases/download/v4.16.2/migrate.linux-amd64.tar.gz | tar xvz
sudo mv migrate /usr/local/bin/

# Run migrations
migrate -path db/migrations -database $DATABASE_URL up

# Rollback
migrate -path db/migrations -database $DATABASE_URL down 1

Migration Tracking:

-- golang-migrate creates this table automatically
SELECT * FROM schema_migrations;

Monitoring (Not Implemented)

Recommended Stack:

  • Prometheus (metrics collection)
  • Grafana (visualization)
  • Loki (log aggregation)
  • Jaeger (distributed tracing)

Prometheus Metrics (to implement):

import "github.com/prometheus/client_golang/prometheus"

var (
    requestsTotal = prometheus.NewCounterVec(
        prometheus.CounterOpts{
            Name: "bedrock_requests_total",
            Help: "Total number of requests",
        },
        []string{"method", "status"},
    )
    
    requestDuration = prometheus.NewHistogramVec(
        prometheus.HistogramOpts{
            Name: "bedrock_request_duration_seconds",
            Help: "Request duration in seconds",
        },
        []string{"method"},
    )
    
    providerErrors = prometheus.NewCounterVec(
        prometheus.CounterOpts{
            Name: "bedrock_provider_errors_total",
            Help: "Total provider errors",
        },
        []string{"provider"},
    )
)

Grafana Dashboard (example queries):

# Request rate
rate(bedrock_requests_total[5m])

# Error rate
rate(bedrock_requests_total{status="error"}[5m]) / rate(bedrock_requests_total[5m])

# P95 latency
histogram_quantile(0.95, rate(bedrock_request_duration_seconds_bucket[5m]))

# Provider error rate
rate(bedrock_provider_errors_total[5m])

Logging (Production)

Structured Logging (to implement):

import "go.uber.org/zap"

logger, _ := zap.NewProduction()
defer logger.Sync()

logger.Info("search request",
    zap.String("query", query),
    zap.Int32("limit", limit),
    zap.String("user_id", userID),
)

logger.Error("provider failed",
    zap.String("provider", "spotify"),
    zap.Error(err),
)

Log Aggregation (Loki):

# promtail config
clients:
  - url: http://loki:3100/loki/api/v1/push

scrape_configs:
  - job_name: bedrock-api
    static_configs:
      - targets:
          - localhost
        labels:
          job: bedrock-api
          __path__: /var/log/bedrock-api/*.log

Backup Strategy

PostgreSQL Backups:

# Daily backup script
#!/bin/bash
BACKUP_DIR=/backups/bedrock-api
DATE=$(date +%Y%m%d_%H%M%S)

pg_dump $DATABASE_URL | gzip > $BACKUP_DIR/bedrock_$DATE.sql.gz

# Keep last 30 days
find $BACKUP_DIR -name "bedrock_*.sql.gz" -mtime +30 -delete

# Upload to S3
aws s3 cp $BACKUP_DIR/bedrock_$DATE.sql.gz s3://backups/bedrock-api/

Cron Schedule:

0 2 * * * /opt/bedrock-api/scripts/backup.sh

Point-in-Time Recovery (WAL archiving):

-- Enable WAL archiving in postgresql.conf
wal_level = replica
archive_mode = on
archive_command = 'aws s3 cp %p s3://backups/bedrock-api/wal/%f'

Scaling Strategies

Vertical Scaling:

  • Increase CPU/RAM for single instance
  • Increase PostgreSQL resources
  • Increase connection pool size

Horizontal Scaling:

  • Run multiple application instances behind load balancer
  • Use read replicas for PostgreSQL (if read-heavy)
  • Add Redis for caching (reduce provider API calls)

Load Balancer (nginx):

upstream bedrock_grpc {
    server bedrock-api-1:50052;
    server bedrock-api-2:50052;
    server bedrock-api-3:50052;
}

server {
    listen 443 ssl http2;
    location /bedrock.BedrockService/ {
        grpc_pass grpc://bedrock_grpc;
    }
}

Kubernetes Deployment:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: bedrock-api
spec:
  replicas: 3
  selector:
    matchLabels:
      app: bedrock-api
  template:
    metadata:
      labels:
        app: bedrock-api
    spec:
      containers:
      - name: bedrock-api
        image: bedrock-api:latest
        ports:
        - containerPort: 50052
          name: grpc
        - containerPort: 8080
          name: http
        env:
        - name: DATABASE_URL
          valueFrom:
            secretKeyRef:
              name: bedrock-api-secrets
              key: DATABASE_URL
        resources:
          requests:
            memory: "256Mi"
            cpu: "250m"
          limits:
            memory: "512Mi"
            cpu: "500m"
        livenessProbe:
          exec:
            command:
            - grpc_health_probe
            - -addr=:50052
          initialDelaySeconds: 10
          periodSeconds: 10
        readinessProbe:
          exec:
            command:
            - grpc_health_probe
            - -addr=:50052
          initialDelaySeconds: 5
          periodSeconds: 5
---
apiVersion: v1
kind: Service
metadata:
  name: bedrock-api
spec:
  selector:
    app: bedrock-api
  ports:
  - name: grpc
    port: 50052
    targetPort: 50052
  - name: http
    port: 8080
    targetPort: 8080
  type: LoadBalancer

Deployment Checklist

Pre-Deployment

  • Update Go version in Dockerfile to match go.mod (1.25)
  • Configure environment variables (secrets management)
  • Run database migrations
  • Test provider credentials
  • Configure reverse proxy (TLS)
  • Set up monitoring (Prometheus, Grafana)
  • Set up logging (structured logs, aggregation)
  • Configure backups (PostgreSQL, WAL archiving)
  • Load test (ensure performance under load)
  • Security audit (JWT secret, database credentials, etc.)

Post-Deployment

  • Verify gRPC endpoint (grpcurl)
  • Verify HTTP proxy endpoints (curl)
  • Check logs for errors
  • Monitor metrics (request rate, error rate, latency)
  • Test authentication (register, login, refresh)
  • Test search (all providers)
  • Test streaming (SoundCloud, YouTube Music)
  • Test lyrics (LrcLib, Genius)
  • Verify database connection
  • Test backup restoration

Ongoing Maintenance

  • Monitor provider API changes
  • Rotate JWT secret periodically
  • Update dependencies (go mod tidy)
  • Review logs for errors
  • Monitor disk usage (PostgreSQL, logs)
  • Test backup restoration monthly
  • Update TLS certificates (Let's Encrypt auto-renewal)
  • Review security advisories (Go, dependencies)

Deployment Environments

Development

Infrastructure: Local machine or Docker Compose
Database: PostgreSQL in Docker
Secrets: .env file
TLS: No (HTTP only)
Monitoring: No
Backups: No

Staging

Infrastructure: Single VM or Kubernetes cluster
Database: Managed PostgreSQL (AWS RDS, Google Cloud SQL)
Secrets: Secrets manager (AWS Secrets Manager, Vault)
TLS: Yes (Let's Encrypt)
Monitoring: Prometheus + Grafana
Backups: Daily automated backups

Production

Infrastructure: Kubernetes cluster (multi-region)
Database: Managed PostgreSQL with read replicas
Secrets: Secrets manager with rotation
TLS: Yes (Let's Encrypt or commercial cert)
Monitoring: Full observability stack (Prometheus, Grafana, Loki, Jaeger)
Backups: Hourly backups + WAL archiving + point-in-time recovery
Scaling: Horizontal pod autoscaling (HPA)
High Availability: Multi-zone deployment, load balancing

Cost Estimation (AWS)

Small Deployment (1000 requests/day)

Resource Specification Monthly Cost
EC2 Instance t3.small (2 vCPU, 2 GB RAM) $15
RDS PostgreSQL db.t3.micro (1 vCPU, 1 GB RAM) $15
Load Balancer Application Load Balancer $20
Data Transfer 100 GB/month $9
Total $59/month

Medium Deployment (100k requests/day)

Resource Specification Monthly Cost
EC2 Instances 3x t3.medium (2 vCPU, 4 GB RAM) $90
RDS PostgreSQL db.t3.small (2 vCPU, 2 GB RAM) $30
ElastiCache Redis cache.t3.micro (1 vCPU, 0.5 GB RAM) $12
Load Balancer Application Load Balancer $20
Data Transfer 1 TB/month $90
Total $242/month

Large Deployment (1M requests/day)

Resource Specification Monthly Cost
EKS Cluster Control plane $73
EC2 Instances 10x t3.large (2 vCPU, 8 GB RAM) $600
RDS PostgreSQL db.r5.large (2 vCPU, 16 GB RAM) + read replica $300
ElastiCache Redis cache.r5.large (2 vCPU, 13 GB RAM) $150
Load Balancer Application Load Balancer $20
Data Transfer 10 TB/month $900
Total $2,043/month

Note: Costs exclude provider API fees (Spotify, Genius, etc.)

Deployment Recommendations for Metadata Aggregator

Adopt

  • Multi-stage Docker build (minimal runtime image)
  • Docker Compose for local development
  • GitHub Actions for CI/CD
  • Reverse proxy for TLS termination
  • Systemd service for production

Avoid

  • Manual migrations (use golang-migrate)
  • No monitoring (implement Prometheus)
  • No structured logging (use zap or zerolog)
  • Go version mismatch (keep Dockerfile and go.mod in sync)

Enhance

  • Add health check endpoint (implement GetServiceStatus properly)
  • Add graceful shutdown (handle SIGTERM)
  • Add readiness probe (check database connection)
  • Add metrics endpoint (/metrics for Prometheus)
  • Add Redis for caching
  • Add backup automation
  • Add deployment documentation