Triple-Stage Docker Builds with Go and Angular
In a previous post I talked about multi-stage builds for creating small images from a single Dockerfile. In this post, we’ll take it a step further by doing 2 build steps, one for a Go backend API, another to compile an Angular application, plus a final step to extract the build artifacts and put them in a final, minimal image.
It also takes advantage of Docker caching so it will only rebuild layers when there are changes, for instance, it won’t re-fetch dependencies unless you change your dependencies and it won’t compile your code unless it’s been changed.
I won’t post all the code here, just the Dockerfile, but it should work with any project that has a Go API and an Angular frontend that talks to the API. Which is a great combo BTW!
Prerequisites:
- This uses the new Go dep tool which shouldn’t be too hard to add to your project, just follow the Setup instructions.
- Your API should serve static files from
../ui/dist
, eg: http.ServeFile(w, r, “../ui/dist/index.html”) - You should be using Angular CLI.
- And of course you must have Docker installed.
That’s about it.
The Dockerfile
Here’s the full Dockerfile, you can copy it into your Dockerfile
and modify as you see fit:
1) The first stage builds the Go API. It first fetches the Go dependencies using the dep tool, then builds the final binary. The dependency part goes first so that it can use Docker’s cache to make subsequent builds run faster.
2) The next stage builds the Angular application in the same fashion: first get dependencies then build the final Javascript code to use in production.
3) The final stage takes the Go binary and the compiled Angular application and stuffs them into a small alpine image that doesn’t contain any of the Go tools nor the node, npm and angular tools required to build the Angular app.
Usage
The one thing you’ll have to change for sure is the following line:
ENV D=/go/src/github.com/USERNAME/REPONAME
Change it the correct path for your Go project.
And you’ll probably have to modify your Go code a bit to serve up files from the ../ui/dist/
directory or modify the Dockerfile a bit to match your code, but this should give you a good idea of how to make this work.
Then it’s just a simple:
docker build -t USERNAME/REPONAME .
Boom.
Conclusion
Multi-stage Docker builds have really made things a lot nicer. Previously if you wanted a small image, you used to have to write your own multi-stage scripts, mount drives, and copy output between steps from your local file system into a final image. Now it’s all wrapped up into a single Dockerfile with a single docker build
command. Nice and clean.