This guide covers deploying SplitMySub using Kamal, a modern deployment tool for containerized applications.
- Linux server (Ubuntu 20.04+ recommended)
- Docker installed on the server
- SSH access with sudo privileges
- Ruby 3.4.4
- Node.js 20+
- Docker (for local testing)
- Git
- Domain name pointing to your server
- DNS A record configured
Update config/deploy.yml with your actual server details:
servers:
web:
- your-server-ip-address # Replace with actual IP
proxy:
ssl: true
host: your-domain.com # Replace with actual domainGitHub Container Registry (Default - FREE for public repositories)
The application is pre-configured to use GitHub Container Registry:
registry:
server: ghcr.io
username: ashwin47
password:
- GITHUB_TOKENAlternative Options:
registry:
username: your-dockerhub-username
password:
- DOCKER_HUB_TOKENregistry:
server: registry.digitalocean.com
username: your-do-username
password:
- DOCKER_REGISTRY_TOKENSet up your environment variables in .kamal/secrets. All secret variables must be configured for production deployment:
# GitHub token for container registry (automatic in GitHub Actions)
export GITHUB_TOKEN="your-github-personal-access-token"
# Rails master key (from config/master.key)
export RAILS_MASTER_KEY="$(cat config/master.key)"
# REQUIRED: Admin access password
export ADMIN_PASSWORD="your-secure-admin-password"
# REQUIRED: Email configuration (choose one)
# Option 1: Resend API (recommended)
export RESEND_API_KEY="your-resend-api-key"
# Option 2: SMTP Configuration
export SMTP_USERNAME="your-smtp-username"
export SMTP_PASSWORD="your-smtp-password"
# OPTIONAL: Cloudflare Turnstile (for CAPTCHA protection)
export CLOUDFLARE_TURNSTILE_SITE_KEY="your-turnstile-site-key"
export CLOUDFLARE_TURNSTILE_SECRET_KEY="your-turnstile-secret-key"Note: When deploying via GitHub Actions, the GITHUB_TOKEN is automatically provided - no manual configuration needed!
The application is pre-configured to use GitHub Container Registry:
image: ghcr.io/ashwin47/splitmysubFor alternative registries:
# Docker Hub
image: your-dockerhub-username/splitmysub
# DigitalOcean
image: registry.digitalocean.com/your-registry/splitmysub| Variable | Description | Example |
|---|---|---|
RAILS_MASTER_KEY |
Rails encryption key | your-32-char-key |
ADMIN_PASSWORD |
Admin interface password | secure-admin-password |
RESEND_API_KEY |
Resend email service API key | re_xxxxx |
SMTP_USERNAME |
SMTP email username | your-email@gmail.com |
SMTP_PASSWORD |
SMTP email password | your-app-password |
| Variable | Description | Default |
|---|---|---|
CLOUDFLARE_TURNSTILE_SITE_KEY |
Cloudflare Turnstile site key | Test key |
CLOUDFLARE_TURNSTILE_SECRET_KEY |
Cloudflare Turnstile secret key | Test secret |
The following environment variables are configured in the env.clear section:
| Variable | Description | Default | Required |
|---|---|---|---|
RAILS_ENV |
Rails environment | production |
Yes |
APP_HOST |
Your application domain | localhost |
Yes |
APP_PROTOCOL |
Protocol (http/https) | https |
Yes |
SOLID_QUEUE_IN_PUMA |
Run background jobs in Puma | true |
Yes |
VITE_RUBY_HOST |
Vite server host | 0.0.0.0 |
Yes |
VITE_RUBY_PORT |
Vite server port | 3036 |
Yes |
| Variable | Description | Default | Notes |
|---|---|---|---|
WEB_CONCURRENCY |
Number of Puma workers | 1 |
Increase for multi-core servers |
JOB_CONCURRENCY |
Background job processes | 1 |
Increase for heavy job processing |
RAILS_MAX_THREADS |
Max threads per worker | 3 |
Adjust based on server resources |
RAILS_LOG_LEVEL |
Logging level | info |
Use debug for troubleshooting |
| Variable | Description | Default |
|---|---|---|
SMTP_ADDRESS |
SMTP server address | smtp.gmail.com |
SMTP_PORT |
SMTP server port | 587 |
SMTP_DOMAIN |
SMTP domain | Same as APP_HOST |
SMTP_AUTHENTICATION |
SMTP authentication method | plain |
SMTP_ENABLE_STARTTLS_AUTO |
Enable STARTTLS | true |
SMTP_OPENSSL_VERIFY_MODE |
SSL verification mode | peer |
The repository is configured with GitHub Actions for automatic deployment:
- Push to main branch triggers automatic deployment
- GitHub Container Registry authentication is handled automatically
- No manual deployment commands needed
For manual deployment from your local machine:
-
Setup the server:
bin/kamal setup
-
Deploy the application:
bin/kamal deploy
bin/kamal deploy# Check application status
bin/kamal app details
# View logs
bin/kamal logs
# Access Rails console
bin/kamal console
# Run database migrations
bin/kamal migrate
# Seed the database
bin/kamal seed
# Access shell
bin/kamal shell
# Rollback to previous version
bin/kamal rollback
# Remove application (careful!)
bin/kamal remove- Sign up for Resend at resend.com
- Get your API key from the dashboard
- Add to secrets:
export RESEND_API_KEY="re_your_api_key"
Popular SMTP providers:
export SMTP_USERNAME="your-email@gmail.com"
export SMTP_PASSWORD="your-app-password" # Use App Password, not regular passwordexport SMTP_USERNAME="your-postmark-token"
export SMTP_PASSWORD="your-postmark-token"Add to config/deploy.yml:
env:
clear:
SMTP_ADDRESS: smtp.postmarkapp.com
SMTP_PORT: 587export SMTP_USERNAME="apikey"
export SMTP_PASSWORD="your-sendgrid-api-key"Add to config/deploy.yml:
env:
clear:
SMTP_ADDRESS: smtp.sendgrid.net
SMTP_PORT: 587The admin interface is protected by HTTP Basic Authentication:
- Username:
superadmin - Password: Set via
ADMIN_PASSWORDenvironment variable
For CAPTCHA protection on authentication forms:
- Get Cloudflare Turnstile keys from Cloudflare Dashboard
- Add to secrets:
export CLOUDFLARE_TURNSTILE_SITE_KEY="your-site-key" export CLOUDFLARE_TURNSTILE_SECRET_KEY="your-secret-key"
Note: The application works without Turnstile (uses test keys by default).
Kamal automatically handles SSL certificates via Let's Encrypt when you configure:
proxy:
ssl: true
host: your-domain.comIf using Cloudflare:
- Set encryption mode to "Full" in SSL/TLS settings
- Configure proxy settings in
config/deploy.yml
SplitMySub uses SQLite with multiple databases for different purposes:
volumes:
- "splitmysub_storage:/rails/storage" # File uploads
- "splitmysub_db:/rails/db" # All SQLite databasesDatabase Files:
production.sqlite3- Main application dataproduction_cache.sqlite3- Cache dataproduction_queue.sqlite3- Background jobsproduction_cable.sqlite3- WebSocket connectionsproduction_errors.sqlite3- Error tracking
# Access database console
bin/kamal app exec --interactive --reuse "bin/rails dbconsole"
# Run migrations
bin/kamal app exec --reuse "bin/rails db:migrate"
# Create database backup
bin/kamal app exec --reuse "bin/rails db:backup"Kamal monitors the application via /up endpoint:
proxy:
healthcheck:
path: /up
port: 3000
max_attempts: 10
interval: 3Logs are configured with rotation to prevent disk space issues:
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"SQLite Database Backup:
# Create backup
docker run --rm -v splitmysub_db:/source -v $(pwd):/backup alpine tar czf /backup/db-backup-$(date +%Y%m%d).tar.gz -C /source .
# Restore backup
docker run --rm -v splitmysub_db:/target -v $(pwd):/backup alpine tar xzf /backup/db-backup-YYYYMMDD.tar.gz -C /targetStorage Backup:
# Backup uploaded files
docker run --rm -v splitmysub_storage:/source -v $(pwd):/backup alpine tar czf /backup/storage-backup-$(date +%Y%m%d).tar.gz -C /source .Built-in Error Tracking:
- Errors are tracked in
production_errors.sqlite3 - Access via
/admin/solid_errors(requires admin authentication)
Log Monitoring:
# Real-time logs
bin/kamal logs -f
# Application-specific logs
bin/kamal logs --since 1h-
Build failures:
# Check build logs bin/kamal build logs # Rebuild from scratch bin/kamal build --no-cache
-
Deployment failures:
# Check application logs bin/kamal logs # Check container status bin/kamal app details
-
Email delivery issues:
# Test email configuration bin/kamal console # In Rails console: ActionMailer::Base.mail(to: "test@example.com", subject: "Test", body: "Test").deliver_now
-
SSL issues:
# Check proxy status bin/kamal proxy logs # Restart proxy bin/kamal proxy restart
-
Database issues:
# Check database connectivity bin/kamal app exec --interactive --reuse "bin/rails dbconsole" # Run migrations manually bin/kamal migrate
-
Admin access issues:
# Verify admin password is set bin/kamal app exec --reuse "printenv ADMIN_PASSWORD"
Enable debug logging by adding to config/deploy.yml:
env:
clear:
RAILS_LOG_LEVEL: debugApplication Not Responding:
# Check container status
bin/kamal app details
# Restart application
bin/kamal app restart
# Check recent logs
bin/kamal logs --since 30mDatabase Corruption:
# Check database integrity
bin/kamal app exec --reuse "bin/rails runner 'puts ActiveRecord::Base.connection.execute(\"PRAGMA integrity_check\").first'"
# Restore from backup if needed
# (See backup section above)- Keep secrets secure: Never commit
.kamal/secretsorconfig/master.key - Use strong passwords: For admin access and email accounts
- Regular updates: Keep Docker images and dependencies updated
- Firewall configuration: Only expose necessary ports (80, 443, 22)
- SSH key authentication: Disable password authentication
- Rate limiting: Built-in via Rails 8 native rate limiting (configured automatically)
- Security headers: Automatically configured (HSTS, CSP, etc.)
- Multi-stage builds: Already configured in Dockerfile
- Asset precompilation: Handled during build process
- Container limits: Configure in
config/deploy.yml - Database optimization: Use connection pooling and proper indexing
Add more servers to config/deploy.yml:
servers:
web:
- server1.your-domain.com
- server2.your-domain.comUse a load balancer in front of multiple servers or configure Kamal proxy for multiple hosts.
- Kamal Documentation
- Kamal GitHub Repository
- Rails Deployment Guide
- Docker Best Practices
- SSL/TLS Configuration
- Production Rails Security
- Application Logs:
bin/kamal logs - System Logs: SSH to server and check
/var/log/ - Health Status: Monitor
/upendpoint - Admin Interface:
/admin(requires authentication) - Error Tracking:
/admin/solid_errors(requires authentication) - Community: Rails and Kamal community forums
- Professional Support: Consider professional Rails hosting services
Quick Deployment Checklist:
- Server with Docker installed
- Domain pointing to server
- Container registry configured
- All required environment variables set in
.kamal/secrets -
config/deploy.ymlupdated with your details - Email delivery configured (Resend or SMTP)
- Admin password set
- Run
bin/kamal setupandbin/kamal deploy