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

840 lines
17 KiB
Markdown

# Meelo Deployment
## Deployment Overview
Meelo deploys as a multi-container Docker application orchestrated by Docker Compose. Three deployment variants support different use cases: production (pre-built images), development (hot reload), and local build (custom images).
## Docker Compose Variants
### Production (docker-compose.yml)
**Use Case**: End users running stable releases
**Images**: Pre-built from Docker Hub
**Startup Time**: Fast (no build step)
**Updates**: Pull new images, restart containers
```yaml
services:
server:
image: arthichaud/meelo-server:latest
restart: always
depends_on:
db:
condition: service_healthy
meilisearch:
condition: service_healthy
mq:
condition: service_healthy
environment:
- DATABASE_URL=postgresql://postgres:postgres@db:5432/meelo
- MEILISEARCH_URL=http://meilisearch:7700
- RABBITMQ_URL=amqp://guest:guest@mq:5672
volumes:
- ${CONFIG_DIR}:/config
- ${DATA_DIR}:/data
```
**Key Features**:
- `restart: always` for automatic recovery
- Health check dependencies ensure startup order
- Environment variables from .env
- Volumes for config and data persistence
### Development (docker-compose.dev.yml)
**Use Case**: Contributors developing features
**Images**: Built from source with hot reload
**Startup Time**: Slower (build + watch)
**Updates**: Automatic on file save
```yaml
services:
server:
build:
context: ./server
dockerfile: Dockerfile.dev
volumes:
- ./server/src:/app/src
- ./server/prisma:/app/prisma
ports:
- "4000:4000"
environment:
- NODE_ENV=development
command: npm run start:dev
```
**Key Features**:
- Source directories mounted for hot reload
- Exposed ports for debugging
- Development commands (start:dev, test:watch)
- No restart policy (manual control)
### Local Build (docker-compose.local.yml)
**Use Case**: Testing Dockerfile changes, custom builds
**Images**: Built from source
**Startup Time**: Slow (full build)
**Updates**: Rebuild images manually
```yaml
services:
server:
build:
context: ./server
dockerfile: Dockerfile
restart: unless-stopped
```
**Key Features**:
- Builds production images locally
- Tests Dockerfile changes before pushing
- `unless-stopped` restart policy
## Service Configuration
### Server (NestJS)
**Image**: arthichaud/meelo-server
**Port**: 4000
**Dependencies**: PostgreSQL, MeiliSearch, RabbitMQ
**Environment Variables**:
```bash
DATABASE_URL=postgresql://postgres:postgres@db:5432/meelo
MEILISEARCH_URL=http://meilisearch:7700
RABBITMQ_URL=amqp://guest:guest@mq:5672
JWT_SIGNATURE=your_secret_key
PORT=4000
PUBLIC_URL=https://meelo.example.com
CONFIG_DIR=/config
DATA_DIR=/data
```
**Volumes**:
- `${CONFIG_DIR}:/config` - settings.json
- `${DATA_DIR}:/data` - music files (read-only)
**Health Check**:
```yaml
healthcheck:
test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost:4000/api/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
```
### Scanner (Go)
**Image**: arthichaud/meelo-scanner
**Port**: 8133
**Dependencies**: Server
**Environment Variables**:
```bash
SERVER_URL=http://server:4000
API_KEY=your_api_key
```
**Volumes**:
- `${DATA_DIR}:/data` - music files (read-only)
**Health Check**:
```yaml
healthcheck:
test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost:8133/"]
interval: 30s
timeout: 10s
retries: 3
```
### Matcher (Python)
**Image**: arthichaud/meelo-matcher
**Port**: 6789
**Dependencies**: Server, RabbitMQ
**Environment Variables**:
```bash
SERVER_URL=http://server:4000
RABBITMQ_URL=amqp://guest:guest@mq:5672
GENIUS_ACCESS_TOKEN=your_genius_token
DISCOGS_ACCESS_TOKEN=your_discogs_token
```
**Health Check**:
```yaml
healthcheck:
test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost:6789/health"]
interval: 30s
timeout: 10s
retries: 3
```
### Front (Next.js)
**Image**: arthichaud/meelo-front
**Port**: 3000
**Dependencies**: Server
**Environment Variables**:
```bash
NEXT_PUBLIC_API_URL=http://localhost/api
```
**Health Check**:
```yaml
healthcheck:
test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost:3000/api/health"]
interval: 30s
timeout: 10s
retries: 3
```
### PostgreSQL
**Image**: postgres:alpine3.14
**Port**: 5432 (internal only)
**Volume**: meelo_db
**Environment Variables**:
```bash
POSTGRES_USER=postgres
POSTGRES_PASSWORD=postgres
POSTGRES_DB=meelo
```
**Health Check**:
```yaml
healthcheck:
test: ["CMD", "pg_isready", "-U", "postgres"]
interval: 10s
timeout: 5s
retries: 5
```
### MeiliSearch
**Image**: getmeili/meilisearch:v1.5
**Port**: 7700 (internal only)
**Volume**: meelo_search
**Environment Variables**:
```bash
MEILI_ENV=production
MEILI_NO_ANALYTICS=true
```
**Health Check**:
```yaml
healthcheck:
test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost:7700/health"]
interval: 10s
timeout: 5s
retries: 5
```
### RabbitMQ
**Image**: rabbitmq:4.2-alpine
**Port**: 5672 (AMQP), 15672 (management UI)
**Volume**: meelo_rabbitmq_data
**Health Check**:
```yaml
healthcheck:
test: ["CMD", "rabbitmq-diagnostics", "ping"]
interval: 10s
timeout: 5s
retries: 5
```
### Kyoo Transcoder
**Image**: zoriya/kyoo_transcoder:latest
**Port**: 7666 (internal only)
**Volume**: meelo_transcoder_cache
**Environment Variables**:
```bash
TRANSCODER_CACHE_ROOT=/cache
```
No health check (optional service).
### Nginx
**Image**: nginx:1.29.7-alpine
**Port**: 80 (exposed to host)
**Config**: Mounted from nginx.conf
**Configuration**:
```nginx
server {
listen 80;
server_name localhost;
location / {
proxy_pass http://front:3000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
location /api/ {
proxy_pass http://server:4000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
location /scanner/ {
proxy_pass http://scanner:8133;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
location /matcher/ {
proxy_pass http://matcher:6789;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
location /api/events {
proxy_pass http://server:4000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}
```
**Health Check**:
```yaml
healthcheck:
test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost/"]
interval: 30s
timeout: 10s
retries: 3
```
## Volumes
### Named Volumes
```yaml
volumes:
meelo_db:
driver: local
meelo_search:
driver: local
meelo_rabbitmq_data:
driver: local
meelo_transcoder_cache:
driver: local
```
**Persistence**:
- `meelo_db`: PostgreSQL data (critical, backup regularly)
- `meelo_search`: MeiliSearch index (can rebuild from database)
- `meelo_rabbitmq_data`: Message queue state (can lose without data loss)
- `meelo_transcoder_cache`: Transcoded video segments (can delete to free space)
### Bind Mounts
```yaml
volumes:
- ${CONFIG_DIR}:/config
- ${DATA_DIR}:/data:ro
```
**Paths**:
- `CONFIG_DIR`: Directory containing settings.json (default: ./config)
- `DATA_DIR`: Music library directory (default: ./data)
**Permissions**:
- `DATA_DIR` mounted read-only (`:ro`) to prevent accidental modification
- Services run as non-root user (UID 1000)
## Startup Order
Docker Compose orchestrates startup using health checks:
```
1. PostgreSQL starts
└─ Health check: pg_isready
2. MeiliSearch starts
└─ Health check: GET /health
3. RabbitMQ starts
└─ Health check: rabbitmq-diagnostics ping
4. Server starts (depends on db, meilisearch, mq)
└─ Runs Prisma migrations
└─ Seeds initial data
└─ Health check: GET /api/health
5. Scanner starts (depends on server)
└─ Registers with Server
└─ Health check: GET /
6. Matcher starts (depends on server, mq)
└─ Connects to RabbitMQ
└─ Health check: GET /health
7. Front starts (depends on server)
└─ SSR requires Server API
└─ Health check: GET /api/health
8. Transcoder starts (no dependencies)
9. Nginx starts (depends on all application services)
└─ Health check: GET /
```
**Start Period**: Each service has a start period (30-40s) before health checks begin. This allows initialization without false failures.
## Configuration Files
### .env
Environment variables for deployment:
```bash
# Ports
PORT=4000
FRONT_PORT=3000
SCANNER_PORT=8133
MATCHER_PORT=6789
# URLs
PUBLIC_URL=https://meelo.example.com
# Directories
CONFIG_DIR=./config
DATA_DIR=/path/to/music
# Database
DATABASE_URL=postgresql://postgres:postgres@db:5432/meelo
# Search
MEILISEARCH_URL=http://meilisearch:7700
# Message Queue
RABBITMQ_URL=amqp://guest:guest@mq:5672
# Authentication
JWT_SIGNATURE=your_secret_key_here
ALLOW_ANONYMOUS=0
# External Providers
GENIUS_ACCESS_TOKEN=your_genius_token
DISCOGS_ACCESS_TOKEN=your_discogs_token
# Last.fm OAuth
LASTFM_API_KEY=your_lastfm_key
LASTFM_API_SECRET=your_lastfm_secret
# CORS
CORS_ORIGINS=https://meelo.example.com
```
### settings.json
User preferences (stored in CONFIG_DIR):
```json
{
"trackRegex": "(?P<artist>[^/]+)/(?P<album>[^/]+)/(?P<disc>\\d+)-(?P<track>\\d+) (?P<title>.+)\\.(?P<ext>\\w+)",
"metadata": {
"source": "providers",
"order": ["musicbrainz", "genius", "wikipedia", "lrclib"]
},
"providers": {
"musicbrainz": { "enabled": true },
"genius": { "enabled": true },
"wikipedia": { "enabled": true },
"wikidata": { "enabled": true },
"discogs": { "enabled": false },
"allmusic": { "enabled": false },
"metacritic": { "enabled": false },
"lrclib": { "enabled": true }
},
"compilations": {
"detectByArtist": true,
"detectByFolder": true,
"keywords": ["Various Artists", "Compilation", "Soundtrack"]
}
}
```
## First-Time Setup
### 1. Clone Repository
```bash
git clone https://github.com/Arthi-chaud/Meelo.git
cd Meelo
```
### 2. Configure Environment
```bash
cp .env.example .env
nano .env
```
Fill in required values:
- `DATA_DIR`: Path to music library
- `JWT_SIGNATURE`: Random secret key
- `GENIUS_ACCESS_TOKEN`: Genius API token (optional)
- `DISCOGS_ACCESS_TOKEN`: Discogs API token (optional)
- `LASTFM_API_KEY`, `LASTFM_API_SECRET`: Last.fm OAuth credentials (optional)
### 3. Create Settings File
```bash
mkdir -p config
nano config/settings.json
```
Copy example settings from above, adjust `trackRegex` to match your file naming.
### 4. Start Services
```bash
docker-compose up -d
```
Wait for all services to become healthy:
```bash
docker-compose ps
```
### 5. Register Admin User
Navigate to `http://localhost` and register first user (becomes admin automatically).
### 6. Create Library
1. Go to Settings > Libraries
2. Click "Add Library"
3. Enter name and path (must match DATA_DIR mount)
4. Save
### 7. Trigger Initial Scan
```bash
curl -X POST http://localhost/scanner/scan
```
Monitor progress:
```bash
curl http://localhost/scanner/tasks
```
### 8. Wait for Enrichment
Matcher processes files asynchronously. Check progress in UI (Artists/Albums pages populate as metadata arrives).
## Updates
### Pull New Images
```bash
docker-compose pull
```
### Restart Services
```bash
docker-compose up -d
```
Docker Compose recreates containers with new images. Volumes persist data.
### Database Migrations
Prisma migrations run automatically on Server startup. No manual intervention needed.
## Backup
### Database Backup
```bash
docker exec meelo-db pg_dump -U postgres meelo > backup.sql
```
### Restore Database
```bash
docker exec -i meelo-db psql -U postgres meelo < backup.sql
```
### Volume Backup
```bash
docker run --rm -v meelo_db:/data -v $(pwd):/backup alpine tar czf /backup/db.tar.gz /data
```
### Restore Volume
```bash
docker run --rm -v meelo_db:/data -v $(pwd):/backup alpine tar xzf /backup/db.tar.gz -C /
```
### Config Backup
```bash
cp -r config config.backup
```
## Monitoring
### Service Status
```bash
docker-compose ps
```
Shows health status for all services.
### Logs
**All Services**:
```bash
docker-compose logs -f
```
**Specific Service**:
```bash
docker-compose logs -f server
```
**Last 100 Lines**:
```bash
docker-compose logs --tail=100 server
```
### Resource Usage
```bash
docker stats
```
Shows CPU, memory, network, and disk I/O per container.
## Troubleshooting
### Service Won't Start
Check logs:
```bash
docker-compose logs <service>
```
Common issues:
- **Database connection failed**: PostgreSQL not healthy yet, wait longer
- **Port already in use**: Change port in .env
- **Volume mount failed**: Check DATA_DIR path exists and has correct permissions
### Health Check Failing
Increase start period in docker-compose.yml:
```yaml
healthcheck:
start_period: 60s # Increase from 40s
```
### Out of Memory
Increase Docker memory limit (Docker Desktop settings) or reduce concurrent services.
### Slow Performance
Check resource usage:
```bash
docker stats
```
Bottlenecks:
- **High CPU on Matcher**: Too many providers enabled, disable optional ones
- **High memory on MeiliSearch**: Large library, increase Docker memory
- **High I/O on Scanner**: Slow disk, use SSD
## Production Deployment
### Reverse Proxy
Use Nginx or Caddy as external reverse proxy:
```nginx
server {
listen 443 ssl http2;
server_name meelo.example.com;
ssl_certificate /path/to/cert.pem;
ssl_certificate_key /path/to/key.pem;
location / {
proxy_pass http://localhost:80;
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;
}
}
```
### HTTPS
Use Let's Encrypt with Certbot:
```bash
certbot --nginx -d meelo.example.com
```
Or use Caddy (automatic HTTPS):
```
meelo.example.com {
reverse_proxy localhost:80
}
```
### Firewall
Open only port 443 (HTTPS):
```bash
ufw allow 443/tcp
ufw enable
```
### Security Hardening
- Set `ALLOW_ANONYMOUS=0` in .env
- Use strong `JWT_SIGNATURE` (32+ random characters)
- Restrict `CORS_ORIGINS` to your domain
- Run Docker in rootless mode
- Enable Docker Content Trust
### Monitoring
Use Prometheus + Grafana (future enhancement, not built-in).
### Backups
Automate database backups with cron:
```bash
0 2 * * * docker exec meelo-db pg_dump -U postgres meelo > /backups/meelo-$(date +\%Y\%m\%d).sql
```
Rotate backups:
```bash
find /backups -name "meelo-*.sql" -mtime +30 -delete
```
## CI/CD
### GitHub Actions
Meelo uses GitHub Actions for CI/CD. Workflows per service:
**server.yml**:
```yaml
name: Server CI/CD
on:
push:
branches: [main]
paths:
- 'server/**'
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 20
- run: npm ci
working-directory: server
- run: npm run lint
working-directory: server
- run: npm test
working-directory: server
- uses: SonarSource/sonarcloud-github-action@master
env:
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
build:
needs: test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: docker/setup-buildx-action@v2
- uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- uses: docker/build-push-action@v4
with:
context: ./server
push: true
tags: arthichaud/meelo-server:latest
```
Similar workflows for scanner, matcher, front.
### Quality Gates
SonarCloud enforces:
- Code coverage > 80%
- No critical bugs
- No security vulnerabilities
- Maintainability rating A
Failing quality gates block merges.
## Scaling
### Horizontal Scaling
Run multiple instances of stateless services:
```yaml
services:
scanner:
image: arthichaud/meelo-scanner
deploy:
replicas: 3
```
Load balance with Nginx upstream:
```nginx
upstream scanner {
server scanner_1:8133;
server scanner_2:8133;
server scanner_3:8133;
}
location /scanner/ {
proxy_pass http://scanner;
}
```
### Vertical Scaling
Increase container resources:
```yaml
services:
server:
deploy:
resources:
limits:
cpus: '2'
memory: 4G
reservations:
cpus: '1'
memory: 2G
```
## Summary
Meelo's deployment uses Docker Compose to orchestrate 8 services with health checks ensuring correct startup order. Three variants (production, development, local build) support different use cases. Configuration via .env and settings.json separates deployment and user preferences. Volumes persist data, bind mounts provide access to music files. First-time setup involves configuring environment, creating settings, starting services, registering admin, creating library, and triggering scan. Updates are simple (pull images, restart). Backups cover database, volumes, and config. Production deployment adds reverse proxy, HTTPS, firewall, and security hardening. CI/CD via GitHub Actions ensures quality. Scaling options include horizontal (multiple instances) and vertical (more resources).