Run a One-Off Boot Command with systemd-run-generator
systemd-run-generator lets the kernel option systemd.run= run a command as a service during boot. You will test this against a disposable systemd-nspawn container before going near a real boot entry, in about 15 minutes.
The route
Jump straight to the step you need, or tick off Done means at the end.
This guide describes the installed systemd 255.4-1ubuntu8.17 package. The generator is not a normal command launcher: systemd runs it during early boot and it reads the kernel command line. Do not add a command to a production boot entry until you have a recovery route and have tested the quoting in a disposable environment.
1. Check the installed generator and container tool
Nothing here changes the system. Confirm the package version and the paths used by the local install.
$ dpkg-query -W -f='${Package} ${Version}\n' systemd
systemd 255.4-1ubuntu8.17
$ command -v systemd-nspawn
/usr/bin/systemd-nspawn
$ systemd-nspawn --version | head -n 1
systemd 255 (255.4-1ubuntu8.17)
The generator itself normally lives at /usr/lib/systemd/system-generators/systemd-run-generator. You never invoke that path by hand: systemd calls generators while assembling the units for a boot, and this one builds the transient unit from the kernel command line.
Checkpoint
You are working with systemd 255 and have a container tool available. If systemd-nspawn is absent, stop here or use a separately managed test boot. Do not experiment first on a host whose boot process you cannot repair.
2. Run a harmless command in a disposable container
Prepare a container directory that already has a working systemd install, and replace /var/lib/machines/testbox below with the path to your own disposable image. This command runs id during the container boot.
$ sudo systemd-nspawn -D /var/lib/machines/testbox -b 'systemd.run="/usr/bin/id"'
Spawning container testbox on ...
...
uid=0(root) gid=0(root) groups=0(root)
Container testbox exited with status 0.
sudo is needed because starting a booted system container normally requires access to namespaces, mounts and the container tree. Boot messages vary; what matters is that the command ran and the container stopped with status 0.
Two layers of quoting are at work here: your shell strips the outer single quotes, and the inner double quotes travel through as part of the container's kernel command line, where systemd-run-generator removes them. That second level is what keeps /usr/bin/id together as one command. If you add arguments, quote the whole command line inside the double quotes.
$ sudo systemd-nspawn -D /var/lib/machines/testbox -b 'systemd.run="/usr/bin/printf %s\\n generator-ok"'
generator-ok
Container testbox exited with status 0.
This test does not modify the container's files. It does start a container and run a command as its boot-time root, so use an image you can discard or restore.
3. Know the default exit behaviour before it surprises you
The generator creates a unit called kernel-command-line.service: an ordinary Type=oneshot service. By default it sets both SuccessAction=exit and FailureAction=exit, which is why the booted container above shut down as soon as id or printf finished. The command's exit status is propagated to the container manager, so your outer shell can see success or failure too.
This default is easy to miss. A successful command does not leave a login shell running, and a failed command is not just logged while the boot continues; the whole thing exits. If you expected a persistent container, check the exit action first.
Run the same harmless command with both actions disabled when you want the container to keep running afterwards.
$ sudo systemd-nspawn -D /var/lib/machines/testbox -b \
'systemd.run="/usr/bin/printf %s\\n generator-ok"' \
systemd.run_success_action=none \
systemd.run_failure_action=none
generator-ok
The generator accepts none for these two options, leaving the system running after the command completes, with nspawn continuing to host the container.
Recovery
Stop this test container from another terminal with the normal nspawn or systemd procedure for your image. Do not kill an unrelated production container just because its name looks similar.
4. Run more than one command in order
Specify systemd.run= more than once and the generated service gets multiple ExecStart= lines, run in order. Keep the examples independent and observable so a failure is easy to locate.
$ sudo systemd-nspawn -D /var/lib/machines/testbox -b \
'systemd.run="/usr/bin/printf %s\\n first"' \
'systemd.run="/usr/bin/printf %s\\n second"'
first
second
Container testbox exited with status 0.
The commands are not joined by a shell: operators like &&, pipes and redirections have no special meaning here unless you explicitly start a shell as the command. That is a useful safety boundary, but it also means a command copied straight from a shell script may not behave as expected. If you need shell syntax, name the shell and its script arguments explicitly and review them before booting.
5. Diagnose quoting and command failures
Start with an absolute-path command that takes no user input. If systemd.run="/usr/bin/id" works but a longer command does not, simplify it until the failing argument is visible. Check the two parsing layers separately: your shell parses the outer command, then the generator parses the quoted kernel-command-line value.
A common mistake is dropping the inner double quotes:
$ sudo systemd-nspawn -D /var/lib/machines/testbox -b 'systemd.run=/usr/bin/printf %s\\n broken'
systemd-nspawn: ...
The exact diagnostic depends on the container and systemd release, but the value is no longer protected as one command line. Restore the inner quotes and test with printf before trying a state-changing command. Do not treat a container's generic boot failure as proof the generator itself is broken.
If the command returns non-zero, inspect the final status from the container manager and, when it stays running, the service logs inside the image. With the default failure action, the container exits promptly, so capture the outer status.
$ sudo systemd-nspawn -D /var/lib/machines/testbox -b 'systemd.run="/usr/bin/sh -c exit 7"'
$ printf 'container status: %s\n' "$?"
container status: 7
The shell here is explicit and exit 7 is deliberately harmless. Only replace it with a real command after checking its privileges, paths and rollback plan.
6. Move from a test to a real boot only with recovery ready
On a physical or virtual machine, systemd.run= has to sit on the kernel command line for that boot. Editing a bootloader entry can affect the next startup, and a command may run as root before the normal service set is even available.
- Make a one-boot change where your bootloader supports it, rather than a permanent edit.
- Keep an independent console or rescue path open before you start.
- Remove the option after the test, and never make a permanent bootloader edit for a command you have not already tested in a disposable container.
Warning
A command that writes files, changes accounts, alters networking or stops services is not a harmless smoke test, and the generator's default exit actions can shut the machine down immediately after it runs. Use systemd.run_success_action=none and systemd.run_failure_action=none only when continued operation is genuinely intended; disabling the exit action does not make the command itself any safer.
There is no persistent unit file to remove after normal use of the generator, since the unit is generated fresh from that boot's kernel command line. Undo the test by rebooting without the temporary systemd.run= options, or by removing them from the bootloader configuration if you made them persistent. Keep the original boot entry until the new one is verified.
Done means
- Version confirmed: you confirmed the installed systemd version and found
systemd-nspawn. - Harmless test ran: an absolute-path command ran successfully in a disposable container.
- Quoting understood: you used the two quoting layers correctly and know where each is stripped.
- Unit identified: you know the generated unit is
kernel-command-line.service, a oneshot service. - Exit actions checked: you checked the default success and failure exit actions before using a real boot.
- Rescue path ready: you have a rescue path and an undo plan before adding the option to a persistent boot entry.