Continuous Deployment with Hugo using Deploybot and a CronJob

Beside of all advantages of a Static Site, they have one significant disadvantage for blogs in comparison to popular Systems like Wordpress or Drupal running on a PHP-Server. Static Sites needs to be re-deployed to publish new content. In this article, I show you a way to do this with a regular update without a manual process in place.

For organizational purposes and to publish content on a regular base many editorials on blogs produce their content in advance and plan the publication properly often months ahead. This process might work without further hassle for any well known CMS Systems like Wordpress or Drupal. Since they build their content on server request directly from the database (with or without a caching layer in between for performance), Static Sites needs a redeployment to publish new content.

So, Hugo does not fit for Blogs then? No, not at all. I think you should especially read the next lines if you do not trust me.

How does Hugo decide to build a content or not?

In the contents Front-Matters, there are three main properties to understand for a time-based generation of content. .Draft, .PublishDate and .ExpiryDate. Although all of them have a very descriptive name let us go a little bit in depth for these parameters you can set and what they do.

Draft

The draft state expects a boolean so true or false can be set. When draft is missing in the Front-Matters, Hugo renders the content - so it uses false as a default setting. If the draft is set to true, the content will not be generated, no matter what is defined in PublishDate or ExpiryDate.

You can overrule this with the command-line flag --buildDrafts (or -D) so Hugo ignores the draft flag in the decision if it should be generated or not. But you can still access to the proper set draft state with .Draft (boolean) variable in the template. Upon this a Site-Variable .Site.BuildDrafts (boolean) returns if drafts were generated or not.

# include content marked as draft
hugo server --buildDrafts

An example front-matter for a draft content:

+++
date = "2017-02-09T19:09:15.686Z"
title = "This article is a draft. Still ongoing writing in the process."
draft = true
+++

PublishDate

Like Date PublishDate expects an ISO-8601 Date (e.g. 2017-02-09T19:09:15.686Z) formatted like YYYY-MM-DDTHH:mm:ss.sssZ. Hugo considers the PublishDate only at the moment of building the content and has no influence on the static generated output that it appears like magic.

To include the content with a PublishDate in future you can use the --buildFuture (or -F) flag in the command.

#  include content with publishdate in the future
hugo server --buildFuture

An example front-matter with a publish date set:

+++
date = "2017-02-09T19:09:15.686Z"
publishdate = "2017-02-15T07:15:00.686Z"
title = "This article will be published on 15th of february."
draft = false
+++

ExpiryDate

Like the PublishDate every content can have an ExpiryDate. Hugo does not build a content with an expiry date that passed by.

To ignore the ExpiryDate in the build, you can pass the flag --buildExpired (or -E) on the command line.

# include expired content
hugo server --buildExpired

An example front-matter for an expiry date

+++
date = "2017-02-01T07:00:15.686Z"
publishdate = "2017-02-14T22:00:00.686Z"
title = "Valentines Day Special offer."
draft = false
+++

Triggering continuous deployments manually or automatically?

So the strategies for a time-based content generation in Hugo is pretty straight forward. However, there is still no chance to swap content after deployment is over, and as a tradeoff having the benefits of a static site an interval or repeating deployments are mandatory.

Now there are 2 main general ways to do this. On the one hand side, we have the obvious choice to trigger the deployments manually always when a new content should be published, and on the contrary, we can choose to trigger the deployments automatically in a regular loop.

Nevertheless, which way to you prefer more to trigger the deployment, having an automatic deployment process in place helps a lot.

Let us have a look at these two options.

Manually trigger deployments

Maybe it sounds more like a joke than a serious option. However, the benefits of manually triggering the deployments may are reasonable in certain situations.

Pros:

  • Less overhead since its triggered just in time when needed
  • You could be more or less sure, the responsible person checks if the deployment went as expected after a manual trigger of the deployment

Cons:

  • An update can be forgotten very easily
  • It is time-consuming. That is not good in a “time-equals-money”-society
  • Personal planning and organization is involved

