The need for reproducible deployments
Unlike Node.js, Python, Ruby or most other popular ecosystems, Golang allows you to compile your software into statically linked binaries which means all dependencies are neatly packaged with your source code. You could easily get away with distributing your Go app as a single binary, however things get more complicated when you need to be able to install and track multiple versions and bundle various configuration and data files with it.
You can implement reproducible deployments of Go apps in your organisation using tools like Ansible which would involve installing Go, cloning the repository, building the application and placing the main binary and assets in specific locations. The downside to this approach is that you have to waste a lot of time downloading the source code (big security risk) and building your application on every server it has to be installed.
Use a continuous integration tool
Continuous integration tools are outside the scope of this post but the short version is that they are essential in every development workflow and you can use one even if you do not intend on doing continuous delivery - automatically deploying to production when tests pass.
Apart from alerting you when certain commits break builds they can also be used to build and package your application into artifacts. Most CI tools allow you to specify a sequence of commands to execute when tests pass or perform a more high level deployment such as pushing a Docker image to your Docker Registry or deploying to Google App Engine.
Use your distro’s package manager
If your CI tool zipped the directory with everything it needs to run and uploaded the archive to a central repository with an HTTP server, you could make Ansible download it and extract it on the servers into the correct location. While this approach would be a significant improvement from what was described in Naive approach, over time you would end up making your Ansible playbook very complicated by reinventing basic package manager features such as versioning and dependency management.
If your servers run a Debian or Ubuntu-based distro you can package services or
.deb archives. They are nothing more than a regular archive
you would produce using the naive approach above with a few additional metadata
files which specify the name of your package, version and optionally
dependencies and pre/post installation scripts.
The directory structure of a simple Go service with a YAML configuration file
/etc/foobar/foobar.yaml) and an upstart file (
look like this:
foobar ├── DEBIAN │ ├── conffiles │ └── control ├── etc │ ├── foobar │ │ └── foobar.yaml │ └── init │ └── foobar.conf └── usr └── bin └── foobar
In order to build a .deb package you have to create a directory structure which
corresponds to where parts of this service would go and a special directory
control file is required. Below is an example of a
control you can use as a starting point.
Package: foobar Architecture: amd64 Maintainer: Lee Archer <firstname.lastname@example.org> Priority: optional Version: 0.3.1 Description: Demonstrate how .deb packages work
Most fields are self-explanatory, however you can use this documentation page for more information.
conffiles file contains full paths of all configuration files. In this case it would look like this:
This file is used to track changes in configuration files. If you make a change to one of these files by hand and attempt to install a newer version of this package you will get a few options - keep original, overwrite with the same file in the new version or do a diff between the versions of this file.
Produce a .deb package
You can build the package by running the following command:
dpkg-deb --build foobar
This command assumes that
foobar is the path to the
foobar directory in the example section.
When you build a
.deb package using the command above the filename is simply the name of the directory with
.deb at the end. You can install it using
dpkg like this:
sudo dpkg -i foobar.deb