Kamal Deployment
This guide covers deploying Lean CMS to a production server using Kamal 2.
Prerequisites
- Ruby 3.4+
- Docker (local, for building images)
- A Linux server (Ubuntu 24.04 LTS recommended)
- A domain name pointed to your server
- A container registry (GitHub Container Registry recommended)
Initial setup
1. Server requirements
Minimum specs:
- 1GB RAM (512MB works with swap, but not recommended)
- 25GB disk
- Ubuntu 24.04 LTS
If using a small droplet (512MB), add swap:
ssh root@your-server-ip "fallocate -l 2G /swapfile && chmod 600 /swapfile && mkswap /swapfile && swapon /swapfile && echo '/swapfile none swap sw 0 0' >> /etc/fstab"
2. SSH configuration with 1Password
Lean CMS recommends using 1Password for SSH key management:
- Enable 1Password SSH Agent in 1Password settings (Developer → SSH Agent)
- Store your SSH key in 1Password
- Configure
~/.ssh/config:
Host *
IdentityAgent "~/Library/Group Containers/2BUA8C4S2C.com.1password/t/agent.sock"
Host your-server
HostName your-server-ip
User root
3. Container registry setup
Create a GitHub Personal Access Token with write:packages and read:packages scopes.
Create .env file (gitignored):
KAMAL_REGISTRY_PASSWORD=ghp_your_token_here
4. Configuration
Edit config/deploy.yml:
service: your-app-name
image: your-username/your-app-name
servers:
web:
- your-server-ip
proxy:
ssl: true
host: your-domain.com
healthcheck:
path: /up
interval: 3
timeout: 10
registry:
server: ghcr.io
username: your-username
password:
- KAMAL_REGISTRY_PASSWORD
env:
secret:
- RAILS_MASTER_KEY
volumes:
- "your-app_storage:/rails/storage"
5. Secrets configuration
Edit .kamal/secrets:
KAMAL_REGISTRY_PASSWORD=$(grep KAMAL_REGISTRY_PASSWORD .env | cut -d '=' -f2)
RAILS_MASTER_KEY=$(cat config/master.key)
Deployment
First deploy
kamal setup
This will:
- Install Docker on the server
- Set up the kamal network
- Start kamal-proxy
- Build and push your image
- Deploy your application
Subsequent deploys
kamal deploy
Useful commands
# View logs
kamal logs
# Rails console
kamal console
# Shell access
kamal shell
# App status
kamal app details
SQLite database management
Lean CMS uses SQLite for simplicity and file-based backups.
Manual database sync
Copy local DB to production:
# Stop the container
ssh root@your-server "docker stop $(docker ps -q --filter name=your-app-web)"
# Copy database
scp storage/development.sqlite3 root@your-server:/var/lib/docker/volumes/your-app_storage/_data/production.sqlite3
# Clean WAL files and restart
ssh root@your-server "rm -f /var/lib/docker/volumes/your-app_storage/_data/production.sqlite3-{shm,wal} && docker start $(docker ps -aq --filter name=your-app-web)"
Pull production DB locally:
scp root@your-server:/var/lib/docker/volumes/your-app_storage/_data/production.sqlite3 storage/production_local.sqlite3
Automated content sync
Lean CMS includes Rake tasks for safe content synchronization. See Content Sync for the full workflow.
New project / first deploy
# 1. Develop normally in development DB
bin/rails server
# 2. Stage your development DB as production_local
bin/rails lean_cms:sync:stage
# 3. Boot Rails in production mode against the staged DB
bin/stage
# 4. When happy, push the staged DB to the real server
bin/rails lean_cms:sync:push
Ongoing sync workflow
# Lock production so editors can't change anything while you work
bin/kamal cms-lock
# Pull production DB to local
bin/rails lean_cms:sync:pull
# Make local changes, then push and unlock in one step
bin/rails lean_cms:sync:finish
Troubleshooting
Health check failures
If deploys fail with “target failed to become healthy”:
- Check container logs:
kamal app logs - Verify
/upendpoint works locally - Check for OOM issues (add swap if needed)
- Increase timeouts in
deploy.yml
Database issues
SQLite uses WAL mode in production. If you see lock errors:
- Stop the container before any manual DB operations
- Always remove
.sqlite3-shmand.sqlite3-walfiles when replacing the DB - Use the content sync Rake tasks for safe operations
SSH connection issues
- Verify 1Password SSH agent is running:
ssh-add -l - Check
~/.ssh/confighas the IdentityAgent configured - Ensure your SSH key is stored in 1Password
Production checklist
- Server has adequate RAM (1GB+ recommended)
- Swap configured if using small instance
- SSL certificate provisioning works (DNS pointing to server)
- Health check endpoint (
/up) accessible - Database volume is persistent
- Secrets configured in
.kamal/secrets - 1Password SSH integration set up