Create a custom Feature and build an image with the Builder
The Builder is a generic, containerized tool for building Linux system images from config directories. It is reusable by any project that needs to create customized Linux distributions.
This guide covers two scenarios:
- Creating a custom feature for a Debian-based builder project — using the Builder standalone with your own config directory.
- Creating a custom feature for the Garden Linux Distribution — adding a feature to the Garden Linux distribution.
Both scenarios will work with the Creating a custom Feature guide.
Debian-based builder project
This section walks you through creating a custom feature in a standalone Builder project, such as one created from the builder_example template.
Let's begin by creating a new GitHub repository based on the Builder example repository using this link:
https://github.com/new?template_name=builder_example&template_owner=gardenlinux
This repo has a GitHub Actions workflow enabled, so it will already start building the image on GitHub's hosted runners.
Customizing the image
To customize the image, clone the repo locally:
git clone https://github.com/your_username/my_linux_image
cd my_linux_imageTo ensure that your local Podman installation is working correctly, you can test it by running the following command:
./build baseThis command will create a bootable Debian Forky disk image at .build/base-amd64-forky-6f72b564.raw (note that the commit may have changed since the time of writing). You can test run the image using QEMU:
qemu-system-x86_64 -m 2048 -nodefaults -display none -serial mon:stdio -drive if=pflash,unit=0,readonly=on,format=raw,file=/usr/share/OVMF/OVMF_CODE.fd -drive if=virtio,format=raw,file=.build/base-amd64-forky-6f72b564.rawNow that you have verified that everything is working correctly, proceed to creating a custom feature.
Garden Linux Distribution
This section covers adding a custom feature to the Garden Linux distribution. Garden Linux uses the Builder as its build engine but adds a rich set of platform and element features.
Key difference: platform requirement
Garden Linux enforces exactly one platform feature per build (see ADR 0020). This means:
- Running
./build basefails becausebaseis anelement, not aplatform. - Running
./build myfeaturefails unlessmyfeatureis a platform. - You must always specify a platform:
./build kvm-myfeature-amd64
WARNING
Garden Linux requires exactly one platform feature per build. Running ./build base or ./build myfeature without a platform fails. Always include a platform, for example: ./build kvm-myfeature-amd64.
Feature types in Garden Linux
Garden Linux features follow the same structure as vanilla builder features, but use three types:
platform— target platform such askvm,aws,azure,metal,gcp. Rarely created by users.element— significant feature that may include/exclude other features, e.g.,server,cloud,cis,fedramp.flag— minor toggles, conventionally prefixed with_, e.g.,_prod,_slim,_selinux.
Customizing the image
To customize the image, clone the repo locally:
- Clone the Garden Linux repository:
git clone https://github.com/gardenlinux/gardenlinux
cd gardenlinuxTo ensure that your local Podman installation is working correctly, you can test it by running the following command:
./build kvm-baseThis command will create a bootable Garden Linux disk image at .build/kvm-base-amd64-today-6f72b564.raw (note that the commit may have changed since the time of writing). You can test run the image using QEMU:
qemu-system-x86_64 -m 2048 -nodefaults -display none -serial mon:stdio -drive if=pflash,unit=0,readonly=on,format=raw,file=/usr/share/OVMF/OVMF_CODE.fd -drive if=virtio,format=raw,file=.build/kvm-base-amd64-today-6f72b564.rawNow that you have verified that everything is working correctly, proceed to creating a custom feature.
Creating a custom feature
We will create a custom feature (an element in the Garden Linux structure) and deploy a Nginx Webserver as an example.
- Create a directory called
nginxinside thefeaturesdirectory:
mkdir features/nginxThis is where our nginx feature will live. Features are a concept of the builder that allows us to build variants of images. For example, if we wanted to add an alternative HTTP server later, we could add an apacheHttpd feature. At image build time, we could pick if we want the nginx or the apacheHttpd feature.
- Create a file named
info.yamlinsidefeatures/nginxand edit it with the content below:
description: HTTP server using Nginx
type: element
features:
include:
- baseThe info.yaml file is required for each feature by the builder. We specify that our nginx feature includes the base feature. This makes sense because the nginx feature on its own does not contain a full operating system, so to get a bootable image we include the debian system as it is defined in base. See the features documentation for detailed information on the structure of features.
- Create a file named
pkg.includeinsidefeatures/nginxwith the following content:
nginxpkg.include is a list of packages this feature needs, each on a new line.
- Create a file named
exec.configinsidefeatures/nginxwith the following content:
#!/usr/bin/env bash
set -eufo pipefail
systemctl enable nginxexec.config is a shell script we can use to customize our image. In this case, we enable the systemd unit for nginx which makes nginx start on boot.
- Make the
exec.configfile executable:
chmod +x features/nginx/exec.config- Create a directory named
/var/www/htmlinside thefile.includedirectory of Nginx:
mkdir -p features/nginx/file.include/var/www/htmlThe file.include directory allows us to merge files and directories into the root filesystem of our image.
- Create a dummy
index.htmlfile insidefeatures/nginx/file.include/var/www/htmlwith content like the following:
<!DOCTYPE html>
<html>
<body>
<p>Hello World!</p>
</body>
</html>To test your feature, build the image using the following command:
# vanilla builder
./build nginx
# builder inside gardenlinux repo
./build kvm-nginxYou can then run the image with QEMU using the following command:
# vanilla builder
qemu-system-x86_64 -m 2048 -nodefaults -display none -serial mon:stdio -drive if=pflash,unit=0,readonly=on,format=raw,file=/usr/share/OVMF/OVMF_CODE.fd -drive if=virtio,format=raw,file=.build/nginx-amd64-forky-local.raw -netdev user,id=net0,hostfwd=tcp::8080-:80 -device virtio-net-pci,netdev=net0
# builder inside gardenlinux repo
qemu-system-x86_64 -m 2048 -nodefaults -display none -serial mon:stdio -drive if=pflash,unit=0,readonly=on,format=raw,file=/usr/share/OVMF/OVMF_CODE.fd -drive if=virtio,format=raw,file=.build/kvm-nginx-amd64-today-local.raw -netdev user,id=net0,hostfwd=tcp::8080-:80 -device virtio-net-pci,netdev=net0If everything worked as intended, you should see the system boot up. Once the system is booted, opening http://localhost:8080 in a browser should display the "Hello World!" message.
To also build the new image on GitHub Actions, modify the .github/workflows/build.yml file.
Change the build step to include the nginx feature you just created, and upload the built image to GitHub's artifact storage:
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 181a646..9e4261e 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -13,4 +13,8 @@ jobs:
steps:
- uses: actions/checkout@v3
- name: Build the image
- run: ./build base
+ run: ./build nginx
+ - uses: actions/upload-artifact@v4
+ with:
+ name: my-linux-image
+ path: .build/Commit and push your changes and GitHub will build the image for you.
You have successfully created your first feature with the Builder and set up a CI pipeline to build the image.