Tips for deploying Ruby on Rails with Dokku on Hetzner

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.

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:

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

Or set buildpacks via command line:

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

Then enable the environment variable:

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:

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

Paste the following into the file:

[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:

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

Optionally, start it now to test:

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:

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:

# 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:

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

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

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

Rollback (just in case)

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