Simon's Blog

Dev vs Ops - Go embed

March 21, 2021

Introduction

In my latest article on my game Cabin Fever. I document how I use Docker to compile and bundle all of the code into a single, deployable package.

With Go 1.16, embedding files has become a first class citizen.

This potentially means that, rather than relying on Docker to keep my binary and the generated CSS and JS files together, I can embed these files directly in the Go binary and ship it out as it is, without the Docker wrapping around it.

This is made possible due to Go’s efficient cross-compilation and the ability to put everything into a single binary, so your mileage may vary with other programming languages.

Using Go embed

To be able to use Go embed, I need to ensure that both my development machine and my CI machine are operating with go version 1.16. At the time of writing, my development machine is on Ubuntu 20.04.2 LTS and the latest Go version available for me is go 1.15.8. Using the instructions available here I was able to install a backport without any hassle.

Switching to Go embed

Working off of the following blogpost, it was very simple to switch to Go embed. I did not include the author’s solution to switch between the normal filesystem and the embedded one depending on the environment at this time, since the hot reload provided by modd already recompiles the binary from scratch.

My next issue was that my go.mod file was still using Go 1.13 and was unable to understand the embed directive. This was quickly solved by updating the version to 1.16.

After resolving a minor issue where the latest golang Docker image was not the 1.16 image (quickly resolved by performing docker pull golang), it looked like the embedding was not working. If I changed index.html after compilation, it was still returning the latest version, meaning that the binary was still retrieving it from my machine’s filesystem rather than the embedded one.

This was resolved by re-reviewing how I served the index.html. It turns out I had written code to read a file from the OS filesystem and had not taken the new embedded file system into account.

Whilst I could have resolved this by directing it to the new embedded filesystem, I decided to use Go embed again, but instead of embedding the file as a filesystem, I used the option to embed the contents of the file as a string, which achieved the same result.

Switching to Go embed actually made the resulting Docker image smaller, reducing it from 10.5MB to 9.27MB. Not a staggeringly large difference, but a difference nonetheless.

Technically I meant to do away with the Docker wrapping completely - however I can still use the Dockerfile as a compilation mechanism that is isolated from the machine I am running it on. According to the following StackOverflow link I can run docker create and docker cp to extract the binary from the Docker image that is created.

Takeaway

This article shows how, despite the latest and greatest Ops tools (such as Docker) technically mean that DevOps practitioners can opt to not care about whatever the code is written in, if we combine Dev knowledge of the underlying code being packaged and deployed we can simplify and streamline the DevOps process even further.


Written by Simon who lives in Malta. You can find out more about me on the about page, or get in contact.