Moving From Wordpress to Hugo

March 4, 2019
blog firebase git travis-ci

I decided to change things up for my blog again, after getting restless with Wordpress. I wanted to play with some new tech, and here I am.

Summary:

Goals:

Benefits:

Downsides:

So how does the new system work?

Create accounts

Create a Github account and create two repositories, one for the raw Markdown content, images, themes. Another for the generated website. It’s up to you whether the first one is public or private, but the generated website repository must be public. Why? Travis CI needs access to it via webhooks to trigger a built upon every commit.

Using your Github credentials, create a Travis-CI account, and sync your repositories to it. Don’t worry, all this is free unless your blog gets really popular and you update content a lot.

Finally, create a Firebase account and upgrade to the Blaze account type. This uses ‘pay as you go’ pricing with a decent free-tier that will help you get up and running and judge whether to jump to the next pay tier if the site gets more popular.

Writing content

Create your blog using the instructions on the Hugo quickstart.

Depending on how you want to set up your Git repositories there are several ways to do the next part.

I created a private Github repository for the raw blog site, where I can do my endless commits and fixes before content goes public. I then created a public Github repository for the generated HTML content, images, etc. This public repository is a submodule of my raw blog site directory, where I and commit the generated content.

After I am done writing the post in markdown, I run the following script to commit the changes to both private and public repo, and then submit everything.

publish_to_blog.sh

#!/bin/bash -x

if [ "$#" -lt 1 ]; then
    echo "Error: Forgot to specify an environment: test or prod"
    exit 1
fi

if [ "$#" -ne 1 ]; then
    COMMIT_MSG="${@:2}"
else
    COMMIT_MSG="generic commit on `date`."
fi

HUGO_ENV="$1"
echo "Committing and deploying to $HUGO_ENV"
echo "Commit message: $COMMIT_MSG"

BASEDIR='/git/blog-private'

cd "$BASEDIR/blog/public/blog-$HUGO_ENV"
git submodule update --remote --merge 

cd $BASEDIR

# # create the content
$BASEDIR/bin/hugo --environment $HUGO_ENV -s $BASEDIR/blog

# # commit the changes in the private repo
cd "$BASEDIR"
git commit -m "commit to $HUGO_ENV: $COMMIT_MSG"
git push origin master

# # # commit the changes in the $STAGE public repo
cd "$BASEDIR/blog/public/blog-$HUGO_ENV"
git add .
git commit -m "deploy to $HUGO_ENV: $COMMIT_MSG"
git push origin master

Deploying Content

After submission of the blog content to the public Github repository, travis-ci is used as the continuous deployment solution to get my changes pushed to Firebase. Why?

I wanted to reduce the number of local dependencies required to publish Blog content. The bare minimum: an editor, the Hugo binary, and access to the private Git repo that houses my content. If I wanted to deploy directly from my machine to Firebase it would require nodejs installed (Firebase’s tools require it), plus the Firebase API keys for my account. Often the machine I create personal content on has restrictions about storing credentials, and so I’d rather avoid that.

Back to the workflow.

Generating Credentials

To allow travis-ci access to your Firebase hosting account, you need to create API credentials. Not wanting to install nodejs on my machine itself, I fired up a nodejs container:

hostmachine$ docker run -it --rm node:11-alpine /bin/ash
.....
container# npm install -g firebase-tools
...
container# firebase login --no-localhost
...
container# firebase init
(select hosting)
container# firebase login:ci --no-localhost

The --no-localhost flag is important. These commands normally set up a locally running httpd to verify your identity. Since you ran the commands inside a container, ‘localhost’ is within the container, and NOT your hosts interface. It was more work than it’s worth doing all the port forwarding, and –no-localhost solves that problem completely.

Save the token returned from the login:ci invocation. You’ll need it when you set up your travis-ci repository.

I used all the default options on my repo’s configuration in travis-ci, and added the below specified environment variables to be able to perform the actual deploy.

The following three files should be located in the base directory of your public Git repository.

.travis.yml

language: node_js
node_js:
  - "stable"
script:
  - echo "Deploying!"
install:
  - npm install -g firebase-tools
after_success:
  - firebase deploy --project $FIREBASE_PROJECT --token $FIREBASE_TOKEN --only hosting:$TARGET

This travis-ci configuraiton uses a node_js container, installs the firebase-tools, and then runs a hosting-only deploy of the content to the configured project and host target.

.firebaserc

{
  "projects": {
    "prod": "formannet-infra"
  },
  "targets": {
    "formannet-infra": {
      "hosting": {
        "blog-prod": [
          "blog.jeffreyforman.net"
        ]
      }
    }
  }
}

This JSON file configures the project associated with your repository, along with the targets of the hosting deploy. (This was one of the more confusing parts of the deploy. If you only have one site per Firebase project, you don’t need a .firebaserc file)

In this instance:

firebase.json

{
  "hosting": {
    "target": "blog-prod",
    "public": "blog",
    "ignore": [
      "firebase.json",
      "**/.*",
      "**/node_modules/**"
    ]
  }
}

firebase.json configures which directory is used for the deploy (in this case, blog), for the target blog-prod, excluding the listed files from being deployed.

Documentation for the configuration files can be found at https://firebase.google.com/docs/hosting/full-config.

An image of travis-ci deploying a blog update:

Image of TravisCI deploy workflow

You have now, with two Git commits, deployed a blog update to your Firebase-hosted website without any of the other expensive dependencies of deployment. And you even get an email once deployment has completed.

Victory.

References