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.
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"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.githttps://github.com/heroku/heroku-buildpack-ruby.gitOr set buildpacks via command line:
dokku buildpacks:add rails-app https://github.com/gaffneyc/heroku-buildpack-jemalloc.gitThen enable the environment variable:
dokku config:set app JEMALLOC_ENABLED=trueAutomatically 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.servicePaste the following into the file:
[Unit]Description=Restore Dokku Containers at BootAfter=docker.serviceRequires=docker.service
[Service]Type=oneshotExecStart=/usr/bin/dokku ps:restoreRemainAfterExit=true
[Install]WantedBy=multi-user.targetReload systemd and enable the service:
sudo systemctl daemon-reexecsudo systemctl daemon-reloadsudo systemctl enable dokku-restore.serviceOptionally, start it now to test:
sudo systemctl start dokku-restore.serviceThis runs dokku ps:restore once at boot, after Docker is ready.
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 -eThe 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 time5 4 * * * /usr/bin/docker builder prune -f 2>&1 | /usr/bin/logger -t docker-prune5 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 -lYou should see the line you just added.
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.
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/fstabfree -h # swap row ≈ 2.0Gswapon --show # lists /swapfile size 2Gsudo swapoff /swapfilesudo rm /swapfilesudo sed -i '/\/swapfile none swap/d' /etc/fstabIf you’re still timing out, you’re probably CPU-bound. Build in CI and dokku tags:deploy or upgrade your VPS.
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
{ "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 } ] }}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 }}