Deploying Golang services using Debian packages - part 1

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.

Naive approach

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 utilities using .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.

Example

The directory structure of a simple Go service with a YAML configuration file (/etc/foobar/foobar.yaml) and an upstart file (/etc/init/foobar.conf) would 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 called DEBIAN. The control file is required. Below is an example of a simple control you can use as a starting point.

Package: foobar
Architecture: amd64
Maintainer: Lee Archer <lee@connected-ventures.com>
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.

The conffiles file contains full paths of all configuration files. In this case it would look like this:

/etc/foobar/foobar.yaml

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.

Install it

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