So here I am trying to get back into writing blogs. With no-where to write a blog. I've had the repository for this middleman site sitting around for about a half year now and had a day to kill. So I thought today seemed as good as any other day to have a play with Dokku.
Mainly because it's just a fun project. But deploying a static site, means we aren't having to process a heck of a lot every time our webserver gets a request. Which makes it ideal to host on a low-end non-expensive instance.
Dokku is a docker powered mini-Heroku or Platform as a Service (PaaS) you can spin up in any virtual server you might have. It's pretty easy to set up yourself and even easier if you're willing to pay $5 a month to DigitalOcean for one of their one-click Dokku instances.
If you have your own domain you can also do the cool heroku thing and host apps on subdomains off your domain. (blog.dekai.kiwi etc.)
Once you're up and running on Dokku the first thing we want to do is create our blog app.
dokku apps:create blog
and that's about it for now…
Dokku (and also Heroku) don't really have an out of the box solution for deploying static HTML pages. So what can we do about this?
Well.. To be honest I just googled it and found this lovely gem to generate the files for me :)
But let's go through each of the files this gem generates and figure out what is going on…
Procfile
web: bundle exec puma -p $PORT
Pretty standard Heroku web worker definition here. A puma instance will be initialized for each web
worker we choose to run. Each puma instance will run the below mentioned config.ru
config.ru
# Modified version of TryStatic, from rack-contrib
# https://github.com/rack/rack-contrib/blob/master/lib/rack/contrib/try_static.rb
# Serve static files under a `build` directory:
# - `/` will try to serve your `build/index.html` file
# - `/foo` will try to serve `build/foo` or `build/foo.html` in that order
# - missing files will try to serve build/404.html or a tiny default 404 page
module Rack
class TryStatic
def initialize(app, options)
@app = app
@try = ['', *options.delete(:try)]
@static = ::Rack::Static.new(lambda { [404, {}, []] }, options)
end
def call(env)
orig_path = env['PATH_INFO']
found = nil
@try.each do |path|
resp = @static.call(env.merge!({'PATH_INFO' => orig_path + path}))
break if 404 != resp[0] && found = resp
end
found or @app.call(env.merge!('PATH_INFO' => orig_path))
end
end
end
use Rack::Deflater
use Rack::TryStatic, :root => "build", :urls => %w[/], :try => ['.html', 'index.html', '/index.html']
# Run your own Rack app here or use this one to serve 404 messages:
run lambda{ |env|
not_found_page = File.expand_path("../build/404.html", __FILE__)
if File.exist?(not_found_page)
[ 404, { 'Content-Type' => 'text/html'}, [File.read(not_found_page)] ]
else
[ 404, { 'Content-Type' => 'text/html' }, ['404 - page not found'] ]
end
}
So looking at this, we can't strictly say that our site is 100% static, but it's good enough! This is just a simple web app to try and find an HTML file in the immediate file system. Specifically within the build
directory
Rakefile
require 'middleman/dokku'
namespace :assets do
task :precompile do
sh 'middleman build'
end
end
As part of the deployment process for a Ruby project. Dokku will attempt a precompile
step. This is where we can define exactly what should happen at compile-time for our blog. In this case we want middleman to create the static HTML files in the build
directory from the source directory
With that we should be ready to test the deploy!
2022 UPDATE: The middleman-dokku gem is unmaintained and references several dependencies are now considered insecure. To satisfy dependabot on GitHub I removed the gem. The gem itself only generates the boilerplate files as listed above. So copying and pasting these into your own project should do the trick. (Just remove the require 'middleman/dokku' line from the top of the RakeFile).
As with any deploy to Heroku. You will need to set up a new git remote for Dokku.
git remote add dokku dokku@<server-address>:<app-name>
Now all we need to do is give it a push and Dokku should take care of the rest!
git push dokku master
If everything goes well here. Then you should be seeing your Middleman site live in your Dokku app.
Congratulations! Now it's time for the victory lap!!
If you're anything like me you'll want to take as much hassle out of deployment of your blog as possible. So let's make it so that whenever we merge to master we automate a deploy to our Dokku instance.
This shouldn't be a problem for most CI solutions. I opted for CircleCI as it's the solution I'm most familiar with.
.cirleci/config.yml
version: 2
jobs:
deploy:
machine:
enabled: true
working_directory: ~/repo
steps:
- checkout
- run:
name: Deploy Master to Dokku
command: |
git push $DOKKU_GIT_ENDPOINT master
workflows:
version: 2
deploy_middleman:
jobs:
- deploy:
filters:
branches:
only: master
DOKKU_GIT_ENDPOINT
should be the entire git URI that you push to for deploys
Additionally we're going to need to generate some ssh credentials for our CI.
ssh-keygen
Generating public/private rsa key pair.
Enter file in which to save the key (~/.ssh/id_rsa): ~/.ssh/circleci
...
We need set the public key up on the Dokku instance.
cat ~/.ssh/circleci.pub | ssh root@<server-address> "sudo sshcommand acl-add dokku circle_deploy_key"
Then you'll need to set the private key in the CircleCI control panel for your project.
Now in theory whenever you push to master. You should build and deploy the latest version of your blog! Fancy!!
That's all for now. You can find the public repo for this blog and the specific file for this blog post below.
~ Jono