A coworker was wondering about serving static content in Google Cloud's App Engine. I have done a few projects in App Engine with static content, so I went back and did a little research to refresh my memory.

Google Cloud Storage

The ideal way to serve static content would be through Google's own Storage service—roughly equivalent to AWS S3.

There's even a nice, straightforward Google Cloud tutorial about hosting static content. The tutorial includes some information about setting up a CNAME in your DNS to point to your Storage bucket, but then we find this caveat:

Caution: You can only use a CNAME redirect with HTTP and not with HTTPS, because SSL is not currently supported by the Cloud Storage webservers. We recommend that you don't serve content that contains sensitive or private data from your CNAME aliased bucket.

I'm probably missing something, but this is too bad. Anyway, we can cross off the "easy" way to serve static content in Google Cloud.

Other Options

This tutorial also looks promising, but as of 3 January 2017 is marked with a big "alpha: not recommended for production use".

The standard tutorial for setting up HTTP(S) load balancing is non-trivial at the moment.

The nginx-based SSL terminated load balancer tutorial is a little simpler, but still a lot of work if all you're trying to do is protect your traffic. If you need your own domain name, you'll have to do this, but otherwise, read on.

We're going to consider another option: App Engine. It may not be the most cost effective for large content sites, but is really easy to set up for small sites just needing to serve content over HTTPS.

App Engine

In my opinion, Google Cloud App Engine is the most elegant way to serve HTTP-based applications. It works like AWS Elastic Beanstalk, but it includes an SSL terminated load-balancer, autoscaling and a dead-simple configuration file to manage it.

You can also treat it like a plain old Docker host, serving any public or private Docker image you like. We're going to take advantage of that and use nginx to serve our static content.

Here is the image we'll be using: the official nginx image from Docker. There's nothing fancy about this image. Its Dockerfile is based on Debian Jessie; we then apt-get nginx, expose ports 80 and 443, and finally tie off access_log and error_log to /dev/stdout and /dev/stderr respectively.

The problem is that Google's App Engine requires that your application listens on port 8080, which this stock nginx Docker image does not.

To address this, we'll override with our own Dockerfile. Put this in the root of your application's directory:

FROM nginx

COPY nginx.conf /etc/nginx/nginx.conf
COPY site /usr/share/nginx/html

EXPOSE 8080
CMD ["nginx", "-g", "daemon off;"]

Here's our nginx.conf. This is where we listen to TCP port 8080 for App Engine's sake. We also set the location root to the path we specified in the Dockerfile above (into which Docker will copy our site directory during docker build):

worker_processes 1;

events {
    worker_connections 1024;
}

http {
    include /etc/nginx/mime.types;
    default_type application/octet-stream;

    server {
        listen 8080;

        location / {
            root   /usr/share/nginx/html;
            index  index.html;
        }
    }
}

The site directory itself is whatever you want it to be. Here's mine:

site
├── images
│   └── image.png
└── index.html

and finally, we'll need an app.yaml:

runtime: custom
env: flex
threadsafe: true
service: nginx-test

To deploy this, we use gcloud which you can download here. Here's how I deploy:

$ gcloud --project=my-project app deploy app.yaml

The gcloud utility performs a docker build for you, uploading the contents of the working directory to their build server. Once the docker image is built, it will use that to create two instances of your application (you can configure that in your app.yaml file), both of which are placed behind an Google-supplied SSL-terminated load balancer (also nginx, ironically).

Wait a few minutes and you'll have your very own SSL-terminated static website; gcloud will let you know the URL; in this case it would be https://nginx-test-dot-my-project.appspot.com.

You can set your resources (CPU, memory, etc.) and scaling options in app.yaml too. Here is the documentation for further tweaking.