Docker image for using Guix in a CI/CD environment.
The project is designed in the following way:
-
Guix runs deamonless: The container has an entry point that allows you to run commands per instance of the command, the daemon is shutdown for each command or group of commands executed. You can group them by passing a script and executing it or running the image interactively and pass multiple commands.
-
Versions are fixed: We provide a fixed channel commit into
https://codeberg.org/guixrepository, if you want to patch thechannels.scmyou can find them on/root/.config/guix/channels.scm. By default if you doguix pullit will always pull to the same commit and not to the latest. -
Incrementally builds and tarballs for all architectures: In order to avoid CI slowness, we build and cache incrementally our own versions of Guix, snapshots are done periodically to the upstream repository in master branch. They are tagged in the following way:
v20260106for 6th of January of 2026. The binaries are pushed into releases that later on the CI reuses for building the next version so builds are cached in each iteration. -
Default list of substitutes: In order to improve performance, we provide a long list of substitutes a part from the official servers. You can modify them, check the substitute section for doing so.
-
Report errors in-place: If your command fails, we copy all the
*.drv.gzfiles into the current directory, we unpack them and then we show the contents. By default the working directory of the image is root/, so all contents will be put there in case of error. In addition to this, all commands are always traced and printed to stdout withset -exuo pipefail, so it can be easily debugged later on and errors are not silently skipped.
All these design decisions are taken because we focus only on providing CI/CD environment. If you want to use this as a normal end user, take it carefully. You can still modify those behaviors easily but those assumptions are taken due to nature of CI/CD itself. In the future we may make it more customizable for end users.
The main objective of this repository is to have a tool for producing ultra portable, reproducible and cross-platform builds for production ready applications that can be installed from a shell script or uncompressing them into root.
This image encapsulates the Guix daemon. You can view the image details and tags in DockerHub. For now, Guix does not have a daemonless option, so packaging it into a Docker image has some implications. The Guix daemon needs to fork, and forking a process during build phase is not allowed, so we have to work with it in a different way. There are two options:
-
Running the build with Docker, using the
--privilegedflag and commiting the result on each step. For example, imagine we have the followingDockerfile:FROM metacall/guix:latest AS example # Copy some dependencies COPY . .Now we can build the image
metacall/examplewith docker run + commit:# Build the base image docker build -t metacall/example -f Dockerfile . # Run a guix pull docker run --privileged --name tmp metacall/example sh -c 'guix pull' # Commit changes docker commit tmp metacall/new-image && docker rm -f tmp # Install some package docker run --privileged --name tmp metacall/example sh -c 'guix package -i guile' # Commit changes docker commit tmp metacall/example && docker rm -f tmp # Push the final image docker push metacall/example
A complete working example used in production can be found here: https://github.com/metacall/distributable-linux
-
Running the build with BuildKit using the buildx extension for Docker (like how it is done in this repository:
). BuildKit allows to pass extra arguments to theLine 73 in e9a0e79
RUNcommand in the Dockerfile. With the--security=insecureflag we can allow Docker to fork while it is building. For supporting insecure builds, you have to use any docker syntax extension that usesexperimentalorlabssuffix, like# syntax=docker/dockerfile:1.1-experimentalor# syntax=docker/dockerfile:1.4-labs, because this feature is not standardized yet. The previous example can be transformed into this:# syntax=docker/dockerfile:1.4-labs FROM metacall/guix:latest AS example # Copy some dependencies COPY . . # Run guix pull and install dependencies RUN --security=insecure sh -c '/entry-point.sh guix pull' \ && sh -c '/entry-point.sh guix package -i guile'For building this image we need Docker
v19.03or superior and the buildx plugin:# Install the buildx plugin docker build --platform=local -o . git://github.com/docker/buildx mkdir -p ~/.docker/cli-plugins/ mv buildx ~/.docker/cli-plugins/docker-buildx
If you have it already installed, we need to create an insecure builder (this must be run only once):
# Create an insecure builder docker buildx create --use --name insecure-builder --buildkitd-flags '--allow-insecure-entitlement security.insecure'
Finally, for building the
Dockerfilewith the already created insecure builder, we have to run this command:# Build and push the image with buildx docker buildx build -t metacall/example -o type=registry --allow security.insecure .
For building it, we use buildx from Buildkit:
# Run the following command the first time only
docker buildx create --use --name insecure-builder --buildkitd-flags '--allow-insecure-entitlement security.insecure'
# Build the Guix image with the following command
docker buildx build --load -t metacall/guix --allow security.insecure --build-arg METACALL_GUIX_ARCH="x86_64-linux" .For running the image interactively from command line:
docker run --rm --privileged -it metacall/guixThis will give you a prompt where you can write guix commands and execute them directly:
guix pullIf you prefer to have a bash prompt instead, do this:
docker run --rm --privileged --entrypoint bash -it metacall/guixThen execute guix commands like this:
/entry-point.sh guix pullFor building you need to follow the following table:
| Docker Platform (--platform) | Guix Architecture String |
|---|---|
| linux/amd64 | x86_64-linux |
| linux/386 | i686-linux |
| linux/arm/v7 | armhf-linux |
| linux/arm64/v8 | aarch64-linux |
| linux/ppc64le | powerpc64le-linux |
| linux/riscv64 | riscv64-linux |
You will need the following for building and runnig:
# Required for building:
docker run --privileged --rm tonistiigi/binfmt --install all
# Required for running:
docker run --rm --privileged multiarch/qemu-user-static --reset -p yesNow you can build for some architecture, linux/386 for example:
# Build the Guix image with the following command
docker buildx build --load -t metacall/guix --platform linux/386 --allow security.insecure --build-arg METACALL_GUIX_ARCH="i686-linux" .Add all your substitutes public keys in the substitutes folder, they must end with .pub extension.
Later on add the URL of your substitute server in SUBSTITUTE_URLS list on scripts/entry-point.sh.
In case of an error of any kind.
When doing guix pull, you will get the following error:
guix pull: error: Git error: could not read directory '/root/.cache/guix/checkouts/lmgz3ewobtxzz4rsyp72z7woeuxeghzyukvmmnxlwpobu76yyi5a/.git/refs': Value too large for defined data typeThis happens because there is a mismatch in inodes between 32 and 64-bits. The only way to workaround this is to use tmpfs for storing the checkouts of Guix, or any other alternative volume that supports 64-bit inodes, for example binding the volume into the host if you are running a 64-bit host. We do this at build time with the following instruction:
RUN --security=insecure --mount=type=tmpfs,target=/root/.cache/guix \
sh -c '/entry-point.sh guix pull'
Similarly, for doing it at runtime:
docker run --privileged --platform linux/arm/v7 -it \
--mount type=tmpfs,target=/root/.cache/guix \
metacall/guix guix pullIf you want to keep the cache of the guix pull command (normally stored in XDG_CACHE_HOME environment variable, by default set to $HOME/.cache), you can use this method:
RUN --security=insecure --mount=type=tmpfs,target=/tmp/.cache \
&& mkdir -p /tmp/.cache /root/.cache \
&& export XDG_CACHE_HOME=/tmp/.cache \
&& sh -c '/entry-point.sh guix pull' \
&& cp -a /tmp/.cache/. /root/.cache/
This will store the cache in the default folder, speeding up next pulls.
If you get the following error:
error: failed to solve: granting entitlement security.insecure is not allowed by build daemon configurationRun:
# This will select the insecure-builder previously created if it got unselected for some reason
docker buildx use insecure-builderIf you get the following error:
docker run --rm --privileged --platform linux/arm/v7 --entrypoint bash -it metacall/guix
standard_init_linux.go:228: exec user process caused: exec format errorRun this first:
docker run --rm --privileged multiarch/qemu-user-static --reset -p yesMetaCall Guix GCC Example: This repository demonstrates how to use MetaCall Guix Docker Image for building portable self contained packages, in this case [email protected]. The idea of this repository is to make a Proof of Concept for Blink Virtual Machine by using GCC and possibly try to do the same with MetaCall in the near future.
MetaCall Linux Distributable: This repository provides a self-contained and portrable version of MetaCall Core for Linux.