If you build Linux images for VMs, cloud instances, or appliances, you usually face the same tradeoff:
- ship a large image that wastes space everywhere, or
- ship a small image and rely on ad hoc first-boot scripts to resize partitions
systemd-repart gives you a cleaner option. It lets you describe the partitions you want, then grow or add them incrementally at boot or against an image file.
The useful part is not just automation. It is that the behavior is declarative, repeatable, and incremental.
In this guide, I will show a practical pattern for:
- growing the root partition on first boot,
- adding a dedicated
/varpartition when extra disk space exists, - growing the filesystem itself with
x-systemd.growfs, and - dry-running the whole layout safely against an image file before rollout.
What systemd-repart actually does
According to the systemd-repart(8) documentation, it is intended for image-based OS deployments and works in a mostly incremental way: it grows existing partitions and adds new ones, but does not shrink, move, or delete partitions during normal operation.
That matters because it makes first-boot layout changes much less fragile than home-grown parted scripts.
A few facts worth keeping straight:
-
systemd-repartworks with GPT partition tables. - It is typically run in the initrd at boot through
systemd-repart.service. - It can operate on the running system's backing device when invoked without arguments.
- It can also operate on an image file with
--image=. - By default, it changes the partition table, not the filesystem inside the partition, unless you explicitly use formatting features or pair it with filesystem growth.
That last point is the one people often miss.
Growing a root partition is only half the job. You also need the filesystem inside it to grow.
When this is a good fit
I like systemd-repart for cases like these:
- a golden image for KVM, Proxmox, or cloud VMs
- an appliance-style image that should expand to the target disk
- a Linux image where
/var,/home, or swap should appear only when space is available - A/B-style images where the secondary partition is created on first boot
I would not reach for it first on an already hand-managed server with a messy MBR layout, LVM stack, or years of manual partition edits. This is strongest when the image layout is intentional from day one.
Anti-duplication note
This article intentionally avoids overlap with my recent posts on systemd-sysext, systemd-delta, systemd-tmpfiles, systemd-oomd, and needrestart.
The angle here is specifically first-boot GPT partition growth and image adaptation with systemd-repart, plus the operational boundary between repartitioning and filesystem growth.
Example layout: small image, bigger disk
Let us say your image ships with:
- an ESP
- one root partition
But on the target machine you want:
- the root partition to grow beyond its shipped size
- a separate
/varpartition to be created if extra disk space is available
A minimal repart.d layout could look like this.
/usr/lib/repart.d/10-root.conf
[Partition]
Type=root
Label=root
SizeMinBytes=2G
GrowFileSystem=yes
/usr/lib/repart.d/20-var.conf
[Partition]
Type=var
Label=var
SizeMinBytes=4G
Weight=1000
Format=ext4
What this does:
-
Type=rootmatches the architecture-appropriate GPT root partition type. -
SizeMinBytes=2Gensures the root partition is at least 2 GiB. -
GrowFileSystem=yesmarks the partition so tools that honor the flag grow the filesystem on first mount. -
Type=vardeclares a/varpartition using the Discoverable Partitions Specification type. -
Format=ext4tellssystemd-repartto create a filesystem for that new partition.
The matching behavior is filename ordered. The repart.d(5) docs are explicit that definition files are sorted by filename and matched against existing partitions of the same GPT type in that order.
Make the filesystem growth explicit
Even though GrowFileSystem= exists, I prefer making root filesystem growth obvious in the mount configuration too.
For /etc/fstab, that usually means:
LABEL=root / ext4 defaults,x-systemd.growfs 0 1
LABEL=var /var ext4 defaults 0 2
Why I like this:
- it is visible during review,
- it makes the filesystem-growth step explicit, and
- it uses the documented
systemd-growfs@.servicepath that systemd exposes for mounted filesystems.
If the filesystem is already at full size, systemd-growfs does nothing.
Safer dry run against an image file
Before baking this into a boot path, test it against an image file.
Create a working directory:
mkdir -p repart-demo/repart.d
cd repart-demo
Add the definition files:
cat > repart.d/10-root.conf <<'EOF'
[Partition]
Type=root
Label=root
SizeMinBytes=2G
GrowFileSystem=yes
EOF
cat > repart.d/20-var.conf <<'EOF'
[Partition]
Type=var
Label=var
SizeMinBytes=4G
Weight=1000
Format=ext4
EOF
Then run a dry run against an image file:
systemd-repart \
--dry-run=yes \
--empty=create \
--size=12G \
--definitions=repart.d \
demo.img
What to expect:
- a new GPT image file is created,
- the partition plan is computed from your
.conffiles, - and no real disk is modified because
--dry-run=yesis in effect.
Once the output looks right, you can repeat against a disposable VM image and boot it.
A more deterministic fixed-size variant
If you do not want elastic sizing, set the minimum and maximum size to the same value.
Example:
[Partition]
Type=var
Label=var
SizeMinBytes=8G
SizeMaxBytes=8G
Format=ext4
Per repart.d(5), when SizeMinBytes= and SizeMaxBytes= are equal, the weight no longer matters because the partition size becomes fixed.
That is useful when you need predictability more than full-disk consumption.
Where people get tripped up
1. Expecting it to resize filesystems automatically
By default, systemd-repart is mostly about the partition table.
If you enlarge a partition but forget filesystem growth, df -h may still show the old size.
Use:
-
GrowFileSystem=yeswhere appropriate, -
x-systemd.growfsfor mounts, or - a documented filesystem-specific growth step if your setup requires it.
2. Using it on the wrong class of system
This is ideal for image-based, GPT-first designs.
It is not my first choice for a snowflake server with years of manual storage history.
3. Forgetting that matching is type-based and ordered
repart.d definitions are matched to existing partitions by GPT type UUID, then by filename order among definitions of the same type.
If you define multiple partitions of the same type, naming and ordering matter.
4. Assuming it is destructive by default
Normal systemd-repart operation is intentionally incremental.
That said, options like --empty=force are destructive. Treat those as lab-only until you are absolutely sure what you are doing.
A practical VM workflow
If I were rolling this out for real, I would use this sequence:
- build the image with a deliberately small root partition,
- include
repart.dfiles in/usr/lib/repart.d/, - ensure root mounts with
x-systemd.growfsif needed, - test with
systemd-repart --dry-runagainst an image file, - boot a disposable VM on a larger virtual disk,
- verify partition layout with
lsblk -o NAME,SIZE,TYPE,MOUNTPOINTS,PARTLABEL, - verify filesystem growth with
findmnt /anddf -h /, - only then promote the image.
That workflow is boring, and boring is exactly what you want from storage automation.
Verification commands after first boot
After a test boot, I would check:
lsblk -o NAME,SIZE,FSTYPE,TYPE,MOUNTPOINTS,PARTLABEL,PARTTYPE
findmnt -no SOURCE,FSTYPE,OPTIONS /
df -h /
journalctl -b -u systemd-repart.service
Those four commands usually tell you whether:
- the partition exists,
- the expected filesystem exists,
- the mount options include the growth path you expected, and
-
systemd-repartdid what the layout files said.
Final take
I like systemd-repart because it replaces a pile of first-boot storage glue with something declarative and reviewable.
If your Linux images are meant to land on disks of different sizes, this is one of the cleanest ways to keep the shipped image small while still letting the installed system take ownership of real capacity on first boot.
Just keep one boundary in mind:
-
systemd-repartmanages partition intent -
systemd-growfsor equivalent handles filesystem expansion
Get that split right, and the whole setup becomes much easier to reason about.
Sources and references
-
systemd-repart(8): https://manpages.debian.org/testing/systemd/systemd-repart.8.en.html -
repart.d(5): https://manpages.debian.org/testing/systemd/repart.d.5.en.html - systemd.io, Safely Building Images: https://systemd.io/BUILDING_IMAGES/
- Discoverable Partitions Specification: https://uapi-group.org/specifications/specs/discoverable_partitions_specification/
-
systemd-growfs(8): https://manpages.debian.org/testing/systemd/systemd-growfs.8.en.html
Top comments (0)