Publish and auto-deploy a Hugo Static Site on Firebase using GitHub and Travis-CI

w. ian douglas
6 min readDec 1, 2020
“That’s quite a mouthful”
whew, that’s a REALLY long blog title …

I have several static web sites that I maintain and deploy manually, including iandouglas.com and techinterview.guide. It’s not always convenient to be on my typical development machine to write something in markdown, run my static site generation, and deploy the new pages, so I wanted to build a workflow that will allow me to use my browser alone going forward.

Step 1: Build an empty Web project on Firebase

I use Firebase for hosting my static sites for several reasons:

  • Google scale, not that anything I’ve done lately has gone viral, but:
  • Google’s free quotas are extremely generous: 10GB of storage, 10GB/month to your readers and $0.15/GB after that; this feels like “unlimited” for small-scale sites.
  • free SSL/TLS, helping with SEO, cookies, general web safety, etc..
  • attach custom domains to your project; authenticating ownership of the domain is super simplified if you transfer your domain to Google Domains first

Visit https://firebase.google.com/docs/web/setup to get started with a web-based project.

Step 2: Start a static site using Hugo

Visit https://gohugo.io/getting-started/quick-start/ on how to get started with a Hugo static site. I love using Hugo because everything is in Markdown, there are lots of great themes available, and the fragments of code you write are Go-based. (Hugo is written in Go)

Step 3: Put your work in a GitHub repo

Your project doesn’t need to be in a public GitHub repo for this to work. My static site repos are usually private. Start a repo, commit your work in there.

Step 4: Get your GitHub repo visible on Travis-CI

Okay, wait. Travis-CI? Why not just use GitHub Actions for Firebase? Yeah, you could probably go that route though I didn’t look into it too deeply to see how to install and run Hugo to actually build the site. If you run Hugo locally and check in the generated HTML, images, etc, then GitHub actions would probably be more suitable.

I’m also a bit of a nut when it comes to testing things, making sure content exists on pages correctly, and I can tell Travis-CI to do some of the other work I’d typically do on my development workstation as part of its deployment that I’ve found harder to do using GitHub actions.

Get set up on Travis-CI.com (you’ll need to authenticate through GitHub for this to work to see your repos). Once you can see your repo on Travis you’re most of the way there.

Step 5: Build a .travis.yml file to do the deployment

This step is busy:

a. Install the Travis-CI gem (“gem install travis”) to give yourself access to the “travis” command line interface.

b. Install the Firebase CLI tools (“npm install -g firebase-tools”) to give yourself access to the “firebase” command line interface. Make sure you use the -g switch to install globally, or it’ll build a node_modules folder in your Hugo project which you probably don’t want at the root level of your project.

From here, you’ll need to authenticate to firebase from your project directory on your development machine using “firebase login”. This will open a browser where you’ll OAuth to Firebase. It should update the terminal prompt to say something like:

Waiting for authentication...✔ Success! Logged in as ian.douglas@iandouglas.com

Follow that with “firebase login:ci” where it’ll prompt you again in a browser to give your agreement to this step, at which point you should see something like this in your terminal:

Waiting for authentication…✔ Success! Use this token to login on a CI server:2//0lkjaeflinsifnseflnaknwd9305029385laskdfj

You’ll copy that cryptic looking line (it’ll be much longer than what I have above) and do the following steps in your terminal in the base path of your project (where your config.toml file and “content” folder and so on would exist for your Hugo project)

touch .travis.yml
travis encrypt "2//0lkjaeflinsifnseflnaknwd9305029385laskdfj" - add

This should prompt you to overwrite the file you just “touched” to create the file in the first place. Say “yes” at the prompt to allow it.

Now comes the whole .travis.yml file where we need to tell it which version of Hugo to download, and then I’ll talk about extra steps I do as part of my deployment:

install:
- curl -LO https://github.com/gohugoio/hugo/releases/download/v0.71.1/hugo_0.71.1_Linux-64bit.deb
- sudo dpkg -i hugo_0.71.1_Linux-64bit.deb
script:
- rm -rf public/*
- hugo
deploy:
provider: firebase
skip_cleanup: true
on:
branch: main
token:
secure: BZaL.... whatever the travis gem put here for you

Let’s break this down:

The “install” block is where we’ll use the “curl” command to tell Travis-CI to download a copy of Hugo. Use a 64-bit Linux Debian package, which you can grab once you choose a version from https://github.com/gohugoio/hugo/releases. I scrolled to v0.71.1 which I use on my development machine, clicked on that version number, and from there got the Linux 64-bit “.deb” package download link.

Next we’ll use “sudo” to tell Travis-CI to install that debian package so we’ll have access to run the “hugo” command later.

The “script” section is where we tell the CI platform to execute some commands for us. Since I may already have some files in my repo under the “public” folder that will just get rebuilt anyway, I delete all of those files first (but not the public folder itself). Then I tell it to run the “hugo” command which rebuilds my static site.

Last is the “deploy” section where we can specify “firebase” as our deployment target. We “skip_cleanup” so that new files aren’t deleted before deploying. The “on” command specifies which branch to deploy, and the “token” portion is the secured, encrypted version of that “2//whatever” string that Firebase gave us earlier. Do NOT put the “2//whatever” string in there itself as that’s an unencrypted API key, especially if your repo is public.

From there, you should be able to push that .travis.yml file up to GitHub and see if Travis-CI does a build and if that passes, deploy to Firebase.

Net Result: Modify or add markdown pages directly on GitHub and they’re deployed a few minutes later.

The only caveat I have is for my Tech Interview blog where I generate a CHANGELOG, I’d need to work out a way to start a pull request on GitHub, alter several files, and then push them all at once so I’m not cluttering up my commit logs.

And speaking of that:

Extra Things I Add in My .travis.yml file

As I mentioned above, I do extra stuff when I do my Hugo builds. I can run tests to make sure that once the site is done that links show up on the page, etc.. There are a myriad of tools you can use for this that you can set up in your .travis.yml file like Python and BeautifulSoup or any spidering tool that looks for broken links etc..

One thing I do is build a CHANGELOG file of my commit messages for users to track what I’ve changed on my Tech Interview site, so I need to install Node.js to run some JavaScript code to build a running Markdown file of my commit messages.

I also have a “clean_content.sh” script that I run that converts fancy quotes to plain quotes, eplises into literal “...” strings, and so on.

So my .travis.yml for one site might look something like this:

install:
- curl -LO https://github.com/gohugoio/hugo/releases/download/v0.71.1/hugo_0.71.1_Linux-64bit.deb
- sudo dpkg -i hugo_0.71.1_Linux-64bit.deb
- . $HOME/.nvm/nvm.sh
- nvm install stable
- nvm use stable
before_script:
- node -v
- npm install npm -g
script:
- rm -rf public/*
- sh clean_content.sh
- node changelog.js
- hugo
deploy:
etc etc

NVM is installed by default on Travis, so we just tell it to activate that NVM environment, install the latest “stable” version of Node and to “use” that version. The “before_script” portion then just ensures that Node is in fact running (or the “testing” will fail and halt), and then update NPM. At this point I can run “node changelog.js” in my “script” section to build my CHANGELOG page.

Image credits: Pinterest, BallMemes

--

--

w. ian douglas

Husband, Dad, doggo lover. Maker, Developer, Mentor.