Getting Started
Flake.nix setup
To get started you’ll need a flake.nix file that looks something like this:
{
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
nix-caliga = {
url = "github:nix-caliga/nix-caliga";
inputs.nixpkgs.follows = "nixpkgs";
};
};
outputs = inputs: {
caligaConfigurations.x86_64-linux = {
myimage = inputs.nix-caliga.lib.makeCaligaConfigurations {
pkgs = inputs.nixpkgs.legacyPackages.x86_64-linux;
modules = [ ./images/myimage ];
};
};
};
}
Edit the name myimage as you like, and the path ./images/myimage to match the path the image configuration you make below.
Image hash
First, pickout which base image you want to use. See Setting Base Image OS for a list of base images currently in testing.
Then you’ll want to prefetch the bootc image hash. You can use nix-prefetch-docker for this:
nix run nixpkgs#nix-prefetch-docker -- --image-name quay.io/fedora/fedora-bootc --image-tag 44
Output will look something like:
{
imageName = "quay.io/fedora/fedora-bootc";
imageDigest = "sha256:a7f0ccdc982acf78351fc3f425729d1f45e2779b69201350ebff207730ab3a29";
hash = "sha256-cJO4HhJkY+6kUu757PUBdQabjGLxollJV2W1iyq2TBY=";
finalImageName = "quay.io/fedora/fedora-bootc";
finalImageTag = "44";
}
Image Configuration
A full file example is at the end, you can also look at the project’s examples/ folder.
Start by filling in the image information from nix-prefetch-docker.
{ pkgs, ... }:
{
config = {
layeredImage = {
# This is the name of the resulting image you make
name = "ghcr.io/nix-caliga/nix-caliga";
# This is the tag of the resulting image you make
tag = "tag";
fromImage = pkgs.dockerTools.pullImage {
# These come from nix-prefetch-docker
imageName = "quay.io/fedora/fedora-bootc";
imageDigest = "sha256:a7f0ccdc982acf78351fc3f425729d1f45e2779b69201350ebff207730ab3a29";
hash = "sha256-cJO4HhJkY+6kUu757PUBdQabjGLxollJV2W1iyq2TBY=";
finalImageTag = "44";
};
};
};
}
Nix-caliga makes no changes by default, so you need to select what modules you need.
I recommend starting with config.caliga.core.enable = true to enable all core modules, as well as setting config.caliga.os to your base image’s OS.
I also recommend setting the current NixOS version for the state version.
{ pkgs, ... }:
{
config = {
...
# set your base image OS
caliga.os = "fedora";
# this enables all core modules for nix-caliga
caliga.core.enable = true;
# set the current nixos version
system.stateVersion = "25.11";
};
}
Now you can start adding configuration, such as a user account, and maybe some nixpkgs you want available.
To configure systemd service, take a look here. They are configured the same as systemd on NixOS.
{ pkgs, ... }:
{
config = {
...
# set your username
users.users.yourUser = {
isNormalUser = true;
uid = 1001;
description = "Example User";
# This will set your password at first login, it can be changed afterward
initialPassword = "password";
};
# a list of nixpkgs you want available to all users
environment.systemPackages = [
pkgs.cowsay
pkgs.nixfmt
];
};
}
Full Example Image Config
{ pkgs, ... }:
{
config = {
layeredImage = {
# This is the name of the resulting image you make
name = "ghcr.io/nix-caliga/nix-caliga";
# This is the tag of the resulting image you make
tag = "tag";
fromImage = pkgs.dockerTools.pullImage {
# These come from nix-prefetch-docker
imageName = "quay.io/fedora/fedora-bootc";
imageDigest = "sha256:a7f0ccdc982acf78351fc3f425729d1f45e2779b69201350ebff207730ab3a29";
hash = "sha256-cJO4HhJkY+6kUu757PUBdQabjGLxollJV2W1iyq2TBY=";
finalImageTag = "44";
};
};
# set your base image OS
caliga.os = "fedora";
# this enables all core modules for nix-caliga
caliga.core.enable = true;
# set the current nixos version
system.stateVersion = "25.11";
# set your username
users.users.yourUser = {
isNormalUser = true;
uid = 1001;
description = "Example User";
# This will set your password at first login, it can be changed afterward
initialPassword = "password";
};
# a list of nixpkgs you want available to all users
environment.systemPackages = [
pkgs.cowsay
pkgs.nixfmt
];
};
}
Building the Image
Build and load the resulting image:
nix build .#caligaConfigurations.x86_64-linux.myimage.config.build.image && ./result | podman load
Using the Image
You can test the image as a container with podman, replace the image name with your newly created image.
podman run -it --rm ghcr.io/nix-caliga/nix-caliga:tag
To install the image to a disk or VM, use bootc-image-builder which can make a bunch of different disk image formats from your bootc image.
For more information on working with bootc images, see the bootc documentation.
What is Nix-caliga
Nix-caliga is a tool to use the nix language to configure bootc images similarly to how you would configure NixOS. Sharing identical options, and using upstream NixOS modules where possible.
Heavily inspired by numtide’s system-manager significant chunks of nix-caliga uses their works.
Nix-caliga is currently in its early stages and likely making a number of breaking changes.
Example of a caligaConfiguration:
{ pkgs, ... }:
{
config = {
layeredImage = {
name = "ghcr.io/nix-caliga/nix-caliga";
tag = "test";
fromImage = pkgs.dockerTools.pullImage {
imageName = "quay.io/fedora/fedora-bootc";
imageDigest = "sha256:a7f0ccdc982acf78351fc3f425729d1f45e2779b69201350ebff207730ab3a29";
hash = "sha256-cJO4HhJkY+6kUu757PUBdQabjGLxollJV2W1iyq2TBY=";
finalImageTag = "44";
};
};
caliga.os = "fedora";
caliga.core.enable = true;
system.stateVersion = "25.11";
users.users.example = {
isNormalUser = true;
uid = 1001;
description = "Example User";
initialPassword = "password";
};
environment.systemPackages = [ pkgs.cowsay ];
services.bootc-update = {
enable = true;
schedule = {
onBootSec = "20s";
onUnitActiveSec = "2h";
};
};
};
}
Nix-Caliga vs NixOS
Because Nix-caliga is able to build on top of more traditional OS bootc images as a base, we gain a number of benefits. (If supported by the chosen base image)
- POSIX compatablility
- SELinux
- Secure boot out of the box
- Supports applications nixpkgs struggles to package, but that often have first party support in traditional OS repositories.
Additonally, Nix-caliga doesn’t lock the system down to one tool, and can be used along side other existing bootc workflows and tools.
For example, an expected use case scenario would be to take a base image built by another team. Maybe they built it with standard containerfiles, BlueBuild, or BuildStream. Then you use Nix-caliga on top of their image to customize to your use case(s), before finishing up with a final containerfile to rebuild the initramfs, and install a couple base-image specific packages.
NixOS has a number of strong advantages, such as the full system being build by nix. But Nix-caliga should be able to bring the power of Nix to more places.
Nix-Caliga vs “Traditional” Bootc Tools
Nix Language
There are a number of discussions online comparing nix to yaml/containerfile/toml/json/etc and I won’t dive into it here. Nix(language) is not perfect, but it offers significantly more power over yaml.
Different tools will be better suited to different usecases. Nix-caliga expands the tools available for the job.
Nix Ecosystem
Nix-caliga can also support other Nix based projects like Agenix for secrets, Home-manager for user environments and desktop configuration, and Microvm.nix for NixOS based microvms. All are working/testing in progress.
Sops-nix, Vars, Comma and others will hopefully be setup soon too.
Cross Configuration
Bootc images can share configuration, including identical package updates, alongside NixOS based systems. Allowing for more flexibility and less duplicated configuration across systems.
How It Works
This project is built on nixpkgs.dockerTools’ streamLayeredImage which “builds a script which, when run, will stream to stdout a Docker-compatible repository tarball containing a single image, using multiple layers to improve sharing between images.”
Using streamLayeredImage, with a handful of modules, allows us to configure oci images, specifically bootc images, similarly to how we would configure nixos.
Base images in testing right now are Fedora and a handful of uBlue images including the new projectbluefin/dakota image based off of GnomeOS.
The result is a configured image that does not require a nix-package, or a nix-daemon. Both are available to be included if desired, but are not required and disabled by default.
Where possible we use streamLayeredImage.contents to deliver the configuration as symlinks to the image’s nix store. If the nix-daemon is enabled, the nix db for the image contents is included for the nix-daemon.
At times, symlinks don’t cut it and we use streamLayeredImage.fakeRootCommands to copy files into place with required permissions.
Optionally there is a nix configured containerfile that podman will run after streamLayeredImage. Allowing for some workflows such as rebuilding the initramfs, that aren’t available with streamLayeredImage alone. This is disabled by default, and can be handled by external workflows if desired.
Systemd
Systemd configuration is delivered through streamLayeredImage.contents, symlinking the nix store paths to their locations.
Tmpfiles work slightly differently than systemd services, tmpfile configurations are added using config.environment.usr (a duplicate of config.environment.etc from NixOS).
Both config.systemd.*, and config.systemd.tmpfiles through config.environment.usr are placed in the /usr directory and not the /etc directory like on NixOS. See etc-usr for more information.
Optionally, selinux labels can be assigned with config.caliga.core.selinux.enable. See selinux.
Some upstream NixOS services will call scripts that do not end up in nix store paths that are labeled by default (such as with pkgs.writeShellScript).
The config.selinux.labelServiceExecs option accepts a list of systemd service names, and will ensure all of the service’s exec paths (that are in the nix store) get bin_t selinux labels.
Options
Core Systemd
Enabled with config.caliga.core.systemd.enable.
These options should all be fully available and compatible with their upstream NixOS counterparts.
config.systemd.servicesconfig.systemd.targetsconfig.systemd.timersconfig.systemd.socketsconfig.systemd.pathsconfig.systemd.mountsconfig.systemd.automountsconfig.systemd.slicesconfig.systemd.unitsconfig.systemd.packagesconfig.systemd.globalEnvironmentconfig.systemd.enableStrictShellChecks
New options specific to nix-caliga:
config.systemd.maskedUnits- a list of systemd units to mask
config.systemd.defaultUnit- Same as nixos, but it can be left empty leaving the base image’s default target.
Tmpfiles
Enabled with config.caliga.core.tmpfiles.enable.
config.systemd.tmpfiles
Etc & Usr
NixOS makes heavy use of /etc, placing most of the system configuration there.
By default on Bootc, /etc and /var are the only two mutable locations where the running system’s edits will persist. /etc specifically is handled with a three way merge. (See bootc docs)
Bootc upstream recommends configuring your system with /usr instead of /etc where possible. (See bootc docs) And if possible even enabling a transient etc that is reset on reboot. (More details on this below)
For Nix-caliga this means shifting as much configuration to /usr as we can.
While config.environment.etc will work functionally identically to how it does on NixOS, we have also added config.environment.usr with identical options to etc that can handle /usr files.config.systemd.* options are also configured to use /usr and not /etc.
Both config.environment.etc and config.environment.usr by default symlink files into place through streamLayeredImage.contents, pointing into the nix store.
If a mode is set ("0600" etc), the file is instead copied as a real file using streamLayeredImage.fakeRootCommands with the specified permissions. This is necessary for files read early in boot (SELinux configs, ostree-prepare-root, etc.) where symlinks into the nix store won’t work.
Symlinked files resolve into /nix/store paths. If SELinux is enforcing on the base image, enable config.caliga.core.selinux.enable to ensure proper labelling. See selinux.
Transient etc
Transient etc is made available through config.bootc.ostree-prepare-root.transientEtc which will configure the /usr/lib/ostree/prepare-root.conf file for transient etc. I recommend looking over config.bootc.ostree-prepare-root.additionalConf and verifying the prepare-root.conf file has the full set of configuration that you need. (See bootc docs and ostree-prepare-root)
In order for prepare-root.conf changes to take effect, you must regenerate the initramfs.pkgs.dockerTools.streamLayeredImage is unable to regenerate the image’s initramfs, and this must be done with podman. config.bootc.ostree-prepare-root.transientEtc will configure a containerfile with a simple regenerate command.
If config.caliga.core.containerfile.enable is true, then after the streamLayeredImage completes, podman will run using the containerfile on the image before streaming the image itself. (See buildImage)
As a side note, currently the config.bootc.ostree-prepare-root.transientEtc option only works for Fedora, and not for uBlue images which handle initramfs differently.
System Packages
Packages in config.environment.systemPackages have their binaries included in /usr/local/bin by a symlink to config.system.path’s /bin so they can be used by sudo without configuring secure_path.
config.caliga.core.environment.linkCurrentSystem creates a /run/current-system/sw symlink also pointing at config.system.path, so NixOS and home-manager modules that reference paths like /run/current-system/sw/bin work. Only /bin is currently linked. See config.environment.pathsToLink.
Options
Enabled with config.caliga.core.etc-usr.enable.
These options should all be fully available and compatible with their upstream NixOS counterparts.
config.environment.etc- Files to be placed in /etc
config.environment.usr- Files to be placed in /usr
config.environment.systemPackages- Packages made available to all users at
/usr/local/bin
- Packages made available to all users at
config.caliga.core.environment.linkCurrentSystem- Create a symlink to
config.system.pathat/run/current-system/swso modules referencing/run/current-system/sw/binresolve. Defaults totrue.
- Create a symlink to
Nix-caliga options
All nix-caliga options are disabled by default.
You can pick and choose which parts of nix-caliga you want to make use of.
Setting Base Image OS
config.caliga.os is optional, can be left empty.
Selects the base operating system of the bootc image. This sets OS-specific defaults such as SELinux, and system group GIDs to match those already present in the base image.
Currently Available:
- Fedora(
fedora) Fedora bootc base images - Bluefin-dakota(
gnomeOS) projectbluefin/dakota
Enabling All Core Options
config.caliga.core.enable enables all core modules: etc-usr, systemd, tmpfiles, and users.
SELinux is controlled separately through config.caliga.core.selinux.enable and is set automatically based on config.caliga.os.
Individual Core Modules
Each module can be enabled independently:
config.caliga.core.etc-usr.enable- Enables
/etcand/usrfile generation. Required by most other modules. See etc-usr.
- Enables
config.caliga.core.systemd.enable- Enables systemd unit generation. See systemd.
config.caliga.core.tmpfiles.enable- Enables
config.systemd.tmpfilesrule management.
- Enables
config.caliga.core.users.enable- Enables user and group management through userborn. See users-groups.
config.caliga.core.selinux.enable- Enables SELinux file context labeling. See selinux.
Environment
config.caliga.core.environment.linkCurrentSystem- Create a
/run/current-system/swsymlink toconfig.system.pathso NixOS and home-manager modules referencing/run/current-system/sw/binresolve. Defaults totrue. See etc-usr.
- Create a
Containerfile
config.caliga.core.containerfile configures an optional podman build step that runs after streamLayeredImage. Some operations (such as initramfs regeneration) cannot be done inside streamLayeredImage and require a Containerfile instead. See buildImage.
config.caliga.core.containerfile.enable- Apply a Containerfile on top of the
streamLayeredImageoutput. Defaults tofalse.
- Apply a Containerfile on top of the
config.caliga.core.containerfile.file- Path to a custom Containerfile. If set, takes full precedence — any commands from
config.caliga.core.containerfile.extraCommandsor generated commands (e.g. fromconfig.bootc.initramfs.regenerate.enable) are not used.
- Path to a custom Containerfile. If set, takes full precedence — any commands from
config.caliga.core.containerfile.extraCommands- Additional Containerfile commands appended to generated commands. Ignored if
config.caliga.core.containerfile.fileis set.
- Additional Containerfile commands appended to generated commands. Ignored if
Users & Groups
User and group management in nix-caliga, like system-manager, uses userborn to configure users and groups during boot. The config.users.* options from NixOS are almost all supported.
Enabling config.caliga.core.users.enable requires config.caliga.core.etc-usr.enable, config.caliga.core.systemd.enable, and config.caliga.os to be set.
Userborn
Userborn runs as a systemd service early durring boot to create and update users and groups. The base image’s systemd-sysusers.service is masked when config.caliga.core.users is enabled.
Home Directories
On bootc, /home is typically a symlink to /var/home so that home directories persist across image updates. If the user has createHome as true, this is handled by building the /home/* symlink to /var/home* into the image.
Currently NixOS’ config.users.defaultUserHome does not exist, but per-user config.users.users.<name>.home is still configurable.
OS-Specific Base Users and Groups
To add a user to a base image group with config.users.users.*.extraGroups, the group must have a GID in the config.
Additionally, a few system users like root need their id’s set as well.
These ids differ between OS, so we set these as part of config.caliga.os.
Options
Enabled with config.caliga.core.users.enable.
config.users.usersconfig.users.groupsconfig.users.mutableUsers- Defaults to
true
- Defaults to
config.users.defaultUserShellconfig.users.enforceIdUniquenessconfig.users.allowNoPasswordLogin
Differences from NixOS
Removed or Stubbed Options
config.users.defaultUserHomeconfig.users.manageLingeringconfig.users.users.<name>.opensshconfig.users.users.<name>.cryptHomeLuksconfig.users.users.<name>.pamMountconfig.users.users.<name>.subUidRanges/subGidRanges/autoSubUidGidRangeconfig.users.users.<name>.expires
Changed Options
config.users.users.<name>.lingerCurrently there is nonulloption, the user is either lingering, or not.trueorfalseonly.config.users.users.<name>.shell— needs to use the base image’s shell such as/usr/bin/zsh, not a package likepkgs.zshasprograms.${shell}.enabledoes not exist.
Nix
Nix-caliga does not require the nix package itself, or a nix-daemon to be on the bootc images it creates.
However you may still want nix and the nix-daemon to be available on your system. config.nix.enable sets the nix-daemon up.
This module is functional, but it is not yet fully featured, some aspects of the nix configuration are not easily accessible.
Enabling config.nix.enable requires config.caliga.core.etc-usr.enable, config.caliga.core.systemd.enable, config.caliga.core.tmpfiles.enable, and config.caliga.core.users.enable to all be true.
Writable /nix Overlay
On a bootc system /nix is part of the read only image. To allow the Nix daemon to install packages and manage the nix store, we create an overlay mount over /nix with its upper directory at /var/nix/upper and work directory at /var/nix/work (/var being mutable and persistant). The overlay mount only occurs on systems where /nix is actually read only. In a container the overlay mount won’t actually be created.
Nix Daemon
The nix-daemon service and socket units are pulled in through config.systemd.packages from the Nix package itself. It’s not tested, but other “nix” packages should work in place.
Image Nix Database
If the nix-daemon is enabled, config.layeredImage.includeNixDB (see buildImage) is set to true so that the Nix database from the image build is included. This allows the daemon to be aware of store paths that were baked into the image.
Nix Configuration
A nix.conf is placed at /etc/nix/nix.conf with flakes and the nix-command experimental features enabled along with the nixpkgs path set from the flake input. Additional settings can be configured through config.nix.settings.
Eventually the nix-caliga nix-daemon will be more configurable. Hopefully pulling in more of the configuration directly from NixOS.
Options
config.nix.enable- Enable the Nix package and daemon.
config.nix.package- The Nix package to use. Defaults to
pkgs.nix.
- The Nix package to use. Defaults to
config.nix.nrBuildUsers- Number of Nix build users to create. Defaults to
32.
- Number of Nix build users to create. Defaults to
config.nix.settings- Additional settings for
nix.confas an attrset. Merged with the (currently) fixed defaults (build-users-group,experimental-features,nix-path).
- Additional settings for
SELinux
Fedora bootc images ship with SELinux enforcing by default. Nix store paths do not get SELinux context in the base image.
When config.caliga.core.selinux.enable is true, nix-caliga creates a file at /etc/selinux/targeted/contexts/files/file_contexts.local with rules that label common nix store subdirectories.
This file is placed using config.environment.etc as a real file, as SELinux needs it before the nix store is available.
Nix Store Labels
The default nix store context rules (enabled by config.selinux.nixStoreContexts.enable) cover the standard layout used by nix-caliga:
/nix/store/<hash>/bin/,/nix/store/<hash>/sbin/>bin_t/nix/store/<hash>/lib/>lib_t/nix/store/<hash>/etc/>etc_t/nix/store/<hash>/share/,/nix/store/<hash>/usr/,/nix/var/nix/profiles/>usr_t/nix/store/<hash>/lib/systemd/system/,/nix/store/<hash>/usr/lib/systemd/system/>systemd_unit_file_t/nix/store/<hash>/man/>man_t/nix/var/nix/daemon-socket/>var_run_t
This mostly follows the same SELinux types used by the nix-community/nix-installers SELinux policy, with a few additions for /usr and systemd.
Service Exec Labels
Some NixOS modules produce scripts with pkgs.writeShellScript or pkgs.writeTextFile that end up at store paths that dont have a /bin or /sbin. This means the scripts do not get labels by default.
The config.selinux.labelServiceExecs option accepts a list of systemd service names and it will extract all Exec* paths (ExecStart, ExecStartPre, etc.) from those services, and add bin_t rules for all nix store paths.
Enforcement Mode
By default, the base image’s SELinux enforcement mode is left unchanged. The config.selinux.enforcementMode option can override it by writing /etc/selinux/config with the specified mode.
Custom File Contexts
Additional file context rules can be added with config.selinux.fileContexts:
selinux.fileContexts = {
"/nix/store/[^/]+/etc(/.*)?" = "etc_t";
};
Options
Enabled with config.caliga.core.selinux.enable.
config.selinux.nixStoreContexts.enable- Default SELinux rules for
/nix/storepaths. Defaults totrue.
- Default SELinux rules for
config.selinux.labelServiceExecs- List of systemd service names whose Exec store paths need to be labeled
bin_t.
- List of systemd service names whose Exec store paths need to be labeled
config.selinux.fileContexts- Extra SELinux file context rules.
config.selinux.enforcementMode- SELinux enforcement mode. Defers to the base image by default.
config.selinux.ignoreWarnings- Suppress SELinux-related warnings from other modules.
Build Image
Nix-caliga builds bootc-compatible OCI images using pkgs.dockerTools.streamLayeredImage. The module takes a base bootc image (Fedora-bootc, uBlue, etc), layers Nix store paths and configuration on top, and produces a streamable image script.
The final image script is available at config.build.image.
StreamLayeredImage
pkgs.dockerTools.streamLayeredImage.* options are made directly available at config.layeredImage.*. See the NixOS Manual.
The image is labeled with containers.bootc = "1" and ostree.bootable = "true" by default.
Limitations
streamLayeredImage only has access to the files built by streamLayeredImage.
This means steps that need base image contents (initramfs regeneration, rpm operations, etc.) cannot run in streamLayeredImage.fakeRootCommands. Use the containerfile option instead.
Containerfile
When config.caliga.core.containerfile.enable is true, the build gains a second stage. After streamLayeredImage streams the base tar, podman runs a containerfile on top of it. This allows steps such as regenerating the initramfs for bootc’s prepare-root.conf changes to take effect.
You can either provide a complete Containerfile with config.caliga.core.containerfile.file, or list commands with config.caliga.core.containerfile.extraCommands.
Other modules may add to config.caliga.core.containerfile.extraCommands automatically. For example, config.bootc.ostree-prepare-root.transientEtc (see bootc) adds an initramfs regeneration command since streamLayeredImage alone cannot do this.
config.caliga.core.containerfile.file takes precedence over config.caliga.core.containerfile.extraCommands if set.
Options
config.build.image
- The final image script. Read-only. Result is either the
streamLayeredImagescript, or a script with thestreamLayeredImagescript wrapped by a podman build with the containerfile.
config.layeredImage.*
config.layeredImage.name- The full image name (e.g. ghcr.io/org/image).
config.layeredImage.tag- Image tag. Defaults to “latest”.
config.layeredImage.maxLayers- Maximum number of layers in the image. Defaults to
80.
- Maximum number of layers in the image. Defaults to
config.layeredImage.fromImage- Base image to layer on top of, typically from
pkgs.dockerTools.pullImage.
- Base image to layer on top of, typically from
config.layeredImage.contents- Derivations to include in the image contents.
config.layeredImage.created- Timestamp for the image creation date.
config.layeredImage.extraCommands- Shell commands to run after creating the layer directory.
config.layeredImage.fakeRootCommands- Shell commands to run inside a fakeroot environment.
config.layeredImage.enableFakechroot- Whether to run
config.layeredImage.fakeRootCommandsin a fakechroot environment.
- Whether to run
config.layeredImage.includeStorePaths- Whether to include Nix store paths in the image. Defaults to
true.
- Whether to include Nix store paths in the image. Defaults to
config.layeredImage.includeNixDB- Whether to include the Nix database in the image. Useful if running the nix daemon on the target system. Defaults to
false.
- Whether to include the Nix database in the image. Useful if running the nix daemon on the target system. Defaults to
config.layeredImage.config- OCI container config (Cmd, Env, Labels, Entrypoint, etc.).
config.caliga.core.containerfile.*
config.caliga.core.containerfile.enable- Apply a Containerfile on top of the
streamLayeredImageoutput.
- Apply a Containerfile on top of the
config.caliga.core.containerfile.file- Path to a Containerfile. Takes precedence over
config.caliga.core.containerfile.extraCommandsif set.
- Path to a Containerfile. Takes precedence over
config.caliga.core.containerfile.extraCommands- Containerfile commands included alongside generated commands. Applied after
streamLayeredImage.
- Containerfile commands included alongside generated commands. Applied after
Bootc
Nix-caliga is gradually increasing the number of bootc specific options over time.
Mainly focused on Fedora, there will likely be a lot of breaking changes here as the Fedora-bootc image itself changes.
Ostree-prepare-root
config.bootc.ostree-prepare-root configures /usr/lib/ostree/prepare-root.conf.
This file is read during initramfs to configure how the root filesystem is set up at boot. See ostree-prepare-root.
Because this file is read from the initramfs, any changes require regenerating the initramfs to take effect.
Transient /etc
config.bootc.ostree-prepare-root.transientEtc mounts /etc as a transient overlay at boot. Changes to /etc are not persisted across reboots. This is encouraged by upstream bootc when possible.
Enabling config.bootc.ostree-prepare-root.transientEtc automatically:
- Enables
config.bootc.ostree-prepare-root.createConf - Enables
config.bootc.initramfs.regenerate.enable(so the initramfs picks up the new config)
When config.bootc.ostree-prepare-root.transientEtc is enabled, boot.automount is masked and replaced with mount units for /boot (ext4, by-label boot) and /boot/efi (vfat, by-label EFI-SYSTEM).
This works around issues seemingly with /etc/fstab handling under transient etc.
Requires config.caliga.core.systemd.enable = true.
Initramfs Regeneration
Because streamLayeredImage cannot run commands against the base image’s kernel or modules, initramfs regeneration must happen in a separate podman build step using a Containerfile.
Setting config.bootc.initramfs.regenerate.enable = true adds config.bootc.initramfs.regenerate.command to the end of the generated containerfile. The command defaults to running dracut.
Requires config.caliga.core.containerfile.enable = true.
If config.caliga.core.containerfile.file is set the built-in dracut command is ignored in favor of your containerfile.
Automatic Updates
The config.services.bootc-update module sets up a systemd timer that runs bootc upgrade on a schedule.
It masks the upstream bootc-fetch-apply-updates units to avoid conflicts.
Requires config.caliga.core.systemd.enable = true.
Registry Authentication
Two options are provided for authenticating with private registries:
config.services.bootc-update.auth- Builds
auth.jsoninto the image at/etc/ostree/auth.json. Credentials are baked in at image build time. Requiresconfig.caliga.core.etc-usr.enable(see etc-usr) = true.
- Builds
config.services.bootc-update.authFile- Symlinks an existing file on the host to
/etc/ostree/auth.jsonat runtime through tmpfiles. Requiresconfig.caliga.core.tmpfiles.enable = true. Takes priority overconfig.services.bootc-update.authif both are set.
- Symlinks an existing file on the host to
Auth values are base64-encoded user:pass strings, generate with echo -n 'user:pass' | base64.
Options
config.bootc.ostree-prepare-root
config.bootc.ostree-prepare-root.createConf- Write
/usr/lib/ostree/prepare-root.confinto the image. Defaults tofalse.
- Write
config.bootc.ostree-prepare-root.transientEtc- Mount
/etcas a transient overlay at boot. Defaults tofalse.
- Mount
config.bootc.ostree-prepare-root.additionalConf- Additional lines appended to
prepare-root.conf. Defaults to enabling composefs and readonly sysroot.
- Additional lines appended to
config.bootc.initramfs
config.bootc.initramfs.regenerate.enable- Add an initramfs regeneration step to
caliga.core.containerfile. Defaults tofalse.
- Add an initramfs regeneration step to
config.bootc.initramfs.regenerate.command- The command to run to regenerate the initramfs. Defaults to a
dracutcommand.
- The command to run to regenerate the initramfs. Defaults to a
config.services.bootc-update
config.services.bootc-update.enable- Enable the automatic update timer.
config.services.bootc-update.autoReboot- Reboot after applying an update (
bootc upgrade --apply). Defaults totrue.
- Reboot after applying an update (
config.services.bootc-update.schedule.onBootSec- Delay after boot before the first update check. Defaults to
30s.
- Delay after boot before the first update check. Defaults to
config.services.bootc-update.schedule.onUnitActiveSec- Interval between update checks. Defaults to
1h.
- Interval between update checks. Defaults to
config.services.bootc-update.schedule.onCalendar- If set, overrides
config.services.bootc-update.schedule.onBootSecandconfig.services.bootc-update.schedule.onUnitActiveSecwith a calendar expression (e.g. “daily”). Defaults tonull.
- If set, overrides
config.services.bootc-update.auth- Registry credentials baked into the image.
config.services.bootc-update.authFile- Path to a
containers-auth.jsonfile on the host, symlinked at runtime.
- Path to a
Agenix
Agenix is configured identically to how agenix is configured on NixOS. See the agenix documentation.
Also see the examples in the project: examples/agenix/.
Known Limitations
No known limitations so far.
bootc-image-prefetcher
Updating the base image hashes manually gives a lot of control, but can be annoying. bootc-image-prefetcher checks for updates every night, and can update your project as you update your lockfile.
Add it as a flake input alongside nix-caliga:
inputs.bootc-image-prefetcher.url = "github:nix-caliga/bootc-image-prefetcher";
Then in your image config, use a pin as layeredImage.fromImage.
The image names are the name of the directories within /pin, and the tag is the name of the file.
bootc-image-prefetcher.pins.<imageName>."<tag>"
{ inputs, pkgs, ... }:
{
layeredImage.fromImage = pkgs.dockerTools.pullImage inputs.bootc-image-prefetcher.pins.fedora-base-atomic."44";
}
See the full example: examples/bootc-image-prefetcher/.
To adjust which images and tags are pinned, I recommend forking and configuring updater/images.nix with the images you want to have auto-update.
Home-manager
Home-manager on Nix-caliga is configured identically to how home-manager is configured on NixOS. See the home-manager documentation.
Home-manager requires the nix daemon to be enabled. See Nix Package and Daemon.
See the examples in the project: examples/home-manager/.
Known Limitations
home-manager.startAsUserServiceis set to false by default in hm. Hm will not work activate in Nix-caliga if set to true.
Microvm.nix
Microvm.nix is configured identically to how microvm.nix is configured on NixOS. See the microvm.nix documentation.
Depending on how microvms are configured, the nix daemon may be required on the host image. See Nix Package and Daemon.
Also see the examples in the project: examples/microvm/.
Known Limitations
The following options are currently just stubs and have no effect on your system:
These are likely already handled in your base image
boot.kernelModuleshardware.ksm.enable
May have a few limitations when running microvms manually with the microvm command because of a lack of PAM?:
security.pam.loginLimits