danmc.net

Hosting a Static Website on Google Cloud Storage

There are a lot of ways to build a simple website, but for the somewhat technically inclined (i.e., you know what CLI and git mean), a static site generator is a compelling option.

What's a static site generator? Let's break it down: What do I mean by static? A static website does not have a server side process dynamically creating content; everything is just static files (html, js, jpg, etc.). All it requires is some method of serving those files to the internet. Pretty simple. What about generator? A generator in this context is just a tool that combines content created with a simple markup language like markdown, some configuration (site name, copyright info, menu names, etc.), and a pre-built theme or template to create an optimized set of static files ready to serve to the internet.

In this post I describe the method I used creating a web site; I'll call it blog.example.com. Depending upon what you're familiar with, you may find other options to your liking. My intent isn't to justify that this method is the best as there is no end to those kind of arguments. I should know as I have them with myself all the time 😃.

Register a domain name

The first step was to register a domain name for the website. I like to use namecheap.com as a registrar. I used their search feature to find an available domain name that I liked and bought it. The process was pretty straight forward.

Figure out how to host the files

I considered these options:

If I chose a cloud hosting service or a VPS, I would have had to configure a lot of stuff and setup a web server such as NGINX or Apache to serve my content. I also would have to continually keep up with OS updates and other maintenance, and pay for a constantly running virtual server or compute instance. In the end, I chose to use an object storage service as it seemed simpler and cheaper. I can just upload the files and the service takes care of serving them to the public. I just pay for the storage and bandwidth that I use.

I chose Google Cloud Storage over the other object storage services because it is what I'm most familiar with and I generally like their admin interface better than AWS.

To setup my static website on Google Cloud Storage (GCS) I followed the great instructions here. I am not going to go through each step in detail, as the instructions provided there are pretty good. Instead, I'm just going to highlight the major steps.

Setup DNS records

To direct traffic from http://blog.example.com to the static content hosted on GCS, there are two DNS records I had to add. Namecheap, like most domain registrars, provides a DNS service and you can add records in their admin interface. To direct requests for http://blog.example.com to http://c.storage.googleapis.com, I added a CNAME record.

The other DNS record I added was a special TXT record that indicates to Google that I do indeed own the example.com domain. I followed the instructions here to get the appropriate TXT record and then clicked Verify to tell Google to do a DNS query to verify domain ownership. The basic idea is that if Google gives you a special record to add to your DNS server, and then they query the DNS server for the domain and see that the record is there, they can be sure you indeed do control that domain and are not trying to hijack a domain that you do not own. After successful verification I got something like this:

domain verified

Here are the final DNS records I added:

DNS records

Later I also added a non-standard "unmasked" URL Redirect Record. This just makes it so if someone requests http://example.com, they get a 302 HTTP status code redirecting them to http://blog.example.com.

Create a Google Cloud Platform (GCP) billing account

To use GCP, I had to have a GCP billing account set up. I already had one from other projects, but I could have used google's instructions to set it up if required.

Create a GCP project

I added a new GCP project for the site. To create a project, I went to the project selector page, clicked "Create", entered a project name, and clicked "Create" again.

GCP project selector
New GCP project

Create a Google Cloud Storage (GCS) bucket

Next I created a GCS bucket for the site's static files. I went to the storage browser and clicked "Create" to create a new bucket.

storage browser

The wizard was pretty self-explanatory, but I had to make sure to name the bucket the same as the fully qualified domain name (blog.example.com). This is how Google knows how to route requests sent to c.storage.googleapis.com. I also selected "Bucket Policy Only" so that the same permissions apply to the whole bucket to keep things simple.

Setup permissions for the GCS bucket

After creating the bucket, it took me to the bucket details:

bucket details

I clicked on the "Permissions" tab, then "Add Members" and added "allUsers" with the "Storage-> Storage Object Viewer" role so that the public can see my static files.

Assign an index page

At this point, when I browsed to http://blog.example.com, it just showed an XML document describing the bucket. To get it to serve a default page such as index.html, I went to the storage browser and clicked on the three vertical dots all the way at the right and then clicked "Edit website configuration". That was a little hard to find:

select edit website configuration

