Debian Packaging For Golang

I have been craving some novelty in my projects outside of work. Six years ago I remember asking myself how Debian packages were built, but I did nothing about it. Instead of reaching for the comfort of a Docker build I want to take that walk down Debian packaging lane that I avoided so many years ago. Since I have been working with Golang for the last six months let's learn together how to distribute our projects like the linux afficinados of old.

This is the primer I wish I had before taking on this task.

Tooling

Some key tools should be present on your system before you begin.

sudo apt-get install build-essential devscripts debhelper

This command installs:

  • build-essential: A meta-package that pulls in the essential tools for compiling software, such as gcc and make.
  • devscripts: A collection of scripts that are incredibly helpful for Debian package maintainers.
  • debhelper: A suite of tools that simplifies the packaging process by automating common tasks.

It is assumed your Golang dependencies are already met.

Source vs Binary Packages

A source package typically contains the source code of the upstream project in a .tar.gz file, patches made by the package maintainer, then a Makefile file debian/rules is used by the system to compile the code and create a binary package. These source packages cannot be directly installed, but they are highly portable and sometimes required for security auditing.

You are probably more familiar with binary packages. These are pre-compiled packages that are ready to install. They typically consist of the pre-compiled binary, configuration files, man pages, and other necessary data files. The contents are organized into a specific directory structure that a package manager (.e.g. apt) will place on to your file system.

Binary packages need to be compiled for a specific processor architecture (e.g. x86_64 ARM64). Most times these are built for a specific operating system and version too. A benefit to this process is that dev tools do not need to be installed for a user to make use of the package.

This project focuses on building a binary package.

Package Files and Structure

When building a Debian package you must create a DEBIAN/ directory which in a typical build process will contain a number of essential files.

  • DEBIAN/control: Contains essential metadata about the source package and the binary package(s) it will create. This includes the package name, version, dependencies, maintainer information, and a description of the software
  • DEBIAN/rules: This is an executable Makefile that automates the building of the package. It has targets for compiling the software (build), installing it into a temporary directory (install), and creating the final .deb archive(binary). Modern rules files are often very simple, relying on debhelper to do the heavy lifting
  • DEBIAN/changelog: This file documents the changes made to the Debian package, not the upstream software. It follows a strict format and is used by the build tools to determine the package version.
  • DEBIAN/copyright: This file specifies the copyright and license of the software. It's a legal requirement and should accurately reflect the licensing terms of the upstream source code and any modifications made for the Debian package.
  • DEBIAN/compat: This file specifies the debhelper compatibility level, which ensures that the build process is consistent even as debhelper evolves.
  • DEBIAN/source/format: This file indicates the source package format (e.g., 3.0 (quilt))

A binary package can be constructed for a Go project with as little as a control and changelog file. Since this primer is focused on a minimal packaging process for your own golang project, we will focus on the control file and the changelog.

The files and binaries we wish to ship in our package will simply have a directory hierarchy that mirrors where these files should be installed on the standard Linux filesystem. So if you want to install your project under /opt/local/bin/foo you would create a directory structure next to the DEBIAN/ that reflects this.

./DEBIAN/
./opt/local/bin/foo

This goes for configuration and data files you intend to ship too.

Control File

The DBIU/control file defines a package's metadata and build-time parameters. Refer to https://www.debian.org/doc/debian-policy/ch-controlfields.html for indepth descriptions of all the available fields.

The most minimal file can contain:

Package: YOUR_PACKAGE_NAME
Version: YOUR_DEBIAN_PACKAGE_VERSION
Architecture: YOUR_TARGET_PROCESSOR_ARCHITECTURE
Maintainer: YOUR_EMAIL(Ex: Your Name <youremail@example.com>)
Description: YOUR_PACAKGE_DESCRIPTION

Changelog

The change log is not a change log of the upstream, it is a changelog of the Debian package. This file has a specific format that needs to be adhered to, or the build will fail.

A DEBIAN/changelog file is a plain text file with a series of entries, where the newest entry is always at the very top. Each entry has a precise format:

