Deployment
Roe CMS supports Kamal deployments. This means that you can use Roe with many popular Ruby on Rails hosts. This document contains basic instructions for general deployment and step-by-step instructions for DigitalOcean.com and Fly.io.
DigitalOcean
DigitalOcean has an excellent step-by-step article that explains How To Set Up an Ubuntu Server on a DigitalOcean Droplet. I would go through this article and below, I will include any Roe specific recommendations and instructions.
Create an Account
- Go to DigitalOcean.com and click Menu → Products → Computer → Droplets
- Add your name, email and password.
- Signup
Create a Droplet
- Choose a datacenter region near you or your primary audience.
- Choose an image (Ubuntu recommended)
- Choose a Droplet Plan
- Basic
- Regular (if available in region. try Permium AMD or Premium Intel if Regular is not available.)
- Lowest price possible for the size of your site (most sites should work with the 35GB plan.)
- You can skip “Add additional storage” unless you have a very large site with tons of content.
- Skip “Enable backups” unless you really need them. Roe takes care of backups for you.
Authentication
Add an SSH Key
If you’re unfamiliar with how to create an SSH key, this article with DigitalOcean goes through it step-by-step for all platforms: Create SSH Keys with OpenSSH on macOS, Linux, or Windows
When you create the SSH, paste this command into your terminal:
ssh-keygen -t ed25519 -C "roe-do-deploy" -f ~/.ssh/roe_do
You can skip adding a PASSPHRASE or add one if you like.
This will create 2 SSH files on your system:
/Users/<username>/.ssh/roe_do/Users/<username>/.ssh/roe_do.pub
Never share roe_do, this is your private key. roe_do.pub is the part you need to add to DigitalOcean (or any platform).
You can add this SSH key to your ssh/config so Kamal can automatically pick the right key without you having to remember every time. Copy and paste the command below into your terminal:
Mac/Linux
Make sure to replace the DROPLET_IP with your Public IP from DigitalOcean for this droplet.
mkdir -p ~/.ssh && chmod 700 ~/.ssh
DROPLET_IP="100.200.100.200"
cat >> ~/.ssh/config << EOF
Host $DROPLET_IP
IdentityFile ~/.ssh/roe_do
User root
EOF
chmod 600 ~/.ssh/config
Windows/PowerShell:
$DropletIP = "100.200.100.200"
New-Item -ItemType Directory -Force -Path "$HOME\.ssh" | Out-Null
@"
Host $DropletIP
IdentityFile ~/.ssh/roe_do
User root
"@ | Add-Content -Path "$HOME\.ssh\config"
Copy the public key to your clipboard so you can paste it into DigitalOcean:
Mac
pbcopy < ~/.ssh/roe_do.pub
Linux
xclip -selection clipboard < ~/.ssh/roe_do.pub
Windows
cat ~/.ssh/roe_do.pub | clip
Paste it into DigitalOcean’s SSH key field when prompted.
Additional
Enable Improved Metrics and monitoring
Free, gives you CPU/memory/disk graphs in the DO control panel. Highly recommended on small droplets where running out of memory is a real failure mode.
Don’t enable Startup scripts
Kamal handles all the Roe-specific setup. Startup scripts add a layer that’s hard to debug if something goes wrong on first boot.
Connect to the Droplet
Once the droplet is created, you can sign in via SSH:
ssh root@100.200.100.200
Replace the numbers with your droplet’s public IP address. You should land in a shell prompt without being asked for a password — that’s because the ~/.ssh/config entry from the previous step tells SSH which key to use automatically.
Type exit to leave SSH at any time.
Prepare the Droplet
Add a Swap File (recommended for small droplets)
This is only recommended if you’re using a small droplet (1GB). Adding a 2GB swap file prevents the system from being overloaded. SSH into the droplet and paste this:
sudo swapon --show
sudo fallocate -l 2G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab
free -h
The last command should show Swap: 2.0Gi in the output. The swap file persists across reboots thanks to the /etc/fstab line.
This article has more info about this fix: Boosting Your DigitalOcean Droplet’s Memory: No Upgrade Needed When You’re Short on RAM
Set Up a Docker Registry
Kamal builds the Roe image locally and pushes it to a Docker registry. The droplet pulls from there to deploy. Docker Hub is the easiest option for getting started.
Create a Docker Hub Account
- Go to hub.docker.com and sign up.
- Verify your email.
Generate a Personal Access Token
Don’t use your account password — generate a dedicated token instead:
- Docker Hub → Account Settings → Personal Access Tokens → Generate New Token
- Description:
roe-kamal-deploy - Expiration:
None(or pick the longest option offered) - Permissions: Read, Write, Delete (Kamal needs all three)
- Copy the token immediately — Docker Hub only shows it once
You’ll paste this into .kamal/secrets shortly.
Configure Roe for Kamal
Roe needs three values to deploy. You set them once in the Roe admin UI; Roe writes the necessary config and secrets files for you.
Open the Deployment Settings
In your local Roe admin: Settings → Deployment.
Fill in the three required fields:
- Droplet IP address — the public IP from your DigitalOcean droplet.
- Docker Hub username — the username from your Docker Hub account.
- Docker Hub access token — the personal access token you generated earlier. (This one is treated as a secret — it’s stored encrypted and never displayed after save.)
Click Save. Roe generates two files behind the scenes:
-
config/deploy.yml— Kamal’s main configuration, populated with your droplet IP, Docker Hub username, volume paths, and the Roe-specific defaults (SQLite single-container deploys, asset paths, aliases). -
.kamal/secrets— holds your Docker Hub access token and reads the Rails master key fromconfig/master.key(which Rails created automatically when Roe was installed).
Both files are written to the right place and given the right permissions. You don’t need to open or edit either one directly.
Verify the Rails Master Key
Roe uses config/master.key to decrypt encrypted credentials at runtime. This file was created when Rails was installed and lives at the standard location. Confirm it’s there:
cat config/master.key
You should see a single 32-character hexadecimal string. Add the master.key value to your password manager — losing it means losing access to any encrypted credentials.
Optional: Build on the Droplet Instead of Your Laptop
By default, Roe builds your Docker image on the machine running Kamal (your laptop). If you’re on an Apple Silicon Mac (M1/M2/M3/M4) and your droplet is amd64, the build uses Docker’s emulation layer. This works, but can be slow and occasionally produces inconsistent output.
Under Settings → Deployment → Advanced, you can toggle “Build on the droplet”. This SSHes into your droplet to build the image there directly, eliminating any cross-architecture issues. Tradeoff: builds use the droplet’s RAM (which is why we set up the swap file earlier). On a 1GB droplet, expect builds to take 5–10 minutes.
If you hit build issues, try this option before debugging anything else.
Deploy
First Deploy
The first time you deploy, use kamal setup. This:
- Installs Docker on the droplet (if not present)
- Logs into Docker Hub from the droplet
- Builds the Roe image
- Pushes it to Docker Hub
- Pulls it on the droplet and starts the container
cd /path/to/your/roe
kamal setup
Expect 5–10 minutes on first run. Subsequent deploys (kamal deploy) are faster because Docker caches layers.
Verify the Deploy
Once kamal setup finishes, hit your droplet’s IP in a browser:
http://<your-droplet-ip>
You’ll get a 500 error initially — that’s expected, because the /site directory on the droplet is empty (no pages/home.md yet). The error confirms Roe is running; it just doesn’t have any content yet.
Verify the container is healthy:
kamal app details
kamal app logs --lines 30
Should show one running container and Rails boot messages.
Create an Admin User on the Droplet
Roe’s database is per-environment (it lives in /site/db/production/ on the droplet, separate from your local one). So you need to create your first admin user there:
kamal console
Then in the Rails console:
User.create!(
email_address: "you@example.com",
password: "your-strong-password",
password_confirmation: "your-strong-password"
)
exit
Use a real strong password and save it to your password manager. This is the only admin user on production until you add more.
Sync Your Site to Live
Now that the droplet is running Roe, you need to push your local content (/site folder) to it. Roe has a Site Sync feature for this.
Configure the Sync Token
Both your local Roe and the live Roe need to share a token to authenticate API calls between them.
- On your local admin: visit
/admin/site_syncand copy the token from the settings panel. - On the droplet: open a Rails console and set the same token:
kamal consoleSyncConfig.current.update!(token: "<paste-token-from-local>") exit
Set the Peer URL on Local
Back on your local admin’s /admin/site_sync page, set Peer URL to your droplet’s address:
http://<your-droplet-ip>
Save. Click Refresh exchange. You should see “Last contact with Live site: less than a minute ago” — that confirms the API connection works.
Push Your Site
From the local admin’s /admin/site_sync page, click Push to live, type LIVE to confirm. This rsyncs your /site folder to the droplet. A few minutes later, your posts, pages, and theme are on production.
Verify
Reload your droplet’s IP in a browser. The home page should render now. Sign in at http://<your-droplet-ip>/session/new with the admin credentials you created earlier.
Domain and SSL (when ready)
For your first deploy you can run on the droplet’s IP over plain HTTP. When you have a domain ready:
- Point an
Arecord at the droplet’s IP via your DNS provider. -
Edit
config/deploy.ymland add aproxy:block:proxy: ssl: true host: yourdomain.com - Run
kamal proxy rebootto enable Let’s Encrypt SSL.
You’ll have HTTPS within a few minutes (Let’s Encrypt provisions automatically).
Going Forward: Routine Deploys
After the first deploy, deploys are simpler. The recommended sequence:
kamal app stop
ssh root@<your-droplet-ip> 'cd /var/lib/roe/site/db/production && rm -f *-wal *-shm'
kamal deploy
The first command stops the running container (releases SQLite locks). The second cleans up any stale SQLite write-ahead-log files. The third deploys.
Why the manual stop instead of just kamal deploy? Kamal’s default zero-downtime deploy strategy runs the new container alongside the old one for a moment. With SQLite, two processes can’t safely share the database file — the new container fails to initialize SolidQueue and the deploy aborts. Stopping first avoids this.
Troubleshooting
Tailwind CSS errors after deploy (“asset ‘tailwind.css’ was not found”)
If you’ve made local changes to assets, ensure they’re committed to git before deploying — Kamal versions images by git SHA. If the SHA hasn’t changed, Kamal may reuse a cached build that misses your new assets.
If the issue persists after committing, the Docker layer cache may have stale state. Force a fresh build:
ssh root@<your-droplet-ip> 'docker builder prune -a -f && docker buildx prune -a -f'
kamal deploy
Container fails health check / “target failed to become healthy within configured timeout (30s)”
Usually a SQLite lock issue. Stale write-ahead-log files left over from a crashed previous container can prevent the new one from booting. Recovery:
kamal app stop
ssh root@<your-droplet-ip> 'cd /var/lib/roe/site/db/production && rm -f *-wal *-shm'
kamal deploy
Droplet becomes unresponsive during deploy
The 1GB plan can run out of memory during the build + container restart cycle. If SSH stops responding:
- Power Cycle the droplet from the DigitalOcean dashboard (Power → Power Cycle).
- Wait ~30 seconds.
- SSH back in.
- Make sure the swap file from earlier is active:
free -hshould show 2GB of swap.
If this happens repeatedly, consider upgrading to the 2GB plan ($12/mo) — the extra RAM gives you headroom during deploys. The resize is reversible and doesn’t lose data.
“No such file: config/master.key” errors
The master key wasn’t created or wasn’t shipped to the container. Check that config/master.key exists locally and contains the right value, and that .kamal/secrets has the line RAILS_MASTER_KEY=$(cat config/master.key).
Need to roll back to a previous version
Kamal tags every image in your registry. To roll back:
kamal app boot --version=<previous-tag>
Find the previous tag in your Docker Hub repository’s tags list, or via kamal app version.