There I set the "Main page" to index.html and set "404 (not found) page" to 404.html.

edit website configuration

It works!

I then uploaded a simple "Welcome" html page using the storage browser then navigated to http://blog.example.com to verify that everything was working. Miraculously, it worked on the first try.

welcome

Static site generators

A "Welcome!" page is fine and all, but I wanted something a little fancier than that of course. I could have created a hand-crafted custom static website at this point, but instead chose to use a static site generator to get things going faster.

Just like hosting options, there are many static site generators. See www.staticgen.com for examples. I chose Hugo because I had experimented with it some in the past.

Create a git repo

I wanted to keep my site in a git repo. I've been moving my projects to gitlab.com because I like their CI/CD tools. Initially I did not set up a CI/CD pipeline, but I plan on doing it sometime so I can just push a commit and my changes will automatically publish. Another thing I like about gitlab is they allow unlimited private repos.

Setup Hugo

Getting started with Hugo is pretty easy. I just followed their quick start guide.

# Make a temporary directory to unpack the hugo tarball.
cd $(mktemp -d)
# Download a release.
wget https://github.com/gohugoio/hugo/releases/download/v0.58.2/hugo_0.58.2_Linux-64bit.tar.gz
# Unpack
tar xf hugo_0.58.2_Linux-64bit.tar.gz 
# Move the binary to somewhere that is in my path.
mv hugo ~/bin/
# Clone my empty repo.
cd ~/repos/gitlab.com/me/
git clone git@gitlab.com:me/example.git
cd example/
# Create my Hugo site
hugo new site blog
cd blog
# Add a theme
git submodule add https://github.com/budparr/gohugo-theme-ananke.git themes/ananke
echo 'theme = "ananke"' >> config.toml
# Create my first post.
hugo new posts/it-works.md
# Start the dev server. It live updates as code is edited locally.
hugo server -D

After running those commands, I navigated to http://localhost:1313 and found my shiny new website with a post titled "It Works".

Add content

The config.toml file contains settings such as site title, copyright statement, favicon, language, URL, etc. Each theme is a little different in the customizations that it allows. For the Ananke theme, see this.

I copied the config.toml file from the exampleSite included with the Ananke theme and then made changes:

$ diff config.toml themes/ananke/exampleSite/config.toml

1,2c1,2
< title = "example.com"
< baseURL = "http://example.com"
---
> title = "Notre-Dame de Paris"
> baseURL = "https://example.com"
4c4,5
< theme = "ananke"
---
> theme = "gohugo-theme-ananke"
> themesDir = "../.."
9c10
< Paginate = 20 # this is set low for demonstrating with dummy content. Set to a higher number
---
> Paginate = 3 # this is set low for demonstrating with dummy content. Set to a higher number
20c21
<   description = ""
---
>   description = "The last theme you'll ever need. Maybe."
22c23
<   twitter = ""
---
>   twitter = "https://twitter.com/GoHugoIO"
31c32
<   featured_image = ""
---
>   featured_image = "/images/gohugo-default-sample-hero-image.jpg">

I also added a _index.md file and an image to use as my main page image (hero.jpg) to the content directory:

$ cat content/_index.md

---
title: "My Fancy Blog"
featured_image: "hero.jpg"
description: ""
---

One trick I learned is that you can create directories for each post instead of just a plain markdown file. The directories are nice because it allows keeping related images with the markdown file.

For example, running the following creates content/posts/it-works.md:

hugo new posts/it-works.md

To then turn that into a directory based post:

mkdir content/posts/it-works
mv content/posts/it-works.md content/posts/it-works/index.md

Then I can add image files to content/posts/it-works/ and reference them in index.md. The url of the post will still be http://blog.example.com/posts/it-works/.

Generate

To generate my static files, all I had to do was:

hugo

That was easy. It created static files in ./public/ ready to upload!

Upload

To upload, the easiest method I found was to use gsutil (part of the Google Cloud SDK).

# -m option does parallel uploads
# -R option sync the directory recursively
gsutil -m rsync -R public gs://blog.example.com

Conclusion

Without too much trouble, I was able to make a flexible, performant, and inexpensive website and learned a little about Hugo and Google Cloud Storage along the way.