package-name (version) distribution(s); urgency=level

    * Change details for the first change in this version.
    (This can wrap to multiple lines if indented).

    * Change details for the second change. This could be a bug fix.
    (Closes: #bug-number)

    -- Maintainer Name <maintainer@example.com>  Day, dd Mon yyyy hh:mm:ss +zzzz

The devscripts package installed earlier has the dch(debchange) tool that can assist in automating the change log changes, and ensure that they correctly formatted.

Debian Versioning Scheme

[epoch:]upstream_version[-debian_revision]

  • epoch: small, rarely used integer that helps correct mistakes in past versioning by ensuring a new version is seen as "later" than an old one
  • upstream_version: The main version number from the original software developer, which can contain alphanumeric characters and symbols like periods, hyphens, and colons
  • debian_revision is appended by the package maintainer to signify changes to the packaging itself, such as updates to dependencies or build scripts, without any change to the upstream software

When assessing versions dpkg's algorithm evaluates the string from left to right, with special rules for characters like the tilde (~), which indicates a pre-release version and sorts before a final release of the same number.

A Manual Packaging Example

With the basics out of the way, let's assume we have a foo v0.1.0 program that we would like to offer as a debian package.

go build -o ./build/foo .

Create the required debian package structure.

mkdir -p ./dist/foo_0.1.0-1/DEBIAN
mkdir -p ./dist/foo_0.1.0-1/usr/local/bin

Copy the binary to the usr/local/bin/ directory under our package namespace and ensure it's executable.

cp ./build/foo ./dist/foo_0.1.0-1/usr/local/bin/foo
chmod +x ./dist/foo_0.1.0-1/usr/local/bin/foo

Create a control file under the DEBIAN/ directory

touch ./dist/foo_0.1.0-1/DEBIAN/control

And using a text editor add:

Package: foo
Version: 0.1.0-1
Architecture: amd64
Maintainer: Your Name <youremail@example.com>
Description: foo - A Go Application

Now create a changelog file under the DEBIAN/ directory.

touch ./dist/foo_0.1.0-1/DEBIAN/changelog

We will use the debchange tool dch to create the first entry in our file. Use man dch to learn more about all the options available to you for editing these entries.

dch --changelog ./dist/foo_0.1.0-1/DEBIAN/changelog --create

Since we did not set envvars for things like our package or maintainer email you will see warnings. Press enter to continue and you will drop into an text editor with a basic template.

output:

PACKAGE (VERSION) UNRELEASED; urgency=medium

  * Initial release. (Closes: #XXXXXX)

 -- toor <toor@toor-runcible>  Thu, 26 Jun 2025 14:28:27 -0400

Change the contents of this entry as needed and save.

foo (0.1.0-1) unstable; urgency=low

  * Initial release.

 -- me <me@example.com>  Thu, 26 Jun 2025 14:28:27 -0400

Now we can build the package with dpkg-deb. Again see man dpkg-deb for more information.

cd ./dist
dpkg-deb --build foo_0.1.0-1/

pkg-deb: building package 'foo' in 'foo_0.1.0-1.deb'.

You should now have successfully built a foo_0.1.0-1.deb in your ./dist/ directory.

You can view the contents of this pacakge

dpkg-deb --contents ./foo_0.1.0-1.deb

drwxrwxr-x toor/toor         0 2025-06-22 15:44 ./
drwxrwxr-x toor/toor         0 2025-06-22 15:44 ./usr/
drwxrwxr-x toor/toor         0 2025-06-22 15:44 ./usr/local/
drwxrwxr-x toor/toor         0 2025-06-22 15:44 ./usr/local/bin/
-rwxrwxr-x toor/toor   3433713 2025-06-22 15:44 ./usr/local/bin/foo

You can test the installation of this new package

sudo dpkg -i ./foo_0.1.0-1.deb

Selecting previously unselected package dirp.
(Reading database ... 217265 files and directories currently installed.)
Preparing to unpack foo_0.1.0-1.deb ...
Unpacking foo (0.1.0-1) ...
Setting up foo (0.1.0-1) 

We can check that a the binary is now in our /usr/local/bin

which foo

/usr/local/bin/foo

We can see which version of the package is installed

dpkg -l foo

Desired=Unknown/Install/Remove/Purge/Hold
| Status=Not/Inst/Conf-files/Unpacked/halF-conf/Half-inst/trig-aWait/Trig-pend
|/ Err?=(none)/Reinst-required (Status,Err: uppercase=bad)
||/ Name           Version      Architecture Description
+++-==============-============-============-=================================
ii  foo            0.1.0-1      amd64        foo - A Go application

And we can remove it.

sudo dpkg -r foo

(Reading database ... 217268 files and directories currently installed.)
Removing foo (0.1.0-1) ...
dpkg: warning: while removing dirp, directory '/usr/local/bin' not empty so not removed

We can disregard the warning because the package manager is doing what its supposed to. It removed the /usr/local/bin/foo and attempts to clean up all directories it may have created in the process. Once it finds existing files in the directory structure it was avoid deleting them.

Where To Go From Here

This article barely scratches the surface on Debian packaging. You technically have a package that meets all of Debian's requirements. Maybe you are stasfied with that, and call it a day. If not the best source for your next steps down this path are to read the official Debian Packaging Guide. In it you will also find the official guidance on building Go packages for Debian and be introduced to tools like dh-make-golang which can take most of the toil out of packaging non-trival go projects with deep dependencies.

Thanks for the attention and good luck!

tags: golang, packaging