-
Notifications
You must be signed in to change notification settings - Fork 0
Docker Deployment
Complete guide for deploying Grimnir Radio using Docker Compose.
- Quick Start
- Prerequisites
- Deployment Modes
- Configuration
- Services
- Advanced Usage
- Troubleshooting
- Upgrading
Get Grimnir Radio running in 30 seconds:
# Clone repository
git clone https://github.com/friendsincode/grimnir_radio.git
cd grimnir_radio
# Run quick-start script (handles everything automatically)
./scripts/docker-quick-start.shThe script will:
- ✓ Check Docker and Docker Compose are installed
- ✓ Create
.envfile with secure random passwords - ✓ Build Docker images
- ✓ Start all services (API, Media Engine, PostgreSQL, Redis, Icecast2)
- ✓ Display access URLs and credentials
Default URLs:
- API: http://localhost:8080
- Metrics: http://localhost:9000/metrics
- Icecast2: http://localhost:8000
- Icecast Admin: http://localhost:8000/admin
- Docker 20.10+ (Install Docker)
- Docker Compose 2.0+ (included with Docker Desktop, or Install separately)
Minimum:
- 2 CPU cores
- 4 GB RAM
- 20 GB disk space
Recommended (Production):
- 4+ CPU cores
- 8+ GB RAM
- 100+ GB disk space (for media library)
Ensure these ports are available:
- 8080 - Grimnir Radio HTTP API
- 9000 - Prometheus metrics
- 9091 - Media Engine gRPC
- 8000 - Icecast2 streaming server
- 5432 - PostgreSQL database
- 6379 - Redis
Full production-ready deployment with all dependencies included.
What's Included:
- ✓ Grimnir Radio Control Plane
- ✓ Media Engine (GStreamer)
- ✓ PostgreSQL database
- ✓ Redis (event bus & leader election)
- ✓ Icecast2 streaming server
- ✓ Health checks on all services
- ✓ Auto-generated secure passwords
- ✓ Persistent data volumes
Deploy:
./scripts/docker-quick-start.shStop:
./scripts/docker-quick-start.sh --stopClean (remove all data):
./scripts/docker-quick-start.sh --cleanManual deployment with full control over configuration.
Steps:
- Create environment file:
cp .env.example .env
# Edit .env with your preferred settings
nano .env- Build images:
docker-compose build- Start services:
docker-compose up -d- Check status:
docker-compose ps
docker-compose logs -fOptimized for local development with debug logging and hot-reloading.
Deploy:
./scripts/docker-quick-start.sh --devOr manually:
cp .env.example .env
# Enable debug logging
sed -i 's/LOG_LEVEL=info/LOG_LEVEL=debug/' .env
sed -i 's/ENVIRONMENT=production/ENVIRONMENT=development/' .env
docker-compose up --buildAll configuration is managed via .env file. See .env.example for full list.
Key Settings:
GRIMNIR_HTTP_BIND=0.0.0.0
GRIMNIR_HTTP_PORT=8080POSTGRES_PASSWORD=your-secure-password
GRIMNIR_DB_BACKEND=postgres
GRIMNIR_DB_DSN=host=postgres port=5432 user=grimnir password=... dbname=grimnirJWT_SIGNING_KEY=your-random-secret-change-in-production
GRIMNIR_JWT_TTL_MINUTES=15ICECAST_ADMIN_USERNAME=admin
ICECAST_ADMIN_PASSWORD=secure-password
ICECAST_SOURCE_PASSWORD=source-password
ICECAST_PORT=8000
ICECAST_MAX_CLIENTS=100
ICECAST_MAX_SOURCES=10# Filesystem (default)
GRIMNIR_MEDIA_BACKEND=filesystem
GRIMNIR_MEDIA_ROOT=/var/lib/grimnir/media
# S3 (optional)
# GRIMNIR_MEDIA_BACKEND=s3
# GRIMNIR_S3_BUCKET=grimnir-media
# GRIMNIR_S3_REGION=us-east-1
# GRIMNIR_S3_ACCESS_KEY_ID=...
# GRIMNIR_S3_SECRET_ACCESS_KEY=...Create docker-compose.override.yml for customizations:
cp docker-compose.override.yml.example docker-compose.override.yml
nano docker-compose.override.ymlExample overrides:
Change API port:
services:
grimnir:
ports:
- "3000:8080"Mount local media directory:
services:
grimnir:
volumes:
- ./my-media:/var/lib/grimnir/mediaUse external database:
services:
grimnir:
environment:
GRIMNIR_DB_DSN: "host=external-db.example.com port=5432 user=grimnir password=secret dbname=grimnir"
depends_on:
- redis
- mediaengine
postgres:
profiles:
- disabled # Disable local postgres service┌─────────────────────────────────────────────┐
│ Load Balancer / Nginx │
│ (optional) │
└────────────────┬────────────────────────────┘
│
┌────────────────▼────────────────────────────┐
│ Grimnir Radio Control Plane │
│ (HTTP API, Scheduler) │
│ Port: 8080, 9000 │
└──┬─────────┬─────────┬───────────┬──────────┘
│ │ │ │
▼ ▼ ▼ ▼
┌──────┐ ┌────────┐ ┌──────┐ ┌──────────┐
│ DB │ │ Redis │ │Media │ │ Icecast2 │
│(PG) │ │ │ │Engine│ │ │
│:5432 │ │ :6379 │ │:9091 │ │ :8000 │
└──────┘ └────────┘ └──────┘ └──────────┘
Container: grimnir-radio
Ports: 8080 (HTTP API), 9000 (metrics)
Purpose: REST API, scheduler, authentication, playout coordinator
Endpoints:
-
GET /healthz- Health check -
GET /api/v1/*- REST API -
GET /metrics- Prometheus metrics -
WS /api/v1/events- WebSocket event stream
Container: grimnir-mediaengine
Ports: 9091 (gRPC), 9092 (metrics)
Purpose: Audio processing, GStreamer pipelines, DSP
Features:
- Crossfading with cue points
- Loudness normalization (EBU R128)
- AGC, compression, limiting
- Live input routing
- Webstream relay with failover
Container: grimnir-postgres
Ports: 5432
Purpose: Primary database for metadata, schedule, users
Volume: postgres-data (persistent)
Container: grimnir-redis
Ports: 6379
Purpose: Event bus, leader election, session storage
Volume: redis-data (persistent with AOF)
Container: grimnir-icecast
Ports: 8000
Purpose: HTTP streaming server for audio broadcast
Volume: icecast-logs (logs)
Admin URL: http://localhost:8000/admin
Run multiple API instances with leader election for high availability.
Create docker-compose.override.yml:
version: '3.8'
services:
grimnir:
environment:
LEADER_ELECTION_ENABLED: "true"
GRIMNIR_INSTANCE_ID: grimnir-1
grimnir-2:
image: grimnir-radio:latest
container_name: grimnir-radio-2
environment:
# Copy all from grimnir service
LEADER_ELECTION_ENABLED: "true"
GRIMNIR_INSTANCE_ID: grimnir-2
# ... other env vars ...
ports:
- "8081:8080"
- "9001:9000"
networks:
- grimnir-network
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
mediaengine:
condition: service_healthy
grimnir-3:
image: grimnir-radio:latest
container_name: grimnir-radio-3
environment:
LEADER_ELECTION_ENABLED: "true"
GRIMNIR_INSTANCE_ID: grimnir-3
# ... other env vars ...
ports:
- "8082:8080"
- "9002:9000"
networks:
- grimnir-network
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
mediaengine:
condition: service_healthyStart:
docker-compose up -dAdd nginx load balancer:
upstream grimnir_api {
least_conn;
server localhost:8080;
server localhost:8081;
server localhost:8082;
}
server {
listen 80;
server_name radio.example.com;
location /api/ {
proxy_pass http://grimnir_api;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
location /events {
proxy_pass http://grimnir_api;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}Add to docker-compose.override.yml:
services:
prometheus:
image: prom/prometheus:latest
container_name: grimnir-prometheus
ports:
- "9090:9090"
volumes:
- ./deploy/prometheus/prometheus.yml:/etc/prometheus/prometheus.yml:ro
- ./deploy/prometheus/alerts.yml:/etc/prometheus/alerts.yml:ro
- prometheus-data:/prometheus
command:
- '--config.file=/etc/prometheus/prometheus.yml'
- '--storage.tsdb.path=/prometheus'
networks:
- grimnir-network
grafana:
image: grafana/grafana:latest
container_name: grimnir-grafana
ports:
- "3000:3000"
environment:
GF_SECURITY_ADMIN_PASSWORD: admin
volumes:
- grafana-data:/var/lib/grafana
- ./deploy/grafana/dashboards:/etc/grafana/provisioning/dashboards:ro
- ./deploy/grafana/datasources:/etc/grafana/provisioning/datasources:ro
networks:
- grimnir-network
depends_on:
- prometheus
volumes:
prometheus-data:
grafana-data:Start:
docker-compose up -d prometheus grafanaAccess:
- Prometheus: http://localhost:9090
- Grafana: http://localhost:3000 (admin/admin)
Add Jaeger to docker-compose.override.yml:
services:
jaeger:
image: jaegertracing/all-in-one:latest
container_name: grimnir-jaeger
ports:
- "16686:16686" # Jaeger UI
- "4317:4317" # OTLP gRPC
environment:
COLLECTOR_OTLP_ENABLED: "true"
networks:
- grimnir-network
grimnir:
environment:
TRACING_ENABLED: "true"
OTLP_ENDPOINT: "jaeger:4317"
TRACING_SAMPLE_RATE: "1.0"Start:
docker-compose up -dAccess Jaeger UI: http://localhost:16686
Add nginx with certbot:
services:
nginx:
image: nginx:alpine
container_name: grimnir-nginx
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro
- ./certbot/conf:/etc/letsencrypt:ro
- ./certbot/www:/var/www/certbot:ro
networks:
- grimnir-network
depends_on:
- grimnir
certbot:
image: certbot/certbot
container_name: grimnir-certbot
volumes:
- ./certbot/conf:/etc/letsencrypt
- ./certbot/www:/var/www/certbot
entrypoint: "/bin/sh -c 'trap exit TERM; while :; do certbot renew; sleep 12h & wait $${!}; done;'"Get certificate:
docker-compose run --rm certbot certonly --webroot \
--webroot-path /var/www/certbot \
-d radio.example.com \
--email admin@example.com \
--agree-tos \
--no-eff-email# View all services
docker-compose ps
# View logs
docker-compose logs -f
# View specific service logs
docker-compose logs -f grimnir
docker-compose logs -f mediaengine
docker-compose logs -f icecast
# Check health
docker-compose exec grimnir curl -f http://localhost:8080/healthzError: failed to connect to database
Solution:
# Check postgres is running and healthy
docker-compose ps postgres
# Check database credentials
docker-compose exec postgres psql -U grimnir -d grimnir -c "SELECT 1;"
# Restart database
docker-compose restart postgres
# Verify DSN in .env matches container settings
grep GRIMNIR_DB_DSN .envError: rpc error: code = Unavailable
Solution:
# Check media engine is running
docker-compose ps mediaengine
# Check gRPC port
docker-compose exec mediaengine netstat -tlnp | grep 9091
# Test gRPC connection
docker-compose exec grimnir nc -zv mediaengine 9091
# Check media engine logs
docker-compose logs mediaengineError: connection refused on port 8000
Solution:
# Check icecast is running
docker-compose ps icecast
# Check port binding
docker-compose port icecast 8000
# Test connection
curl -I http://localhost:8000/status.xsl
# Check logs
docker-compose logs icecastError: bind: address already in use
Solution:
# Find what's using the port (e.g., 8080)
lsof -i :8080
netstat -tlnp | grep 8080
# Kill the process or change port in docker-compose.override.ymlError: permission denied when accessing volumes
Solution:
# Fix volume permissions
docker-compose down
sudo chown -R 1000:1000 ./media-data
docker-compose up -d
# Or run with user override
docker-compose run --user $(id -u):$(id -g) grimnir# Stop and remove all containers, volumes, and images
docker-compose down -v --rmi all
# Clean up Docker system
docker system prune -a --volumes
# Start fresh
./scripts/docker-quick-start.sh- Backup data:
# Backup database
docker-compose exec postgres pg_dump -U grimnir grimnir > backup.sql
# Backup media
docker-compose cp grimnir:/var/lib/grimnir/media ./media-backup- Pull new version:
git pull origin main- Update images:
docker-compose build --pull- Run migrations:
docker-compose run --rm grimnir /usr/local/bin/grimnirradio migrate- Restart services:
docker-compose up -d- Verify:
docker-compose logs -f grimnir
curl http://localhost:8080/healthz# Stop current version
docker-compose down
# Checkout previous version
git checkout v1.0.0
# Restore database backup
docker-compose up -d postgres
docker-compose exec -T postgres psql -U grimnir grimnir < backup.sql
# Restart services
docker-compose up -dBefore deploying to production:
- Change all default passwords in
.env - Use strong JWT signing key (32+ random characters)
- Enable TLS/SSL with valid certificates
- Configure firewall rules (only expose 80, 443)
- Set up database backups (daily)
- Configure Prometheus alerting
- Set up log aggregation (ELK, Loki)
- Enable Redis persistence (AOF + RDB)
- Configure resource limits in docker-compose.yml
- Set up health check monitoring (UptimeRobot, Pingdom)
- Test disaster recovery procedure
- Document runbooks for common issues
- Enable multi-instance deployment for HA
- Configure CDN for Icecast streams (optional)
- Set up automated updates (Watchtower)
- Documentation: Wiki Home
- API Reference: API Reference
- Migration Guide: Migration Guide
- Architecture: Architecture
- GitHub Issues: https://github.com/friendsincode/grimnir_radio/issues
- Docker Hub: (TBD)
Grimnir Radio is licensed under the GNU Affero General Public License v3.0 or later.
See LICENSE for full text.
Built in honor of Grimnir. For the community, by the community.
Getting Started
Core Concepts
Deployment
Integration
Operations
Development
Reference