https://superuser.com/questions/610819/how-to-resize-img-file-created-with-dd First make sure the free space is actually empty, and doesn't contain leftovers of deleted files. The easiest way to achieve this is to create a huge file on the disk, containing only null bytes, then delete it.
# losetup --find --partscan foo.img
# lsblk
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT
loop0 7:0 0 4096M 0 loop
├─loop0p1 259:0 0 2048M 0 loop
└─loop0p2 259:1 0 2048M 0 loop
# for part in /dev/loop0p*; do
mount $part /mnt
dd if=/dev/zero of=/mnt/filler conv=fsync bs=1M
rm /mnt/filler
umount /mnt
done
dd: error writing ‘/mnt/filler’: No space left on device
dd: error writing ‘/mnt/filler’: No space left on device
# losetup --detach /dev/loop0
Then compress it with a tool like gzip or xz. Even at lowest compression levels, a long series of zeros will compress well:
# ls -s
4096M foo.img
# gzip foo.img
# ls -s
11M foo.img.gz
Note that you must uncompress the image when writing it back to disk. This will uncompress it 'live':
# cat foo.img.gz | gunzip | dd of=/dev/sda
Note that the output device (sda) must be of sufficient size to fit the original image, otherwise data will be lost or corrupted.