Four days, four languages, four frameworks, Day 2: Running Hugo

Platform.sh
27 Feb 2019
Four Days: Day 2, Go and Hugo

Yesterday we looked at running a “traditional” CMS on Platform.sh — Brightspot, a Java-based CMS backed by a relational database (MySQL) and search index (Apache Solr). Lots of applications run in this way–Drupal, Magento, Wordpress, and Django to name a few.

But what if you want something really fast and light, with no database at all? That’s where Static Site Generators (SSGs) come in handy.

Static Site Generators take raw content files and generate a complete static HTML site from them that can deploy easily on any number of hosts. You don’t need any special database support. You’ll find one written in just about any language you can imagine. We’ll start with one of the fastest: Hugo, written in Go.

Go, or “Golang” if you’re searching for articles, is a statically typed, compiled language with some syntactic similarities to C. It’s also incredibly easy to run on Platform.sh.

SSGs and Platform.sh go together like peanut butter and jelly. Or peanut butter and chocolate. Or pretzels and beer. (Your preference, of course.). Why? Since Platform.sh is built around Git, an SSG like Hugo is a natural fit. Multiple developers and editors can work on their own branches within a Hugo website. And, through version control, merge all of their changes flawlessly into production—without stepping on each others toes.

Let’s get started

If you want to see the final product, we have a ready-to-run example project available. We’ll link to some portions of it throughout this process. If you have an existing Hugo app, you can skip ahead.

1. Setup your local machine

You’ll need 3 tools to deploy your Hugo site on Platform.sh:

2. Bootstrap your Hugo project

Once you’ve got Hugo installed locally, you’ll need to set up a project.

Create a new Hugo folder with the basic files:

$ hugo new site hugo-platformsh
Congratulations! Your new Hugo site is created in <path>/hugo-platformsh.

Just a few more steps, and you're ready to go:

1. Download a theme into the same-named folder.
   Choose a theme from https://themes.gohugo.io/, or
   create your own with the "hugo new theme <THEMENAME>" command.
2. Perhaps you want to add some content. You can add single files
   with "hugo new <SECTIONNAME>/<FILENAME>.<FORMAT>".
3. Start the built-in live server via "hugo server".

See https://gohugo.io/ for a quickstart guide and full documentation.

Now, you need a first article/post:

hugo new posts/hello-world.md

The most common way to write and edit content with a SSG is in Markdown, a markup syntax. There are numerous tools to edit and preview Markdown, and you can even use CMS-like tools such as Forestry.io to make it easy for content creators to write and edit without knowing any markup at all.

For now, let’s just make a simple change in our text editor of choice:

Open content/posts/hello-world.md, change the draft: true to draft: false and add some content to the file.

Once this is done, we need to initialize our project Git repository:

git init .
git add .
git commit -m "Init hugo"

The last step to having a fully working Hugo site is to choose a theme that provides all the layouts needed to render the different pages.

We’ll use hello-friend for this example. Download it into your themes directory:

cd themes
git clone https://github.com/panr/hugo-theme-hello-friend.git themes/hello-friend
rm -rf hello-friend/.git

Hugo is managed through a configuration file that supports a number of formats, but the most widely used is TOML (aka, “Tom’s Obvious, Minimal Language”). The Hugo documentation covers its options in detail.

For this project, the bare minimum configuration would be something like this:

baseurl = "/"
relativeurls = true
languageCode = "en-us"
title = "Hello Hugo"
theme = "hello-friend"

The relativeurls flag is important to allow Hugo to generate URLs that are portable between different Platform.sh environments. If a build doesn’t change when redeploying to a new environment, we reuse the existing build artifact, which saves time, but means links need to be domain-agnostic.

A more complete and robust configuration is included in the example project.

Add these changes: git add .

You should have the following files modified:

$ git status
On branch master
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

	new file:   .gitmodules
	modified:   config.toml
	new file:   themes/hello-friend

Commit the modifications:

git commit -m "Add hello-friend theme"

Launch the Hugo local server, and verify that everything is working as expected:

hugo server

Open http://localhost:1313/. You should see the posts list and a link to your article: http://localhost:1313/posts/hello-world/. Hugo is all ready!

3. Create your Platform.sh project

Yesterday, we used the Platform.sh web UI to create a new project. Did you know that anything you can do through the UI you can also do through our Command Line Interface (CLI)? Follow the instructions you saw at the beginning of this article to download and install the CLI. Then, creating a new project on Platform.sh is as simple as:

platform project:create

Select a Standard project, then choose the region you want your project to be hosted in.

