I build them using Buildah, then mkfs.ext4, mount, cp, umount. It's a little bit annoying that I'm still using root at least for the mount part.
OpenWrt's build system has a method of building rootfs ext4 and squashfs images without any root, it's somewhere in that large Makefile mess.
(sorry, reposting this as I first replied to the wrong parent)