Reference
The annotated TOML schema lives in
examples/reference.toml.
This page summarises the currently implemented module options and resolver behaviour. The TOML schema is broader than the MVP renderer; many fields are parse-only today and do not yet affect Quadlet output.
NixOS module
{ inputs, pkgs, ... }:
{
imports = [ inputs.graft.nixosModules.graft ];
services.graft = {
enable = true;
package = inputs.graft.packages.${pkgs.stdenv.hostPlatform.system}.default;
configRoot = ./containers;
configRoots = [
./containers/common
./hosts/my-host/containers
];
};
}
| Option | Type | Default | Description |
|---|---|---|---|
services.graft.enable | bool | false | Enable system/rootful Graft containers. |
services.graft.package | package or null | null | Package providing graft and graft-pause; required when configRoot or configRoots is set. |
services.graft.configRoot | path or null | null | Directory containing *.toml container definitions. |
services.graft.configRoots | list of paths | [] | Additional directories containing *.toml container definitions, read after configRoot in list order. |
The NixOS module renders only resolved containers with target = "system" and
places files under /etc/containers/systemd/.
configRoot is kept for single-root configurations. When both configRoot and
configRoots are set, Graft reads configRoot first and then each
configRoots entry in order. Configured roots must exist. Duplicate TOML
filenames across roots fail evaluation, and duplicate resolved container names
within the same target fail evaluation.
Home Manager module
{ inputs, pkgs, ... }:
{
imports = [ inputs.graft.homeManagerModules.graft ];
programs.graft = {
enable = true;
package = inputs.graft.packages.${pkgs.stdenv.hostPlatform.system}.default;
configRoot = ./containers;
configRoots = [
./containers/common
./hosts/my-host/containers
];
};
}
| Option | Type | Default | Description |
|---|---|---|---|
programs.graft.enable | bool | false | Enable user/rootless Graft containers. |
programs.graft.package | package or null | null | Package providing graft and graft-pause; required when configRoot or configRoots is set. |
programs.graft.configRoot | path or null | null | Directory containing *.toml container definitions. |
programs.graft.configRoots | list of paths | [] | Additional directories containing *.toml container definitions, read after configRoot in list order. |
The Home Manager module renders only resolved containers with target = "user"
and places files under ~/.config/containers/systemd/.
configRoot and configRoots use the same ordering and collision rules as the
NixOS module.
Current TOML behaviour
Implemented today:
version = 1is required.nameis required and must be a safe container name.deploy.targetdefaults tosystem.deploy.enable = falseprevents rendering.config.container.hostnameis rendered as QuadletHostName=when explicitly set.config.container.useris rendered as QuadletUser=when explicitly set.config.container.groupis rendered as QuadletGroup=when explicitly set together withconfig.container.user.config.container.workingDiris rendered as QuadletWorkingDir=when explicitly set.config.container.environmentis rendered as sorted, quoted QuadletEnvironment="KEY=value"lines when explicitly set.config.container.environmentFileis rendered as ordered, quoted QuadletEnvironmentFile="..."lines when explicitly set.config.filesystem.volumesis rendered as ordered QuadletVolume=lines when explicitly set.config.network.publishis rendered as ordered QuadletPublishPort=lines when explicitly set.config.runtime.modesupports onlyrootfs-store.config.runtime.packagesare mapped to Nix packages.graft-pauseis always added to the package list.- missing
config.runtime.commandbecomes['/bin/graft-pause']. - explicit
config.runtime.commandis preserved. config.service.restartis rendered only when explicitly set.config.service.restartSec,timeoutStartSec, andtimeoutStopSecare rendered only when explicitly set.
Renderer escaping
Rendered [Container] values use systemd-safe escaping while preserving
literal TOML semantics. Command argv and EnvironmentFile= entries are
rendered as quoted systemd arguments, escaping double quotes and backslashes.
Literal % characters are written as %% so systemd does not treat them as
specifiers after Quadlet places them in generated service command lines. Values
that become generated command-line arguments also write literal $ as $$ so
systemd does not perform environment variable substitution. Quoted
Environment="KEY=value" lines also escape double quotes and backslashes for
systemd syntax. [Service] values are rendered verbatim because Quadlet copies
them into the generated unit service section.
Container field validation
config.container.hostname is treated as a literal value for Quadlet
HostName=.
Current hostname validation:
- must not be empty or whitespace-only
- must not contain control characters
- no template expansion is performed
- no DNS/FQDN validation is performed yet
config.container.user is treated as a literal value for Quadlet User=.
Current user validation:
- must not be empty or whitespace-only
- must not contain control characters
- no UID syntax validation is performed yet
config.container.group is treated as a literal value for Quadlet Group=.
It requires config.container.user because Quadlet rejects Group= without
User=.
Current group validation:
- requires
config.container.user - must not be empty or whitespace-only
- must not contain control characters
- no GID syntax validation is performed yet
GroupAdd=, supplemental groups, UID/GID maps, user namespaces, and security hardening defaults are not rendered
config.container.workingDir is treated as a literal value for Quadlet
WorkingDir=.
Current working directory validation:
- must not be empty or whitespace-only
- must not contain control characters
- no path existence validation is performed
- no automatic directory creation is performed
- no workspace copy or host disk mount is created
Future copied workspace support belongs under config.workspace; workingDir
only sets the process working directory inside the container.
config.container.environment is rendered as quoted Quadlet Environment=
assignments. Output is sorted by key for deterministic builds. The whole
KEY=value assignment is double-quoted so values may contain spaces or =.
Double quotes, backslashes, % specifier markers, and literal $ characters
are escaped for systemd syntax and command-line substitution.
Current environment validation:
- keys must not be empty or whitespace-only
- keys must not contain control characters
- keys must not contain whitespace or
= - values may be empty
- values may contain whitespace or
= - values must not contain control characters
- no secret handling is performed
- no environment file generation or host environment passthrough is performed
config.container.environmentFile is treated as literal Quadlet
EnvironmentFile= entries. Entries are rendered as quoted systemd arguments so
paths may contain spaces or backslashes. User order is preserved.
Current environment file validation:
- entries must not be empty or whitespace-only
- entries must not contain control characters
- no env file generation is performed
- no secrets materialisation is performed
- no host environment passthrough is performed
config.filesystem.volumes is treated as literal Quadlet Volume= entries.
User order is preserved. Entries are rendered mechanically as target,
source:target, or source:target:mode.
Current filesystem volume validation:
targetis required by the TOML schematargetmust not be empty or whitespace-onlytargetmust not contain control characterstargetmust not contain:because Graft assembles colon-separatedVolume=text- optional
source, when present, must not be empty or whitespace-only - optional
source, when present, must not contain control characters - optional
source, when present, must not contain:because Graft assembles colon-separatedVolume=text - optional
mode, when present, requiressource - optional
mode, when present, must not be empty or whitespace-only - optional
mode, when present, must not contain control characters - optional
mode, when present, must not contain:because Graft assembles colon-separatedVolume=text - no path existence validation is performed
- no mode allowlist is applied yet
- no Quadlet
.volumeunits are generated - no tmpfs, device, raw mount, workspace, home, or promote semantics are rendered
config.network.publish is treated as literal Quadlet PublishPort= entries.
User order is preserved.
Current published port validation:
- entries must not be empty or whitespace-only
- entries must not contain control characters
- no port syntax validation is performed yet
- no Quadlet
.networkunits are generated - no DNS settings or network aliases are rendered
- no automatic firewall rules are managed
config.service.restartSec, timeoutStartSec, and timeoutStopSec are treated
as literal systemd service timing values. A [Service] section is rendered when
at least one supported service field is set. Service values are rendered
verbatim and are not %-escaped by Graft.
Current service timing validation:
- values must not be empty or whitespace-only
- values must not contain control characters
- no systemd timespan parsing is performed yet
[Install], autostart, service type,remainAfterExit, andrestartIfChangedare not rendered
Not all fields from the annotated TOML reference are rendered yet. Fields that are parsed but not listed above should be treated as reserved/roadmap fields. See Roadmap for planned coverage.