You can now access your newly provisioned project.

Add the remote to your local project:

git remote add platform <project ID>@git.<region>.platform.sh:<project ID>.git

Don’t push anything for now. You still need to set up your configuration files.

4. Set up the .platform.app.yaml configuration

Platform.sh relies on yaml configurations to configure the different containers to deploy. Create the .platform.app.yaml file at the root of your project, and add the following code:

# .platform.app.yaml

# The name of this application, which must be unique within a project.
name: 'hugo'

# This could actually be any container since there's no runtime.
type: 'golang:1.11'

# The hooks that will be triggered when the package is deployed.
hooks:
    # Build hooks can modify the application files on disk, but not access any services like databases.
    build: |
      wget https://github.com/gohugoio/hugo/releases/download/v0.54.0/hugo_0.54.0_Linux-64bit.tar.gz
      tar xvzf hugo_0.54.0_Linux-64bit.tar.gz
      rm hugo_0.54.0_Linux-64bit.tar.gz
      ./hugo

# The size of the persistent disk of the application (in MB).
# You don't need much here at all for a static site as it won't be used.
disk: 256

# The configuration of the application when it's exposed to the web.
web:
    commands:
        start: sleep infinity
    locations:
        '/':
            # The public directory of the application relative to its root.
            root: 'public'
            index: ['index.html']
            scripts: false
            allow: true

The critical parts of the file are:

1) The build hook downloads the Hugo binary on the fly, unpacks it, and then runs Hugo against the source files in the project. That means Hugo itself never needs to be part of your Git repository! By default, Hugo will put all of its generated files in the public directory. 2) The web.locations block then points the site’s docroot at the generated public directory, with a typical index file, allows all file types to be served, but no scripts can be run. (There aren’t any, so that’s just extra careful security.) 3) In practice, there’s no need for a persistent application process since the site is just static files. That means we can either use a PHP-based container (which starts PHP-FPM in the background that never gets used) or use any other container type and explicitly set the web.commands.start key to sleep infinity, which is a UNIX command. There’s still a background process, but it will do nothing.

Also of note: even a static site needs to have a writeable disk defined, but it won’t be used. 256 MB is the smallest it can be, so set it to that, and don’t define any mount points.

We also need two other files: routes.yaml and services.yaml. services.yaml is used to configure additional services like databases, so we don’t need it for that project. Just create the file:

mkdir .platform
touch services.yaml

Add routes.yaml in the .platform folder and add the following configuration:

"https://{default}/":
  type: upstream
  upstream: "hugo:http"

This file tells the Platform.sh router to direct all incoming requests to our hugo container. Commit these new files:

git add .platform.app.yaml .platform
git commit -m "Add platform.sh configuration"

5. Test and deploy

We’re now ready to deploy the project on Platform.sh. Push the repository to the new remote:

git push -u platform master

Then Platform.sh will build the project, with the full output shown below.

