This guide provides comprehensive instructions for deploying the StreamSource Rails application to DigitalOcean with full CI/CD automation via GitHub Actions.
StreamSource uses a modern deployment stack with:
- CI/CD: GitHub Actions for automated testing and deployment
- Infrastructure: DigitalOcean Droplet with Ubuntu 24.04 LTS
- Application Server: Puma with Unix socket communication
- Web Server: Nginx with SSL/TLS termination
- Database: PostgreSQL 18
- Cache/Sessions: Redis 8
- Real-time: ActionCable WebSocket support
- Security: fail2ban, UFW firewall, SSL/TLS, rate limiting
- Monitoring: Health checks, structured logging, Prometheus metrics
Configure these secrets in your GitHub repository (Settings > Secrets and variables > Actions):
# DigitalOcean
DO_API_TOKEN=your_digitalocean_api_token
DROPLET_ID=your_droplet_id
DROPLET_HOST=your_domain_or_ip
# SSH Access
DEPLOY_SSH_KEY=your_private_ssh_key_content
# Optional Notifications
SLACK_WEBHOOK=your_slack_webhook_url
- DigitalOcean account with API access
- Domain name (recommended) or IP address
- SSH key pair for deployment access
- Docker Engine + Docker Compose plugin (recommended for production)
StreamSource is container-first. In production, build a Docker image and run it without installing Ruby/Node on the host.
Install Docker Engine and the Docker Compose plugin on your server (use Dockerβs official install docs for your OS).
Prefer managed PostgreSQL/Redis for production. If you want local containers, you can use Docker Compose:
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d db redisCopy deploy/.env.production.template to a server-local path (for example /var/www/streamsource/.env.production) and
fill in production values. Set DATABASE_URL/REDIS_URL to point at your managed services or container hostnames:
DATABASE_URL=postgres://streamsource:your_secure_password@streamsource-db:5432/streamsource_production
REDIS_URL=redis://streamsource-redis:6379/0If you deploy from a container registry, set STREAMSOURCE_IMAGE=ghcr.io/your-org/streamsource:tag when running
Docker Compose.
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d webThe container entrypoint will create/migrate and seed the database on first boot.
Terminate TLS with your cloud load balancer or a reverse proxy (Nginx/Caddy/Traefik) and forward traffic to port 3000.
The sections below assume a host-based install with systemd + Nginx. If youβre running Docker-first, you can skip this entire section.
Recommended Specifications:
- OS: Ubuntu 24.04 LTS
- Size: 2GB RAM, 1 vCPU minimum (4GB+ recommended for production)
- Region: Choose closest to your users
- SSH Keys: Add your public key
Run the automated setup script on your fresh droplet:
# SSH into your droplet as root
ssh root@your_droplet_ip
# Download and run setup script
curl -fsSL https://raw.githubusercontent.com/YOUR_USERNAME/streamsource/main/deploy/setup-droplet.sh | bashThe setup script will:
- β Update system packages
- β Install Ruby 4.0.1 via rbenv
- β Install Node.js 24.x and Yarn (only required when building assets on the host)
- β Install and configure PostgreSQL 18
- β Install and configure Redis 8
- β Install and configure Nginx
- β Set up UFW firewall (ports 22, 80, 443)
- β Configure fail2ban for security
- β Create deploy user with sudo access
- β Set up application directory structure
After running the setup script:
# Set password for deploy user
passwd deploy
# Switch to deploy user
su - deploy
# Create SSH directory and add your public key
mkdir -p ~/.ssh
echo "YOUR_PUBLIC_KEY" > ~/.ssh/authorized_keys
chmod 600 ~/.ssh/authorized_keys
chmod 700 ~/.ssh
# Create shared configuration files
mkdir -p /var/www/streamsource/shared/{config,log,tmp,storage}Configure PostgreSQL with a secure password:
# Update PostgreSQL password (replace with a strong password)
sudo -u postgres psql -c "ALTER USER streamsource PASSWORD 'your_secure_password';"
# Test connection
psql -U streamsource -h localhost -d streamsource_productionFor production, set up SSL with Let's Encrypt:
# Replace with your domain
sudo certbot --nginx -d yourdomain.com
# Test automatic renewal
sudo certbot renew --dry-runCreate the production environment file:
# Copy template
cp /var/www/streamsource/shared/config/.env.production.template /var/www/streamsource/shared/config/.env.production
# Edit with your actual values
nano /var/www/streamsource/shared/config/.env.productionRequired Environment Variables:
# Core Rails Configuration
SECRET_KEY_BASE=your_secret_key_base_64_chars_minimum
RAILS_ENV=production
RAILS_LOG_TO_STDOUT=true
RAILS_SERVE_STATIC_FILES=true
# Database
DATABASE_URL=postgres://streamsource:your_password@localhost:5432/streamsource_production
# Redis
REDIS_URL=redis://localhost:6379/0
# Application
RAILS_HOSTNAME=yourdomain.com
FORCE_SSL=true
# ActionCable
ACTION_CABLE_ADAPTER=redis
ACTION_CABLE_URL=redis://localhost:6379/1
# Security
RATE_LIMIT_REDIS_URL=redis://localhost:6379/2Generate a secure SECRET_KEY_BASE:
bundle exec rails secretCopy and enable systemd services:
# Copy service file
sudo cp /var/www/streamsource/deploy/puma.service /etc/systemd/system/
sudo systemctl daemon-reload
# Enable services
sudo systemctl enable puma
sudo systemctl enable nginx
sudo systemctl enable postgresql
sudo systemctl enable redis-server
# Copy nginx configuration
sudo cp /var/www/streamsource/deploy/nginx.conf /etc/nginx/sites-available/streamsource
sudo ln -sf /etc/nginx/sites-available/streamsource /etc/nginx/sites-enabled/
sudo rm -f /etc/nginx/sites-enabled/default
# Test nginx configuration
sudo nginx -t
# Start services
sudo systemctl start puma
sudo systemctl restart nginxThe deployment pipeline consists of two main workflows:
The deploy.yml workflow builds/pushes a Docker image and restarts the container on the host using Docker Compose.
The scripts in deploy/ are for the legacy host-based flow.
If the repository is private, make sure the droplet is logged in to ghcr.io with a PAT that has package read access.
Triggers on push to main branch:
Test Stage:
- β Ruby 4.0.1 + Node.js 24 (asset build/test only)
- β PostgreSQL 18 + Redis 8 services
- β Dependency installation (Bundle + Yarn for asset build)
- β Database setup and migrations
- β Full RSpec test suite
- β Security checks (Brakeman + Bundler Audit)
Deploy Stage:
- β Build & push Docker image to GHCR
- β SSH into droplet and restart via Docker Compose
- β Zero-downtime deployment with health checks
- β Automatic rollback on failure
- β Slack notifications
Automated cost savings:
- β Power ON: 9 AM EST (Monday-Friday)
- β Power OFF: 6 PM EST (Monday-Friday)
- β Manual trigger via GitHub Actions UI
- β Graceful service shutdown before power off
For manual deployments, pull/build the image and restart the container:
# Pull from your registry (or build locally)
docker compose -f docker-compose.yml -f docker-compose.prod.yml pull web
# Recreate the app container
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d webFor manual deployments or troubleshooting:
# SSH into droplet
ssh deploy@your_droplet_ip
# Navigate to application directory
cd /var/www/streamsource
# Run deployment script
./deploy/deploy.sh
# Or use GitHub Actions deployment script
GITHUB_REPOSITORY=YOUR_USERNAME/streamsource ./deploy/github-deploy.shIf a deployment fails:
# SSH into droplet
ssh deploy@your_droplet_ip
# Run rollback script
cd /var/www/streamsource
./deploy/rollback.sh
# Or manually rollback to previous release
ln -nfs /var/www/streamsource/releases/PREVIOUS_TIMESTAMP /var/www/streamsource/current
sudo systemctl restart pumaThe application provides several health check endpoints:
- Basic Health:
GET /health - Database Health:
GET /health/db - Redis Health:
GET /health/redis - Detailed Health:
GET /health/detailed
Monitor application logs:
# Docker logs (recommended for container deployments)
docker logs -f streamsource-web
# Application logs
tail -f /var/www/streamsource/shared/log/production.log
# Nginx logs
tail -f /var/log/nginx/access.log
tail -f /var/log/nginx/error.log
# Puma logs
journalctl -u puma -f
# System logs
journalctl -fThe application includes Prometheus metrics at /metrics endpoint.
Regular database maintenance:
# Database backup
./deploy/backup.sh
# Database vacuum (run weekly)
sudo -u postgres psql streamsource_production -c "VACUUM ANALYZE;"
# Check database size
sudo -u postgres psql streamsource_production -c "SELECT pg_size_pretty(pg_database_size('streamsource_production'));"Keep the system updated:
# Update system packages
sudo apt update && sudo apt upgrade -y
# Update application dependencies (Docker-first)
# - Update Gemfile/package.json in git, rebuild the image, and redeploy.
# Restart services after updates
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d web
# Legacy host-based:
sudo systemctl restart puma nginx# Check service status
sudo systemctl status puma
# Check logs
journalctl -u puma -n 50
# Common fixes
sudo systemctl restart puma
sudo systemctl restart nginx# Test database connection
psql -U streamsource -h localhost -d streamsource_production
# Check PostgreSQL status
sudo systemctl status postgresql
# Restart PostgreSQL
sudo systemctl restart postgresql# Test Redis connection
redis-cli ping
# Check Redis status
sudo systemctl status redis-server
# Restart Redis
sudo systemctl restart redis-server# Check certificate status
sudo certbot certificates
# Renew certificates
sudo certbot renew
# Test nginx configuration
sudo nginx -t# Check disk usage
df -h
# Clean old releases
cd /var/www/streamsource/releases
ls -la | head -20
# Remove old releases manually if needed
# Clean logs
sudo journalctl --vacuum-time=30dFor critical issues, use the emergency access guide:
# Emergency access script
./deploy/emergency-access.sh
# Quick service restart
sudo systemctl restart puma nginx postgresql redis-server
# Emergency maintenance mode
# Edit nginx config to show maintenance page
sudo nano /etc/nginx/sites-available/streamsource- β UFW firewall configured (ports 22, 80, 443 only)
- β fail2ban protection against brute force attacks
- β SSH key-based authentication (disable password auth)
- β Regular security updates
- β Non-root application user
- β Rate limiting via Rack::Attack
- β SSL/TLS enforcement
- β Security headers (HSTS, CSP, etc.)
- β Secret key rotation
- β Database connection encryption
- β Session security with Redis
- β Log monitoring for suspicious activity
- β Health check monitoring
- β SSL certificate expiration monitoring
- β Dependency vulnerability scanning
- β Redis caching for sessions and application cache
- β Gzip compression in Nginx
- β Static asset caching
- β Database query optimization
- β Connection pooling
- β Puma worker and thread tuning
- β PostgreSQL performance tuning
- β Redis memory optimization
- β Nginx performance optimization
Before going live:
- SSL certificate installed and configured
- Domain DNS pointed to droplet IP
- All environment variables configured
- Database backups automated
- Monitoring and alerting set up
- Security hardening completed
- Performance testing completed
- Rollback procedure tested
- Documentation updated
- Team access configured
Daily:
- Monitor application logs
- Check health endpoints
- Verify backup completion
Weekly:
- Review security logs
- Update system packages
- Database maintenance (VACUUM)
- Review performance metrics
Monthly:
- Security audit
- Dependency updates
- SSL certificate check
- Disaster recovery test
For deployment issues:
- Check the troubleshooting section above
- Review application logs and GitHub Actions logs
- Verify all environment variables are set correctly
- Ensure all services are running properly
Note: This deployment setup is production-ready and follows Rails and security best practices. The infrastructure automatically handles scaling, security, and monitoring for a robust production environment.