When Heroku first announced the end of free plans I knew I’d need to figure out where to move any projects soon, since spending $16-31 per side-project would be rough. They have since announced low-cost plans, but that would still run $13-19 per project. Comparing the prices to DigitalOcean, you can get two to three Droplets with 1GB of memory each for the same price. Moving on from Heroku was an easy decision based on pricing alone, even if I have been using it nearly as long as I’ve been a Rails developer.
The next step was deciding how to manage the infrastructure and deploy applications. And while solutions like Kubernetes, Terraform, and the like are the go-to for a lot of people, it seemed like a lot to learn and manage for personal projects.
Instead I explored Platform-as-a-Service, or PaaS, options to keep a similar feel to Heroku. I chose Dokku in the end because it’s been around a while, it’s marketed as a mini-Heroku, and it satisfies all the requirements for applications I run. The one pitfall is it’s not built for running separate servers in a cluster, but I’ll cross that path if I ever have the need. Although, you can run the database services on separate services if needed, which should provide plenty of scaling capabilities.
First we need to create a Droplet on DigitalOcean. I’m using Ubuntu 22.04 and Dokku recommends at least 1GB of memory. Once the Droplet is live you should be able to SSH in to get started installing Dokku.
Note: Even with 1GB of memory, I’d still recommend creating a swap file since I ran into build issues with Bundler due to limited free memory with the services running.
Install Dokku on the Droplet by following the official installation instructions, which will take around 5-10 minutes to complete.
After the installation completes you can copy your local SSH key to add it as an administrator in Dokku.
Add your SSH key as an administrator to Dokku on the server.
Creating an Application
For the rest of this article I’m going to be setting up an instance of Miroha, a personal project of mine and the first project I migrated to use Dokku. If you’re setting up a project of your own, be sure to update the naming for each command.
First on the server we’ll create the application.
And while we’re still on the server via SSH, we can install the plug-ins we’re going to use since they have to run with
We shouldn’t need to SSH into the server anymore, so to continue the setup we’re going to install the official client. It’s a convenience wrapper around running remote commands with SSH, which may be your solution if you’re not on macOS.
By default the client looks for a
dokku remote in the Git repository, so we can add that next. You can customize the name via
DOKKU_GIT_REMOTE which can be helpful if you are running more than one instance, such as staging and production. See
brew info dokku for all the options.
We can verify our local configuration and the application remotely by retrieving a report for the application. The information may look different compared to the example below, but you are good to go as long as it succeeds.
Creating the Databases
We already added the PostgreSQL and Redis plug-ins on the server, so it’s one command to create the database and one to link it to the application. To see all of the commands for each plug-in you can run
dokku postgres or
Since you are running all the services on a single instance you may want to adjust the Redis memory settings. You can change the memory limit to 32 megabytes and the policy to evict the least frequently used key. To do so run the
redis:connect command with the database name and run the appropriate Redis commands. See the default redis.conf for more details on each setting.
For other Redis commands and settings see the official Redis documentation.
Preparing for Deployment
We’re almost ready for our first deployment, but there’s a bit of configuration we need to get through first.
Setting the Default Branch
If you’re using
main or another branch name, you’ll want to change the deploy branch in Dokku. For a staging instance, I change it to be a
staging branch to be more explicit when pushing.
If you are sticking with the default builder, Herokuish, then we need to add a couple of buildpacks to build the application.
Note that while the Ruby buildpack does have Node.js available, it doesn’t appear to cache the Yarn dependencies at the time of writing and results in a much slower deployment. And if you add the Node.js buildpack the Ruby buildpack will still install the dependencies a second time unless you disable it. Miroha uses an environment variable to alter the task.
Setting the Master Key
Next we need to set the
RAILS_MASTER_KEY environment variable to ensure the application can boot. The
--no-restart option argument is helpful to avoid restarting the application when changing configuration.
Adding a Domain
Adding a domain is as simple as Heroku, but we need to remove the default local domain. I remove the local domain due to running into issues with adding SSL, which we’ll get to next.
You’ll want to update the DNS for the domain to point to
DROPLET_IP_ADDRESS, if you don’t the SSL certificate will fail.
Enabling Automatic SSL
To use the Let’s Encrypt plug-in, you first need to set an e-mail address for requested certificates with.
With the e-mail set, we can enable the plug-in and add the automatic renewal job. See dokku-letsencrypt for more details.
Tip: Don’t forget to enforce SSL in production. See the ActionDispatch::SSL documentation.
The last step to deploy is to scale up our clock and web processes so they will start after deployment.
To deploy the application it’s as simple as pushing to Heroku. Be sure to use the correct remote and branch names, if you picked different versions.
If you don’t have a
release process set up you’ll need to migrate and seed your database as needed. Miroha uses a release script to automatically run migrations and allow it to be reset via an environment variable after each deployment.
If you don’t see any error during the push or database migration, you should see the application live at the domain you added. Congratulations!
Automatic Deployment with GitHub Actions
If you’re looking to automatically deploy when you merge on GitHub, check out the official GitHub Action with the simple example being the easiest place to get started. If you have a staging branch, you may want to consider enabling force push and adding a post-deploy script to reset the database.