```sh Counting objects: 36, done. Delta compression using up to 4 threads. Compressing objects: 100% (26/26), done. Writing objects: 100% (36/36), 3.77 KiB | 1.26 MiB/s, done. Total 36 (delta 8), reused 0 (delta 0) Validating submodules Validating configuration files Processing activity: Guillaume Moigneu pushed to Master Found 8 commits Building application 'hugo' (runtime type: golang:1.11, tree: 4a2255e) Generating runtime configuration. Executing build hook... W: --2019-02-17 21:18:48-- https://github.com/gohugoio/hugo/releases/download/v0.54.0/hugo_0.54.0_Linux-64bit.tar.gz W: Resolving github.com (github.com)... 192.30.253.112, 192.30.253.113 W: Connecting to github.com (github.com)|192.30.253.112|:443... connected. W: HTTP request sent, awaiting response... 302 Found W: Location: https://github-production-release-asset-2e65be.s3.amazonaws.com/11180687/20035e00-260e-11e9-8a69-0dcf2eda5a47?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAIWNJYAX4CSVEH53A%2F20190217%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20190217T211848Z&X-Amz-Expires=300&X-Amz-Signature=4e1d055aeb0947dcd74b04e7b9e477547d5d8ae44bc3e6e5fff1f7ed87a6f115&X-Amz-SignedHeaders=host&actor_id=0&response-content-disposition=attachment%3B%20filename%3Dhugo_0.54.0_Linux-64bit.tar.gz&response-content-type=application%2Foctet-stream [following] W: --2019-02-17 21:18:48-- https://github-production-release-asset-2e65be.s3.amazonaws.com/11180687/20035e00-260e-11e9-8a69-0dcf2eda5a47?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAIWNJYAX4CSVEH53A%2F20190217%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20190217T211848Z&X-Amz-Expires=300&X-Amz-Signature=4e1d055aeb0947dcd74b04e7b9e477547d5d8ae44bc3e6e5fff1f7ed87a6f115&X-Amz-SignedHeaders=host&actor_id=0&response-content-disposition=attachment%3B%20filename%3Dhugo_0.54.0_Linux-64bit.tar.gz&response-content-type=application%2Foctet-stream W: Resolving github-production-release-asset-2e65be.s3.amazonaws.com (github-production-release-asset-2e65be.s3.amazonaws.com)... 52.216.97.123 W: Connecting to github-production-release-asset-2e65be.s3.amazonaws.com (github-production-release-asset-2e65be.s3.amazonaws.com)|52.216.97.123|:443... connected. W: HTTP request sent, awaiting response... 200 OK W: Length: 7767211 (7.4M) [application/octet-stream] W: Saving to: ‘hugo_0.54.0_Linux-64bit.tar.gz’ W: W: 0K .......... .......... .......... .......... .......... 0% 27.9M 0s [...] W: 7550K .......... .......... .......... ..... 100% 140M=0.1s W: W: 2019-02-17 21:18:48 (61.5 MB/s) - ‘hugo_0.54.0_Linux-64bit.tar.gz’ saved [7767211/7767211] W: LICENSE README.md hugo Building sites … | EN +------------------+----+ Pages | 9 Paginator pages | 0 Non-page files | 0 Static files | 16 Processed images | 0 Aliases | 4 Sitemaps | 1 Cleaned | 0 Total in 14 ms Executing pre-flight checks... Compressing application. Beaming package to its final destination. W: Route '{default}' doesn't map to a domain of the project, mangling the route. Provisioning certificates Validating 1 new domain Provisioned new certificate for 1 domains of this environment (Next refresh will be at 2019-04-20 20:19:01+00:00.) Environment certificates - certificate 18bf626: expiring on 2019-05-18 20:19:01+00:00, covering master-7rqtwti-..platformsh.site Creating environment -master-7rqtwti Environment configuration hugo (type: golang:1.11, size: M, disk: 5120) Environment routes http://master-7rqtwti-..platformsh.site/ redirects to https://master-7rqtwti-..platformsh.site/ https://master-7rqtwti-..platformsh.site/ is served by application `hugo` ```

Let’s review the output of your push. After a basic Git push output, Platform.sh kicks in and runs the build script. Under where it says Executing build hook you can see it downloading Hugo and then running it:

  Building sites …
                           | EN
        +------------------+----+
          Pages            |  9
          Paginator pages  |  0
          Non-page files   |  0
          Static files     | 16
          Processed images |  0
          Aliases          |  4
          Sitemaps         |  1
          Cleaned          |  0

        Total in 14 ms

There’s Hugo doing its thing (and very quickly, too!)

Platform.sh then checks that everything seems correct and deploys the container to a host. Finally it shows the URLs for the created environment:

      Executing pre-flight checks...

      Compressing application.
      Beaming package to its final destination.

    W: Route '{default}' doesn't map to a domain of the project, mangling the route.

    Provisioning certificates
      Validating 1 new domain
      Provisioned new certificate for 1 domains of this environment
      (Next refresh will be at 2019-04-20 20:19:01+00:00.)
      Environment certificates
      - certificate 18bf626: expiring on 2019-05-18 20:19:01+00:00, covering master-7rqtwti-<project ID>.<region>.platformsh.site


    Creating environment <project ID>-master-7rqtwti
      Environment configuration
        hugo (type: golang:1.11, size: M, disk: 5120)

      Environment routes
        http://master-7rqtwti-<project ID>.<region>.platformsh.site/ redirects to https://master-7rqtwti-<project ID>.<region>.platformsh.site/
        https://master-7rqtwti-<project ID>.<region>.platformsh.site/ is served by application `hugo`

The last output is your application’s new URL. You can also check that the project has been successfully deployed on the web interface:

Now go the URL, and you’ll be able to see your Hugo site!

Want a quick start? You can clone our Hugo example repo on Github and follow the instructions we’ve included in the readme.

Tomorrow, we’ll explore another SSG, Gatsbyjs, an SSG written in React, and we’ll add more TLAs to our lexicon: JAM, PWA.

Read the series: Four days, four languages, four frameworks

  1. Day 1, Java: Running Brightspot CMS on Platform.sh