So you want to quickly deploy a small experiment to production. How do you do that? Dokku is one way of doing that. Here are my first impressions.
Dokku lets you deploy an application with a simple
git push, requiring only one or two lines of configuration in the application’s git repository. Dokku was a lot easier to set up than I feared, and it was kind of fun!
The pattern we applied here is:
Deploy a blank page to production, so that you know all the steps necessary to deploy the next time. This way you can start iterating on features together with actual users from day one, and speed up the way you deploy with each iteration.
Deploying blank pages serves a dual purpose. It is useful to quickly share early development. It is also a good way to experiment with ways of deploying.
What is the user need here? I started developing a flash card application to help me remember programming constructs and library functions, because I wasn’t happy with the UX of the ones I had found. This worked well enough on my laptop, but I wanted to use it on my phone as well to quickly review questions on the go.
I didn’t want to lose rapid local development, and I didn’t want to write elaborate Terrorform scripts either. I also didn’t want a fully manual deployment that I would forget the working of in due time. Options discarded:
- BAAS (Back-end As A Service), like Firebase. I’ve used one extensively. They seem to come without SQL, and fast iteration after initial development is still something that requires a fair amount of work.
- Elaborate cloud deployment with e.g. Terraform, AWS CloudFormation or Azure ARM templates. More complicated than needed, as Terraform code tends to become large and detailed even for simple applications.
- Manual deployment on a VPS (virtual private server). It would work at this scale, but I would forget what I did, and is not that repeatable.
It turns out that Dokku is not only more repeatable, but also faster than manual deployment. It boils down to useful primitives, good documentation, and choices that are pre-made.
We trade off ready made deployment for more options, and bounded cost. Generic VPS’es are readily availble. For this one I chose Hetzner. They are based in Germany, which has good privacy laws, and have been running on green energy for years. This saves me time lobbying my provider to become green. Their prices are low, and their 24/7 customer support has served me well over the years.
Cloud providers innovate, but when you don’t need all the latest things, or granular deployment permissions, there are providers with a more conservative approach, that copy what works. Often with simpler, more streamlined UX and bounded, low costs. In this case I spent about £1 on a floating IP address, and 20% for automated, rotating backup of the whole VM. Up and running for a coffee or two a month. Not bad.
For development I settled on a glorious little monolith. Inspired by
Marc and Rob, I chose SQLite instead of
fopen(). As a development language I chose Haskell - because I missed it and because there are some interesting takes on back-end development that originate from it. Based on the feedback I got from deploying with Dokku, I switched frameworks. More about that later.
For a first deployment I would like to protect details with HTTPS (Let’s Encrypt) and quickly hide it behind a username/password with HTTP Basic Authentication, while I am figuring out how I want authentication and authorization to work properly.
If you need at most one (virtual) server, then Dokku appears to be a great fit. I’ve deployed a few experiments with it it now, and was very pleasantly surprised about Dokku’s lack of complexity.
Why does it matter
It is easy to get lured into complex setups recommended by cloud providers. When I’m thinking of overcomplicating things, I remind my self of the WordPress: Best practices on AWS. I find looking at the deployment view sobering:
If you need a set-up this complex for your blog (and most likely you don’t!), you are probably better off hosting it at wordpress.com or a provider that specializes in extremely high volume WordPress setups. Saves you money and time too. I even found a simple looking BAAS can have its own complexities when e.g. trying to create a test environment, or quickly iterating on my laptop. Dokku leverages well established products: git, Docker and Nginx, and does a good job of hiding the complexities of the latter two without introducing unnecessary risks.
It also comes with nice surprises like zero-downtime deployments with very little work.
How does it work
Dokku lets you deploy with a
git push to a (virtual) server
of your choosing. I chose Hetzner for this experiment, they have been using 100% green
energy long before the big cloud providers and their service has always been swift (when necessary, which isn’t often). Their VPSes are priced well below other offerings.
The basic flow, as illustrated by the animation below is, to tell Dokku to
create an app, tell Dokku what components it consists of in a
Procfile (mine is
just one line), and optionally a
.buildpacks file if the application is non-standard. This is not needed for e.g. a Rails application. Push the repository to Dokku and wait for it to build.
Once the git push has completed, you’ll have a working application, connected to a web server, and other services such as a database.
The Procfile for my Haskell web application that consumes no further services looks like:
1 web: <repositoryname>
This is my .buildpacks file:
Dokku is a clone of Heroku and the Haskell tool stack is not included out of the box, hence I needed a .buildpacks file.
I learned after the basic tutorial that providing a FQDN (Fully Qualified Domain Name) with the initial deploy step helps to add Let’s Encrypt in the next step.
Let’s encrypt can be added after that (not before) with one command, you only need to provide an e-mail address. Instructions for dokku-letsencrypt also explain the one-liner for monthly renewals. Basic Authentication is also one command, as explained in the dokku-http-auth documentation.
I recommend going through Dokku’s Deploy tutorial which walks you through setting up an example Ruby on Rails application, even if you don’t care about Rails. I was pleasantly surprised how easy it was to add PostgreSQL to an application. I chose to start with SQLite, because I thought that would make deployment easier, but Dokku makes PostgreSQL so easy that I may be wrong about that. Creating a database and adding it to an application is just two Dokku commands away. It also includes a facility to back up the database to an S3 compatible bucket regularly.
Bonus: cheap zero-downtime deployments
I was pleasantly surprised to find that with adding yet another text file, Dokku can do fast zero downtime deployments. You need to tell Dokku how long to wait before running a health check, how many retries to do and how long, what the health check should be, and what should be in the result.
The file needs to be called
CHECKS. A checks file for a Haskell application I deployed looks like:
1 2 3 4 5 WAIT=1 TIMEOUT=30 ATTEMPTS=30 /static/hello.html Hello!
While Haskell build times can be long, application start up is well under a
second, so we don’t have to wait long for the health check to succeed. The check performs a
GET request to an HTTP endpoint that should have the text Hello! in it.
Storage and Port took a bit longer to figure out
A bit would be: an hour or so, as opposed to Let’s Encrypt and HTTP Basic Authentication that took minutes. Not bad at all.
By default any files you write in your application will be gone after a deploy or another reset. Persistent Storage with Dokku is not hard, but did need a modification to my application, since I chose to use the recommended
/storage path. Convention over configuration tends to simplify things over time.
For my test app, I needed to change two things:
- read the PORT environment variable, so NGINX can forward the application.
- check if there is a
/storagedirectory and use that to open the database from.
The Dokku storage plug-in link allows you to specify a directory on the host that will be mounted in the docker container. An SQLite database is just a file.
Configuring thee ports and storage were also good feedback about the two sets of development frameworks/libraries I based my experiment on. Yesod is batteries included, but figuring out how to specify a port and storage was a pain - the scaffold has lots of code to go through. It is like reading someone else’s mind without mirror neurons. Servant doesn’t do much configuration out of the box, so dirt road solutions for a directory check and reading the PORT environment variable read were quickly added, after searching for the appropriate API calls.
Conclusion: Dokku is a keeper
Dokku makes it easy to deploy with a single
git push with a provider of your choosing. VPSes are cheap and plentiful. Dokku abstracts docker containers away without introducing unnecessary risks. If you need more than a build on git push can provide, you can always add an external continuous delivery pipeline that builds a Docker container, and push that to a Dokku host later.
Docker containers can be intimidating, but Dokku lightens the load.