To expand on the answer provided by @gilles, here's one way to create a disk image containing a formatted filesystem by first creating a filesystem (of type ESP in this example) within a file and then assembling that to a valid disk image; no root, mounts or loop devices required:
diskimg=diskimg # filename of resulting disk image
size=$((260*(1<<20))) # desired size in bytes, 260MB in this case
alignment=1048576 # align to next MB (https://www.thomas-krenn.com/en/wiki/Partition_Alignment)
size=$(( (size + alignment - 1)/alignment * alignment )) # ceil(size, 1MB)
# mkfs.fat requires size as an (undefined) block-count; seem to be units of 1k
mkfs.fat -C -F32 -n "volname" "${diskimg}".fat $((size >> 10))
# insert the filesystem to a new file at offset 1MB
dd if="${diskimg}".fat of="${diskimg}" conv=sparse obs=512 seek=$((alignment/512))
# extend the file by 1MB
truncate -s "+${alignment}" "${diskimg}"
# apply partitioning
parted --align optimal "${diskimg}" mklabel gpt mkpart ESP "${offset}B" '100%' set 1 boot on
The above approach has the side-benefit of being sparse when used on a filesystem that supports sparse files; the resulting "262MB" file occupies less than 200kB on disk:
du -h --apparent diskimg; du -h diskimg
262M diskimg
196K diskimg
Here's a diagram of the resulting image file:
