I was hosting this blog on a Vultr VPS that was running just nginx for these static files (generated by hugo) and it was fine, but I had other plans for the server, and it’s usually a pain to mix your projects with an existing nginx config, so I’ve moved on to dokku for serving this blog as well.

If you don’t know it, dokku is amazing. Spin up a new server, run the bootstrap commands:

$ wget https://raw.githubusercontent.com/dokku/dokku/v0.12.10/bootstrap.sh
$ sudo DOKKU_TAG=v0.12.10 bash bootstrap.sh

Hit the server on a browser to upload your SSH pubkey, and that’s it! From then on, you can just create new git remotes for your projects and do git push dokku to have a ready-to-serve-requests Docker image created for them. It uses Heroku buildpacks, so most popular languages are supported, and you can always set your own buildpack.

For this website, I’m using dokku-static-site, and it was just a matter of creating a Dockerfile in my hugo folder root with

FROM giorgos/dokku-static-site

And change hugo’s output folder to match the expected /html with:

publishDir = "html/"

in config.toml and that’s it. My workflow is now a) create a new hugo post/page, b) run hugo and then c) do a commit and push. The deployment takes less than a minute:

$> git push dokku
Counting objects: 11, done.
Delta compression using up to 16 threads.
Compressing objects: 100% (9/9), done.
Writing objects: 100% (11/11), 850 bytes | 850.00 KiB/s, done.
Total 11 (delta 6), reused 0 (delta 0)
remote: Preparing /tmp/dokku_git.NfRT (identifier dokku_git.NfRT)
remote: ~/blog /tmp/dokku_git.NfRT ~/blog
remote: /tmp/dokku_git.NfRT ~/blog
-----> Cleaning up...
-----> Building blog from dockerfile...
remote: build context to Docker daemon 717.3kB
Step 1/1 : FROM giorgos/dokku-static-site
# Executing 1 build trigger
---> 04a3ab1b444c
Successfully built 04a3ab1b444c
Successfully tagged dokku/blog:latest
-----> Releasing blog (dokku/blog:latest)...
-----> Deploying blog (dokku/blog:latest)...
-----> Attempting to run scripts.dokku.predeploy from app.json (if defined)
-----> No Procfile found in app image
-----> DOKKU_SCALE file found (/home/dokku/blog/DOKKU_SCALE)
=====> web=1
-----> Attempting pre-flight checks
-----> Attempt 1/5 Waiting for 5 seconds ...
CHECKS expected result:
http://localhost/ => ""
-----> All checks successful!
=====> blog web container output:
172.17.0.1 - - [17/Jun/201835:49 +0000] "GET / HTTP/1.1" 200 5936 "-" "curl/7.52.1"
=====> end blog web container output
-----> Running post-deploy
-----> Configuring blog.dokku.nootch.net...(using built-in template)
-----> Configuring blog.nootch.net...(using built-in template)
-----> Creating https nginx.conf
-----> Running nginx-pre-reload
Reloading nginx
-----> Setting config vars
DOKKU_APP_RESTORE: 1
Reinitializing monit daemon
-----> Found previous container(s) (0aee033b0ab5) named blog.web.1
=====> Renaming container (0aee033b0ab5) blog.web.1 to blog.web.1.1529220954
=====> Renaming container (1a347d42c126) priceless_brahmagupta to blog.web.1
-----> Attempting to run scripts.dokku.postdeploy from app.json (if defined)
Trying to find token and chat_id for blog
-----> Notifying Telegram chat ...
-----> Shutting down old containers in 60 seconds
=====> 0aee033b0ab52022514697fcdea4884a4c3edac7409bdead83b55fb70643615a
=====> Application deployed:
http://blog.dokku.nootch.net
http://blog.nootch.net
https://blog.dokku.nootch.net
https://blog.nootch.net

To dokku.nootch.net:blog
2296848..836bf6f master -> master

It’s quite easy to setup Let’s Encrypt, Telegram bot notifications, webhooks, extra domains and more with dokku plugins. This is my go-to setup for new deployments on servers, since Docker images are much more portable and self-contained than a bespoke nginx setup. Give dokku a try, it’ll amaze you!