Manually triggering deployments is obviously not the right approach for frequently updated magazines while it can be reasonable for a flower shop to promote their special offers twice a year.

Automatically trigger deployments

Hooray, automation is king! Absolutely! We are living in a techy world, so we must go with automation for triggering continuous deployments, right? In the automatic approach, something needs to trigger the deployments periodically. I have used a cronjob doing the trick.

Pros:

  • Do it once, and forget.
  • More reliable than triggering by hand
  • Less time consuming

Cons:

  • Overhead when the interval is too short
  • Updates are always late (at least until the next interval)
  • Needs effort to setup and adjust the interval

So basically the automatically interval is a very reasonable approach for Blogs publishing on a regular base, for example, twice a day or once a week. In such a case you can synchronize the periodical trigger of the deployments very well with your publish rhythm. However, think about the overhead if you do too many deployments without any actual changes.

In an ideal world, there should be a way to check if there is an actual publication change since the last check and only trigger the deployments then. Alternatively, even better, creating a schedule for the deployments after every change of the content and update the cronjobs based on the timetable. So you avoid the overhead and always trigger on time.


How to implement the automatic approach?

Finally, here you are at the more practical chapter of this article, where I show you how this blog as implemented its automated deployments.

We are using Deploybot for the build process and the synchronization to AWS S3 and EasyCron maintaining the cronjobs that trigger the deployment very easily.

Deploybot

First, you need to setup a deploybot environment for a Hugo container. For this step, we assume you have an actual working environment on deploybot :-)

To trigger the deployment, we make use of the Deploybot API.

Sending a POST request with your API-Key as X-Api-Token in the header as well as environment_id and a deploy_from_scratch instructions in the body as JSON and the deployment is done.

Further possible options like user_id, deployed_version, trigger_notifications and comment are left out, but feel free to use them if you like.

Generate your API-Key

In Settings > Developer API press the button called Generate a new token.... The direct link is https://yoursubdomain.deploybot.com/api_keys (don’t forget to replace yoursubdomain with your actual subdomain)

Find the environment_id

Navigate to a detail page of a repository under Settings > Webhooks & Badges in the Trigger deployment chapter at the end you see [environment_name] environment there is a link with a highlighted env_id=12345 while 12345 is the environment id you want to trigger.

Why deploy_from_scratch=true

Even the codebase did not change there can be a change within the publication or expiry dates generating different content. Deploybot would ignore the deployment in such a case.

EasyCron

Using EasyCron makes it a piece of cake creating and manage your cronjobs without complicated server settings. EasyCron has a freemium business model. However, it is very affordable for premium packages.

Since Deploybot is requesting custom headers, you need to have at least the Plus Package which is free for the first 7 days and cost you around $20 per year.

Create the cronjob

Head over to the My Cron Jobs-Tab and click the Create New Cron Job upper left in the content section.

Fill out at least the following fields:

Basic-Tab

URL to call: https://yoursubdomain.deploybot.com/api/v1/deployments/ (replace yoursubdomain with your custom deploybot subdomain)

When to execute: chose your ideal interval

Methods & Headers-Tab

HTTP method : POST

Send data :

{
  "environment_id": 12345,
  "deploy_from_scratch":true
}

HTTP headers : X-Api-Token:123456789ABCDEFG

Save it and you are done. It is recommended to test the cronjob you have just created in the column Actions of the overview table press the Laboratory-Icon and you should see a response similar like:

HTTP-Code: 200
Status: Succeeded

View Output:

…some headers - then…

{"id":123456,"repository_id":98765,"environment_id":12345,"user_id":999999,"deployed_version":"ABCSTRANGEHASHVALUE","deploy_from_scratch":true,"trigger_notifications":true,"is_automatic":false,"comment":"Auto Deployment with EasyCron","author_name":"Your Name","state":"waiting","retries":0,"created_at":"2017/02/10 01:51:20 +0300","deployed_at":null}

I wish you happy continuous automatic deployment with Hugo, Deploybot, and EasyCron.