Guix: creating a package from a binary
I'm currently migrating my entire homelab to Guix. While Guix offers a Nginx service out of the box, I prefer using Caddy. The built-in automatic TLS is a nice quality of life feature. In my experience, it is seamless and just works™.
As part of this ongoing migration, I created a dedicated Caddy service which I'll go over in detail in a future post. Everything went well until I realised that Caddy isn't packaged in Guix yet.
I looked at packaging it myself, however packaging Caddy from source requires first packaging a dozen go sub-dependencies. Thankfully, a group of volunteers is currently working on it - it isn't ready yet, but hopefully, soon.
In the meantime, I decided to circumvent the issue by simply packaging the binary. This is quite simple in Guix, and a nice first foray into 'packaging' for me.
Most of what I am about to explain below is paraphrasing the documentation; I highly encourage you to give it a read.
Defining a simple binary package
A package is defined by creating <package> record, as defined in guix/packages.scm.
A <package> record contains the information required by Guix to create a reproducible package.
It contains the metadata of the package, such as its name and description, as well as where to get the source assets and how to build it.
Package information: what are we packaging?
Let's start with the simple metadata fields: name, version, synopsis, description, license, home-page. Those are mostly informational and some of those optional.
In our case, these aren't very important as we will not distribute the package. Some of those fields are used to generate the documentation when searching for package.
If you intend to distribute a package, I would encourage you to be a bit more descriptive than me!
(define caddy
(package
(name "caddy")
(version "2.11.4")
(home-page "https://caddyserver.com")
(synopsis "Web server with automatic HTTPS")
(description "Caddy is an extensible web server with automatic TLS.")
(license asl2)))
With this easy task out the way, we only need to address two more parts: where to get the binary from, and how to "build" it.
Source: where to get the source artifacts?
In this case, for simplicity's sake, we are packaging the binary instead of building Caddy from source.
To do so, we use the source field which contains an <origin> record.
The <origin> record is described in detail in this section of the documentation.
Our case is very simple: we fetch the binary.
I am using the GitHub release page to get the binary, and the url-fetch method.
(source (origin
(method url-fetch)
(uri (string-append
"https://github.com/caddyserver/caddy/releases/download/v"
version "/caddy_" version "_linux_amd64.tar.gz"))
(sha256
(base32 "1fbvxj6mifdqhwm5s1f8snr805k0anllzlri7cg9l61rgj8vyzsj")))
Two things to note here:
- I'm constructing the URL based on the version. This is optional.
- I hard coded the target platform,
amd64. You might need to adapt it, or better, use a "system" variable like I have done for the version.
SHA 256 hash
The hash in the sha256 field is here to verify the integrity of the content.
The idea is simple: the guix daemon create a hash based on the content of the fetched resource.
If the content changes, the daemon will flag the hash as not matching, and refuse to build the package until we validate the new hash.
This protects us from content changing without us knowing it.
To obtain the initial hash, you can use [[https://guix.gnu.org/manual/1.5.0/en/html_node/Invoking-guix-download.html][guix download]]:
guix download "https://github.com/caddyserver/caddy/releases/download/v2.11.4/caddy_2.11.4_linux_amd64.tar.gz"
This will print:
Starting download of /tmp/guix-file.zQdNRJ
From https://github.com/caddyserver/caddy/releases/download/v2.11.4/caddy_2.11.4_linux_amd64.tar.gz...
following redirection to `https://release-assets.githubusercontent.com/github-production-release-asset/29207621/c72e489c-84b8-406e-831f-51de46756914?sp=r&sv=2018-11-09&sr=b&spr=https&se=2026-07-13T01%3A29%3A05Z&rscd=attachment%3B+filename%3Dcaddy_2.11.4_linux_amd64.tar.gz&rsct=application%2Foctet-stream&skoid=96c2d410-5711-43a1-aedd-ab1947aa7ab0&sktid=398a6654-997b-47e9-b12b-9515b896b4de&skt=2026-07-13T00%3A28%3A22Z&ske=2026-07-13T01%3A29%3A05Z&sks=b&skv=2018-11-09&sig=dW0nPJYRzfIGAMH8c7Knsbx3qSpePvvszb1%2BbqMi9KI%3D&jwt=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmVsZWFzZS1hc3NldHMuZ2l0aHVidXNlcmNvbnRlbnQuY29tIiwia2V5Ijoia2V5MSIsImV4cCI6MTc4MzkwNTA5MywibmJmIjoxNzgzOTAzMjkzLCJwYXRoIjoicmVsZWFzZWFzc2V0cHJvZHVjdGlvbi5ibG9iLmNvcmUud2luZG93cy5uZXQifQ.rIGwFUu9wYkziiMvOrw2ane1zlC-CnLgu1jrdC6HEZ8&response-content-disposition=attachment%3B%20filename%3Dcaddy_2.11.4_linux_amd64.tar.gz&response-content-type=application%2Foctet-stream'...
…nux_amd64.tar.gz 16.4MiB 23.2MiB/s 00:01 ▕██████████████████▏ 100.0%
/gnu/store/fydib5lca6gbvrx57n8hj67gy0kh6ngy-caddy_2.11.4_linux_amd64.tar.gz
1fbvxj6mifdqhwm5s1f8snr805k0anllzlri7cg9l61rgj8vyzsj
The last line (1fbvxj6mifdqhwm5s1f8snr805k0anllzlri7cg9l61rgj8vyzsj) is the hash of this resource, based on the content downloaded.
Note that when we rebuild the package, we won't re-download it. guix download keeps a copy in the store, similar to how a cache works.
Build system: how do we build the package?
We now have the metadata and the source. The last missing piece is the building instructions.
Guix offers out of the box multiple build systems for different use cases.
For example, the gnu build system is designed for projects following the GNU coding standard.
You will find many others build systems such as node, zig or golang.
Each build system exposes arguments to customise the build process to your need.
The <package> record can also include inputs, which specifies the build-time or run-time dependencies of the package.
Our case is simple, there are no dependencies as the package is already built. We simply need to copy the executable to the right location.
Guix has a build system for this, the copy-build-system, which expose only one argument: install-plan.
The install-plan argument is used to instruct where to copy files.
In our use case, we simply want to copy the binary into the packages' /bin directory.
(build-system copy-build-system)
(arguments
(list #:install-plan #~'(("caddy" "bin/"))))
Note that the binary isn't installed in the user or root binary folder.
It will be installed in the store, at /gnu/store/...-caddy-2.11.4/bin/caddy
To make it available to your user, you will need to add it as a user package, a system package or within a service.
In this case, I intend to use it for my caddy-service which I'll go over in a future post.
Conclusion
Our final package now looks like this.
(define caddy
(package
(name "caddy")
(version "2.11.4")
(source (origin
(method url-fetch)
(uri (string-append
"https://github.com/caddyserver/caddy/releases/download/v"
version "/caddy_" version "_linux_amd64.tar.gz"))
(sha256
(base32 "1fbvxj6mifdqhwm5s1f8snr805k0anllzlri7cg9l61rgj8vyzsj"))))
(build-system copy-build-system)
(arguments
(list #:install-plan #~'(("caddy" "bin/"))))
(home-page "https://caddyserver.com")
(synopsis "Web server with automatic HTTPS")
(description "Caddy is an extensible web server with automatic TLS.")
(license asl2)))
Declaring a binary package in Guix in a pinch is very easy and a great introduction into the world of guix packaging.
This is quite a handy trick to have in your toolbox; I am definitely happy that I'm now able to do so. It was a good learning exercise, and much easier than I thought.
I will be following up soon with a longer post about creating a caddy service from scratch. The service will be in charge of administrating the caddy file and setting up the shepherd service to run the caddy daemon with a dedicated user.