Serve small with Fly.io and GoStatic

Static websites are great for carrying unchanging content, be it assets, images, fonts or even, as in this case, an entire site. Well, I say entire site, but if you saw my last article, you’ll know I recently rebranded a Maker organization and needed to deploy a “signpost” page pointing people to the new site. I want this signpost to have a tiny footprint so it will never cost anything to deploy.

Now, the thing with Docker images is that you don’t really notice the layers of OS and applications that pile up in the background. Something as notionally simple as say running Apache HTTPD will still need an OS layer under it, no matter how minimal, you put the two parts together and the image size soon builds up. And you still have to add the content.

This is where something like GoStatic comes in. It’s a small, self-contained web page server which can run in a bare Docker image - no OS, just the binary. As the author points out, the official Golang images can weigh in with as much as half a gigabyte of image. For GoStatic, the image is an unchunky 6MB.

Go GoStatic

So, how do you make use of GoStatic on Fly? Let’s step though it now.

One thing you need to know is that by default GoStatic uses port 8043. So add -p 8043 to your fly init command when you create your project. That’ll route traffic to port 80 and 443 to port 8043 on the application.

❯ mkdir examplegostatic
❯ cd examplegostatic
❯ fly init examplegostatic -p 8043
Selected App Name: examplegostatic

? Select organization: Dj (dj)

? Select builder: Dockerfile
(Create an example Dockerfile)

New app created
Name = examplegostatic
Owner = dj
Version = 0
Status =
Hostname = <empty>

Wrote config file fly.toml

We already have an index.html we want to serve, so our next stop is the Dockerfile. Delete the example contents and replace it with just two lines.

FROM pierrezemb/gostatic

COPY index.html /srv/http/index.html

And we are ready to deploy! Just run flyctl deploy:

Deploying examplegostatic
==> Validating App Configuration
--> Validating App Configuration done
Services
TCP 80/443 ⇢ 8043

Deploy source directory '/Users/dj/examplegostatic'
Docker daemon available, performing local build...
==> Building with Dockerfile
Using Dockerfile: /Users/dj/examplegostatic/Dockerfile
Step 1/2 : FROM pierrezemb/gostatic
 ---> 4569615e9ed0
Step 2/2 : COPY index.html /srv/http/index.html
 ---> b0b723d0cb24
Successfully built b0b723d0cb24
Successfully tagged registry.fly.io/examplegostatic:deployment-1595848012
--> Building with Dockerfile done
Image: registry.fly.io/examplegostatic:deployment-1595848012
Image size: 7.7 MB
==> Pushing Image
The push refers to repository [registry.fly.io/examplegostatic]
77bf40a52322: Pushed
3530b7ebed24: Pushed
deployment-1595848012: digest: sha256:d60567799841a4480e410acef113d33c1156eb960b94ae591931801089f61b1a size: 735
--> Done Pushing Image
==> Optimizing Image
--> Done Optimizing Image
==> Creating Release
Release v0 created
Deploying to : examplegostatic.fly.dev

Monitoring Deployment
You can detach the terminal anytime without stopping the deployment

1 desired, 1 placed, 1 healthy, 0 unhealthy [health checks: 1 total, 1 passing]
--> v0 deployed successfully

Once deployed, all you need to do then is flyctl open and a browser will open and navigate to the site.

Our served page

You’ll also notice that this has been upgraded to an https connection. All that is left is to attach a custom domain to it and we’re done.

Behind the scenes

So, what magic is going on here? Well, the Dockerfile in GoStatic explains a lot of it.

It uses a Docker multistage build to build our server binary in the first stage. Then it starts a new stage from scratch, literally using the command FROM SCRATCH. This says that there is no base image, just start building on top of nothing, an empty image. The rest of the GoStatic Dockerfile creates a passwd file so there are some usernames to work with and copies over the GoStatic binary.

And then it all hands over to our own Dockerfile. GoStatic serves files out of /srv/http so we copy over our index.html to that directory. And that’s it. Everything else is managed by Fly, the build, the deployment and the upgrading to an https connection. There used to be a version of GoStatic which would handle HTTPS connections and certificates, but that functionality has been retired now servers like Caddy exist. On Fly, the lack of HTTPS support means it’s simple to just let Fly take on the HTTPS work for you.

A Small Squeeze

One last tip. Fly deploys new applications with 512MB of RAM and about a quarter of a virtual CPU. It’s called a micro-2x firecracker VM. But, we’re doing so little here, we could scale the VM size down. Let’s look at the scale settings:

~/examplegostatic
❯ flyctl scale show
     Scale Mode: Standard
      Min Count: 0
      Max Count: 10
        VM Size: micro-2x

And now we can set the vm size:

~/examplegostatic
❯ flyctl scale vm micro-1x
Scaled VM size to micro-1x
      CPU Cores: 0.12
         Memory: 128 MB
  Price (Month): $2.670000
 Price (Second): $0.000001

If you want to find out about the other VM sizes, run flyctl platform vm-sizes.

Wrapping up

We’ve got ourselves a tiny static web server and deployed it to Fly.io, and as a bonus, shrunk the VM’s footprint to match it and save running costs. Enjoy!