Building a Development Environment for Golang with Docker

14,752 reads

With a focus on developer happiness

A whale and a Gopher go to the bar

I began my journey in Golang a few months ago when I decided that JavaScript was getting a little too unwieldy for my liking. TypeScript solved some problems but it made JavaScript as verbose as Java. Anyhow, I have been writing JavaScript for close to half a decade and I thought it’d be nice to learn a different language with a different mental model and development paradigms.

Starting out, my biggest issues I had with Golang was the lack of tooling and the by-now infamous GOPATH requirement. The latter is a matter of taste by Google’s engineers, but something could be done about the former.

I had been searching for tools that would allow me to replicate the ease of which I could run my Golang services in development, as I could my Node.js based applications, and I found none that fit my needs, and so I decided to create one.

It runs using Docker which solves environment and version management issues, and uses Makefiles for tooling, and it comes in the form of an image that basically contains simple shell scripts which assists in:

Bootstrapping a project Live-reloading of tests and applications Auto-updating of dependencies Statically linked binary compilation Packaging into a scratch Docker image

I call it Go Develop and you can find it at my GitHub repository at:

What follows is a short post on what I went through, and how this project solves the problems I faced while initiating myself into the world of Golang.

For reference as to the environment I’m trying to replicate, I use the following tools with JavaScript to improve my productivity:

NVM for managing of Node versions Yarn for managing dependencies Nodemon for live-reloading of my application and tests ESLint + Prettier for automatic code formatting and live-linting Docker multi-stage builds for packaging and deploying applications

Boostrapping a Go Project

When it comes to bootstrapping any project, it usually comes down to a few things:

A task runner — After going through the source codes of some projects in Go, it seemed that Makefiles were the generally accepted way to go. A dependency manager — Prior to Go 1.11, there was Glide, Dep and Godep. Go 1.11 introduced go mod which seems to be the way to go. A way to manage versions — It seems like the generally accepted way to do this is via Git tags. Since go mod is kind of new, I stuck with using Git tags. A way to release into production — The Go applications I’ve dealt with are generally cloud native, and packaging is usually done via Docker containers.

The recommended way to use Go Develop is hence via a Makefile, with an init script that provisions a directory for development and release. It also includes scripts to bump your Git tags in a semver-compliant manner.

Managing of Go Versions

In almost every modern language there’s always the customary xVM (where x is the first letter of the language/runtime one is using). Ruby has RVM, Node has NVM, and Go has GVM. However, GVM modifies your GOPATH . While I didn’t mind this at first, I soon encountered issues when working with different projects using the linkthis sub-command of GVM. That’s when I began researching on the issue, and… It’s apparently an ongoing gripe of many:

Using Docker, we won’t need to even install Golang on your machine. Simply reference the version of Golang we wish to have and get started! (Go Develop begins at 1.11.2, if earlier 1.11.x versions are needed, I’ll add support for them — drop me an Issue on GitHub).

As a nice side-effect, this also means you no longer need to configure GOPATH on your local machine as long as it’s properly defined in the image.

Auto-Formatting of Code

Like many who work on web-related software, I call Visual Studio Code my choice of IDE, and it already had a surprisingly decent plugin, just search for “go” in your marketplace!

The Intellisense works wonders when the Go Language Server is turned on and the auto-formatting by gofmt gets the job done pretty well. I actually found it better than the ESLint + Prettier combination — but that’s possibly because Go itself enforces the formatting before it’ll compile.

Live Reloading of Application

After using nodemon , it’s hard to go back to manual reloads. The Golang community has it’s own offerings such as Realize, which I went with for awhile in my professional work. However, it couldn’t run tests together with the main service, which was cumbersome if you’d like tests to be running while you write code and break things. I still had to run go test manually.

Then go mod came about in 1.11 and it broke Realize. The live-reload wouldn’t even kick in once there was a go.mod file. The issue is still ongoing (and it doesn’t happen just for Windows):

In Go Develop, I used inotifywait to watch for all *.go files and do a build/update deps/kill/run cycle on file changes. However, this meant that it wouldn’t run on OS X or Windows, but since Docker abstracts away the operating system layer, this is a non-issue, and which is why running Go applications from a container made sense for me.

Live Reloading of Tests

Existing toolings didn’t do justice to live-reloading of tests either. GoConvey sounded great — except I didn’t want to have to switch to a browser to view my test results. I’m more of a CLI person. GoConvey had a CLI mode for auto-reloading of tests, however, failures would not dump the error logs to the terminal which was annoying to me.

Go Develop uses the same inotifywait mechanism to run tests as it does with the application. More info can be found in the documentation.

Auto-Updating of Dependencies

Another issue I had with other live-reload tools was that after I did an import on something new, I had to switch my terminal and install the new package separately.

While this wasn’t possible in the past because different projects used different dependency managers, the release of Go 1.11 saw the official inclusion of go mod which seems to be the way to go (ha-ha).

With Go Develop, the use of go mod is enforced which simplifies issues that other dependency managers face such as version selection. go mod also includes backward compatibility with other dependency managers which convinced me it was the choice to go with.

Binary Compilation and Production Packaging

Most applications written in Go seem to be destined for packaging as a Docker image. In my line of work, I had to do the same. So why not build something that includes a build mechanism with batteries included?

Go Develop was written with the above in mind. Running the build script inside the image results in a statically linked binary that can used in a scratch Docker image (a Docker image without a specified operating system), which reduces the size of the image dramatically.

If static linking and containerisation isn’t your cup of tea, Go Develop also allows you to run builds for your own operating system by providing the GOOS and GOARCH environment variables. Check out the documentation on how to do this.

Last Words

I hope this project benefits others as it has myself — it’s licensed with the permissive MIT license so you can do pretty much whatever you want with it.

If it did help you in some way, do me a favour and star/watch the repository to indicate that you’ve found it beneficial in your developer journey. Feedback is very much welcome too (I’m still a newbie in Go and any improvements to the tooling I’ve done would help!)

You can find the Docker image on DockerHub at:

Thanks for reading!

Lastly, my team at work is expanding and if you’re based in Singapore and would like to work with me professionally, feel free to hit me up at joseph_goh@tech.gov.sg (:

Cheers 😎

Tags