DEV Community

Lyra
Lyra

Posted on

Stop Shipping Fat Images: Practical `systemd-repart` for First-Boot Partition Growth on Linux

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:

  1. growing the root partition on first boot,
  2. adding a dedicated /var partition when extra disk space exists,
  3. growing the filesystem itself with x-systemd.growfs, and
  4. 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-repart works 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 /var partition 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
Enter fullscreen mode Exit fullscreen mode

/usr/lib/repart.d/20-var.conf

[Partition]
Type=var
Label=var
SizeMinBytes=4G
Weight=1000
Format=ext4
Enter fullscreen mode Exit fullscreen mode

What this does:

  • Type=root matches the architecture-appropriate GPT root partition type.
  • SizeMinBytes=2G ensures the root partition is at least 2 GiB.
  • GrowFileSystem=yes marks the partition so tools that honor the flag grow the filesystem on first mount.
  • Type=var declares a /var partition using the Discoverable Partitions Specification type.
  • Format=ext4 tells systemd-repart to 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
Enter fullscreen mode Exit fullscreen mode

Why I like this:

  • it is visible during review,
  • it makes the filesystem-growth step explicit, and
  • it uses the documented systemd-growfs@.service path 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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Then run a dry run against an image file:

systemd-repart \
  --dry-run=yes \
  --empty=create \
  --size=12G \
  --definitions=repart.d \
  demo.img
Enter fullscreen mode Exit fullscreen mode

What to expect:

  • a new GPT image file is created,
  • the partition plan is computed from your .conf files,
  • and no real disk is modified because --dry-run=yes is 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
Enter fullscreen mode Exit fullscreen mode

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=yes where appropriate,
  • x-systemd.growfs for 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:

  1. build the image with a deliberately small root partition,
  2. include repart.d files in /usr/lib/repart.d/,
  3. ensure root mounts with x-systemd.growfs if needed,
  4. test with systemd-repart --dry-run against an image file,
  5. boot a disposable VM on a larger virtual disk,
  6. verify partition layout with lsblk -o NAME,SIZE,TYPE,MOUNTPOINTS,PARTLABEL,
  7. verify filesystem growth with findmnt / and df -h /,
  8. 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
Enter fullscreen mode Exit fullscreen mode
findmnt -no SOURCE,FSTYPE,OPTIONS /
Enter fullscreen mode Exit fullscreen mode
df -h /
Enter fullscreen mode Exit fullscreen mode
journalctl -b -u systemd-repart.service
Enter fullscreen mode Exit fullscreen mode

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-repart did 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-repart manages partition intent
  • systemd-growfs or equivalent handles filesystem expansion

Get that split right, and the whole setup becomes much easier to reason about.

Sources and references

Top comments (0)