If you build Linux images for VMs, lab machines, edge devices, or golden templates, you have probably hit the same mess at least once.
You clone an image, boot it, and realize it still carries a stale hostname, the wrong timezone, or a machine identity you never meant to duplicate.
systemd-firstboot is a small tool that solves exactly that class of problem. It writes first-boot configuration directly into an offline root filesystem or disk image, before the system ever starts.
That makes it useful when you want image builds to stay reproducible, but you still need a clean way to initialize the parts that should be unique or environment-specific.
In this guide, I will show a practical workflow for:
- setting locale, timezone, and hostname in an offline image
- generating a fresh machine ID correctly
- pre-seeding root access without putting a plaintext password on the command line
- resetting first-boot state when you want an image to ask again
- verifying what changed before you ship the image
Why use systemd-firstboot instead of editing files yourself?
You can write /etc/hostname, /etc/locale.conf, /etc/machine-id, and /etc/localtime by hand.
But systemd-firstboot gives you a few advantages:
- it understands both offline root directories and disk images
- it knows which files correspond to each setting
- it avoids overwriting existing values unless you explicitly ask it to
- it can generate a fresh machine ID for an offline image
- it has a supported reset workflow for returning an image to first-boot state
It also operates directly on the filesystem, without needing the target system to be booted. That is the key difference from tools like hostnamectl, timedatectl, or localectl.
What it can configure
According to the upstream manual, systemd-firstboot can initialize:
- machine ID
- locale and message locale
- keyboard map
- timezone
- hostname
- kernel command line used by
kernel-install - root password and root shell
That is a solid set of knobs for image preparation.
Example 1: Initialize an offline root directory
Letβs start with the simplest case: you have a mounted root filesystem at /mnt/golden-root.
sudo systemd-firstboot \
--root=/mnt/golden-root \
--locale=en_US.UTF-8 \
--timezone=UTC \
--hostname=web-template \
--setup-machine-id
What this does:
- writes
/mnt/golden-root/etc/locale.conf - creates the
/mnt/golden-root/etc/localtimesymlink - writes
/mnt/golden-root/etc/hostname - creates
/mnt/golden-root/etc/machine-idwith a random ID
A quick verification pass:
sudo cat /mnt/golden-root/etc/locale.conf
sudo cat /mnt/golden-root/etc/hostname
sudo cat /mnt/golden-root/etc/machine-id
sudo readlink /mnt/golden-root/etc/localtime
Expected shape of the results:
LANG=en_US.UTF-8
web-template
3d6f5d6d8b714d55a78f55c9e08b0d47
../usr/share/zoneinfo/UTC
Example 2: Work directly on a disk image
If your build pipeline produces a raw disk image instead of a mounted root directory, --image= is usually more convenient.
sudo systemd-firstboot \
--image=./debian-golden.raw \
--locale=en_US.UTF-8 \
--timezone=UTC \
--hostname=app-template \
--setup-machine-id
This is especially handy in image-building workflows where you do not want to mount partitions manually first.
Important machine ID rule
The machine ID should be unique per instance.
If you ship multiple clones with the same populated /etc/machine-id, some software will treat them as the same machine identity. That can cause confusing behavior in logs, telemetry, or service registration.
For offline images, use one of these patterns:
Pattern A: generate one during image preparation
sudo systemd-firstboot --root=/mnt/golden-root --setup-machine-id
Use this when the image itself is the final deployed system.
Pattern B: reset first-boot-managed files so the target config happens on first boot
sudo systemd-firstboot --root=/mnt/golden-root --reset
The --reset option removes files managed by systemd-firstboot, so the next boot is treated as first boot again.
I like this pattern for reusable templates that should be finalized only after cloning.
Example 3: Seed a root password without exposing plaintext in ps
The manual explicitly warns against placing plaintext passwords on the command line, because other users may be able to see them via ps.
A safer workflow is to pass a hashed password.
Generate a SHA-512 hash:
openssl passwd -6
You will be prompted for the password instead of placing it in shell history.
Then apply it to the offline image:
sudo systemd-firstboot \
--root=/mnt/golden-root \
--root-password-hashed='$6$rounds=10000$REPLACE_WITH_REAL_HASH'
If you need fully non-interactive automation, store the hash in your secret manager or CI secret store and inject it at runtime.
Afterward, verify that passwd and shadow were created inside the target root:
sudo ls -l /mnt/golden-root/etc/passwd /mnt/golden-root/etc/shadow
Example 4: Copy host settings, but be selective
systemd-firstboot can copy some settings from the build host.
sudo systemd-firstboot \
--root=/mnt/golden-root \
--copy-locale \
--copy-timezone
This is convenient, but I would use it carefully.
For reproducible image builds, explicit values are usually better than inheriting whatever happens to be configured on the build machine that day.
Good use case:
- local lab image built on a trusted workstation, where host timezone and locale are intentional
Less good use case:
- CI runners or shared build hosts, where inherited settings may vary silently
Example 5: Force an update when files already exist
By default, systemd-firstboot does not overwrite existing configuration files.
That is a good default, but it can surprise you if you are iterating on an image and nothing seems to change.
Use --force when you really do want replacement behavior:
sudo systemd-firstboot \
--root=/mnt/golden-root \
--hostname=web-prod-template \
--timezone=Europe/Berlin \
--force
Without --force, existing files are left alone.
A practical golden-image workflow
Here is a pattern I trust for VM templates and appliance-style images.
During image build
- Install packages and application bits.
- Set stable defaults that should be common everywhere.
- Leave machine-specific values for first-boot time.
Example:
sudo systemd-firstboot \
--root=/mnt/golden-root \
--locale=en_US.UTF-8 \
--timezone=UTC \
--hostname=template-base
Before sealing the template
Reset first-boot-managed files if clones should personalize later:
sudo systemd-firstboot --root=/mnt/golden-root --reset
After clone or deployment
Either let systemd-firstboot.service prompt on first boot where appropriate, or inject settings during provisioning.
That split keeps the image generic while still using supported systemd-native tooling.
What systemd-firstboot is not for
A few boundaries matter here.
Do not use it as a general configuration-management replacement. It is not Ansible, not cloud-init, and not a full provisioning engine.
It is best for basic early identity and boot-adjacent settings.
Also, it is not recommended as your normal interface for changing a running system that is already configured. For live systems, use the regular tools:
hostnamectltimedatectllocalectl
Troubleshooting notes
--setup-machine-id does nothing on a live system
That is expected. The manual notes that machine ID setup with --setup-machine-id is for use with --root= or --image=.
--root-shell fails for an offline root
The shell path must exist inside the target root. If your image does not contain /bin/bash, setting --root-shell=/bin/bash will fail.
Verify first:
sudo test -x /mnt/golden-root/bin/bash && echo ok
--reset seems aggressive
It is. --reset removes files configured by systemd-firstboot so the next boot is treated as first boot again. Use it intentionally, ideally near the end of an image pipeline.
Final take
systemd-firstboot is one of those tools that feels small until you start building reusable Linux images regularly.
Then it becomes a very clean answer to a real operational problem: how do you prepare an image without baking in the identity that should only exist after deployment?
If you are shipping templates, appliances, lab VMs, or self-hosted images, it is worth adding to your toolbox.
References
- systemd upstream manual,
systemd-firstboot(1): https://www.freedesktop.org/software/systemd/man/latest/systemd-firstboot.html - Debian manpage mirror for
systemd-firstboot(1): https://manpages.debian.org/bookworm/systemd/systemd-firstboot.1.en.html - ArchWiki overview: https://wiki.archlinux.org/title/Systemd-firstboot
Top comments (0)