Hugo and Google Cloud Storage
- 6 minutes read - 1214 wordsI’m using Hugo as a static site generator for this blog. I’m using Firebase (for free) to host lefsilver.
I have other domains that I want to promote and decided to use Google Cloud Storage buckets for these sites. Using Google Cloud Storage for Hosting a static website and using Hugo to deploy sites to Google Cloud Storage (GCS) are documented but, I didn’t find a location where this is combined into a single tutorial and I wanted to add an explanation for ensuring your sites are included in Google’s and Bing’s search indexes.
I assume you’ve installed Hugo; I’m running Ubuntu and I run Hugo as a Snap Package
I assume you own a domain (${DOMAIN}
) and wish to create a website on it (www.${DOMAIN}
).
NOTE The site we’re going to create will not use TLS; you can add a Load-balancer in front of a GCS bucket if you wish to use TLS but this costs $$$
You will need:
- Domain
- Google Cloud Platform (GCP) Project & Billing account
- GitHub repo
Environment
For convenience, I’m going to define here some constants that we’ll be using in the remainder of this tutorial:
BILLING="[YOUR-BILLING]"
PROJECT="[YOUR-PROJECT]"
WEBSITE="www.[[YOUR-DOMAIN]"
Create Hugo site
The following commands create a directory ${WEBSITE}
in the working directory and prepopulates it with Hugo’s basic site content, then changes to this directory.
hugo new site ${WEBSITE}
cd ${WEBSITE}
If you wish, you may run a debugging server:
hugo server -D
And may browse your site on http://localhost:1313
Create GCP Project and Enable Billing
If you have an existing GCP Project in which you want to create a Google Cloud Storage (GCS) bucket, then skip this step. Otherwise, a GCP Project must be created to hold the GCS bucket. You will need to have a billing account linked to the project in order to continue.
gcloud projects create ${PROJECT}
gcloud beta billing projects link ${PROJECT} \
--billing-account=${BILLING}
Create Cloud Storage Bucket
Now, we may create a bucket in a project. The bucket must be named to reflect your domain name, i.e. for example.org
, the bucket name would be www.example.org
:
gsutil mb \
-b on \
-p ${PROJECT} \
gs://${WEBSITE}
NOTE GCS bucket names are unique and using
${WEBSITE}
as the value may require verification
NOTE
-b on
provides Uniform Bucket-Level Access
Configure access and define index.html
and 404.html
We want to make the bucket’s contents world-viewable and we achieve this by making everyone object viewers (allUsers:objectViewer
).
We will also configure the site to default to ./index.html
and, on errors, ./404.html
. We’ve not yet created either of these files but will do so in a subsequent step.
gsutil iam ch allUsers:objectViewer gs://${WEBSITE}
gsutil web set -m index.html -e 404.html gs://${WEBSITE}
Service Account
For convenience and security, we’re going to create a serivce account that will be used (only) by Hugo to deploy our site (copy the content of the ./public
directory) to the GCS bucket. The following commands create a service account called ${ACCOUNT}
in our project (${PROJECT}
), generate a key for it (called ./${ACCOUNT}.json
) that is stored within the site’s local directory and grants the service account permission to deploy content to the bucket ${BUCKET}
.
ACCOUNT="hugo-deployer" # Or your preference
gcloud iam service-accounts create ${ACCOUNT} \
--project=${PROJECT}
EMAIL="${ACCOUNT}@${PROJECT}.iam.gserviceaccount.com"
gcloud iam service-accounts keys create ${PWD}/${ACCOUNT}.json \
--iam-account=${EMAIL} \
--project=${PROJECT}
MEMBER="serviceAccount:${EMAIL}"
ROLE="roles/storage.objectAdmin"
BINDING="${MEMBER}:${ROLE}"
gsutil iam ch ${BINDING} gs://${WEBSITE}
NOTE
gsutil iam ch
grants the service accountstorage.objectAdmin
to the bucket (only)
Create GitHub repo
Create an empty, private GitHub repo and follow GitHub’s instructions to initialize the local clone:
git init
git remote add origin https://github.com/...
NOTE Even though we copying the site’s
./public
directory content to the GCS bucket, we will commit our source files to the GitHub repo that we created previously.
Add Ananke Gohugo theme
The Ananke Gohugo theme is one of many but, it’s an exemplar. It is recommended to add it as a submodule and we must then revise the site’s configuration file (./config.toml
) to reflect it:
git submodule add https://github.com/budparr/gohugo-theme-ananke.git themes/ananke
echo 'theme = "ananke"' >> ./config.toml
Configure site
The following configuration should be sufficient.
In addition to reflecting the value of your ${WEBSITE}
, these changes:
- enable
robots.txt
andsitemap.xml
which are used by search engines to trawl your content - configure the GCS bucket (
gs://[[WEBSITE]]
) as a deployment target for Hugo - add configuration for static content so that this is gzipped and cached (for one year)
Edit ${PWD}/config.toml
:
baseURL = "http://[[WEBSITE]]/"
languageCode = "en-us"
title = "Your Title Here"
theme = "ananke"
enableRobotsTXT = true
[sitemap]
changefreq = "monthly"
filename = "sitemap.xml"
[params]
show_reading_time = true
[deployment]
[[deployment.targets]]
name = "gcs"
URL = "gs://[[WEBSITE]]"
[[deployment.matchers]]
# Cache static assets for 1 year.
pattern = "^.+\\.(js|css|svg|ttf)$"
cacheControl = "max-age=31536000, no-transform, public"
gzip = true
NOTE Replace
[[WEBSITE]]
with the value of${WEBSITE}
Create Basic Content
We’re closely following Hugo’s Quick Start guide with one exception. The guide assumes we’re building a blog site. If that’s what you want, follow the quick start. For this tutorial, we’re using Hugo and the Ananke theme to create a web site. Rather than periodic posts, we’ll create a possibly more complex structured site rooted on index.html
.
See:
Following the steps described in these 2 links, you will create _index.md
, index.html
and 404.html
. Feel free to tweak these files as you wish. For now _index.md
need only exist and need not contain any content… Perhaps, if you’d like “Hello, World!”.
You should have this file structure:
.
├── archetypes
├── config.toml
├── content
│ └── _index.md
├── data
├── layouts
│ ├── 404.html
│ └── index.html
├── resources
├── static
└── themes
└── ananke
Deploy
Yay!
This is a 2-step process.
We must first generate the site (hugo
). This creates (if necessary) and regenerates the content of ./public
directory.
Second, we push the generated site (i.e. ./public
) to the GCS bucket. To do this step requires 2 pieces of data. A reference to the service account key that we created previously (passed in the environment using GOOGLE_APPLICATION_CREDENTIALS
) and a deployment target. When we edited ./config.toml
, we created a deployment target called gcs
(in the value of deployment.targets.name
)
hugo deploy && \
GOOGLE_APPLICATION_CREDENTIALS=${PWD}/${ACCOUNT}.json \
hugo deploy \
--target=gcs
Enjoy
Assuming the hugo deploy
completes successfully, you should be able to browse your site http://[[WEBSITE]]
You may also check the robots.txt (http://[[WEBSITE]]/robots.txt) and Sitemap (http://[[WEBSITE]]/sitemap.xml)
Commit
Even though hugo deploy
will publish the generated site to the GCS bucket, you should still commit your changes to the GitHub repo.
At a minimum:
content/_index.md
layouts/404.html
layouts.index.html
.gitignore
.gitmodules
config.toml
You may wish to use a .gitignore
:
# Don't commit Service Account key
hugo-deployer.json
# No need to commit generated files
public/
Google Analytics
If you use Google Analytics, you may add your site’s Tracking ID to the top most section of ./config.toml
:
googleAnalytics = "UA-123456789-1"
Search Engines
You will need to perform the following steps to add that your website to Google’s and Bing’s search index to ensure it appears in these engines’ search results.
For each website, add the site on each of these sites.
You may also wish to specify the Sitemap URL too: http://[[WEBSITE]]/sitemap.xml
You may be required to prove ownership of the domain (again). The Consoles will provide options to you to complete this step.