Tips for deploying Rails with Dokku on Hetzner

June 19, 2025

This post is a collection of tips and tricks I’ve gathered over the years, while running my own Rails apps on a VPS with Dokku.

I will be avoiding things you can already find in Dokku’s documentation, which I highly recommend reading through if you haven’t already.

This post is a work-in-progress, I will be updating it as I go. It should only serve as a quick reference.

Performance

Enable YJIT

YJIT is a just-in-time compiler by Shopify that can significantly boost your app’s performance.

Terminal window
dokku config:set rails-app RUBYOPT="--enable-yjit"

Use jemalloc

jemalloc is an efficient memory allocator that can greatly reduce your app’s memory usage.

Add the jemalloc buildpack to your .buildpack file:

.buildpack
https://github.com/gaffneyc/heroku-buildpack-jemalloc.git
https://github.com/heroku/heroku-buildpack-ruby.git

Or set buildpacks via command line:

Terminal window
dokku buildpacks:add rails-app https://github.com/gaffneyc/heroku-buildpack-jemalloc.git

Then enable the environment variable:

Terminal window
dokku config:set app JEMALLOC_ENABLED=true

Reliability

Auto-restore

Automatically start/restore your Dokku containers on reboot to reduce downtime. Using systemd, we can create a simple service that runs dokku ps:restore on startup.

Create the systemd service file:

Terminal window
sudo nano /etc/systemd/system/dokku-restore.service

Paste the following into the file:

/etc/systemd/system/dokku-restore.service
[Unit]
Description=Restore Dokku Containers at Boot
After=docker.service
Requires=docker.service
[Service]
Type=oneshot
ExecStart=/usr/bin/dokku ps:restore
RemainAfterExit=true
[Install]
WantedBy=multi-user.target

Reload systemd and enable the service:

Terminal window
sudo systemctl daemon-reexec
sudo systemctl daemon-reload
sudo systemctl enable dokku-restore.service

Optionally, start it now to test:

Terminal window
sudo systemctl start dokku-restore.service

This runs dokku ps:restore once at boot, after Docker is ready.

Cache cleanup

Frequent deployments with Dokku can lead to Docker’s cache becoming full, potentially exhausting system disk space. Schedule a daily Docker build-cache cleanup with cron.

Open crontab:

Terminal window
sudo crontab -e

The first time you run this you may be asked which editor to use. Pick your favorite (nano is fine).

Insert this single line, then save and exit:

Terminal window
# Purge Docker build cache every day at 04:05 server time
5 4 * * * /usr/bin/docker builder prune -f 2>&1 | /usr/bin/logger -t docker-prune

5 4 * * * schedules the task to run at minute 5 of hour 4 every day. The output is piped to logger which writes to journald, and you can view the logs later with journalctl -t docker-prune.

Verify it’s registered:

Terminal window
sudo crontab -l

You should see the line you just added.

Enable Swap

Deployments can consume significant system resources, particularly RAM, during resource-intensive builds. If the system exhausts its memory, it may hang or crash, potentially bringing down your application.

Swap space is a designated portion of your system’s disk that acts as an extension of RAM. It is particularly beneficial for handling long-running or resource-demanding builds.

Enabling Swap

Terminal window
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

Verify

Terminal window
free -h # swap row ≈ 2.0G
swapon --show # lists /swapfile size 2G

Rollback (just in case)

Terminal window
sudo swapoff /swapfile
sudo rm /swapfile
sudo sed -i '/\/swapfile none swap/d' /etc/fstab

If you’re still timing out, you’re probably CPU-bound. Build in CI and dokku tags:deploy or upgrade your VPS.

Security

Configure Firewall

Configure your firewall to allow only HTTPS traffic from Cloudflare. See the guide from Hetzner for more details:

https://community.hetzner.com/tutorials/cloudflare-website-protect

app.json
{
"formation": {
"web": {
"quantity": 1
}
},
"cron": [
{
"command": "bundle exec rake foo:bar",
"schedule": "0 2 * * *"
}
],
"healthchecks": {
"web": [
{
"type": "startup",
"description": "Checking if the app responds to the /up endpoint",
"path": "/up",
"attempts": 3
}
]
}
}

GitHub Auto-Deploy

.github/workflows/deploy.yml
name: Deploy to Dokku
on:
push:
branches:
- main
workflow_dispatch:
jobs:
deploy:
runs-on: ubuntu-22.04
if: ${{ !contains(toJSON(github.event.head_commit.message), '[skip deploy]') }}
steps:
- name: Cloning repo
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Push to dokku
uses: dokku/github-action@master
with:
branch: main
git_remote_url: ${{ vars.GIT_REMOTE_URL }}
ssh_private_key: ${{ secrets.SSH_PRIVATE_KEY }}