I have been successfully using a script called pishrink by Drew Bonasera ('Drewsif') to back up a headless Raspberry Pi 4 (it works for all models). It is useful because after using dd to make the image it optionally shrinks the image with resize2fs to a little above the data size, and also can optionally insert a run-once command into the image to make Raspbian expand the file system when the image is flashed to a card and used for the first time to boot a Pi.
This means you could flash a smaller card than the original, provided the shrunk image size is smaller than the new card.
It is nice to compress the image to a zip archive (Balena Etcher can use these) but... it is painfully slow if you let the Pi hardware do the compression. From a 32 GB sd card I am making 11 GiB images which the Pi takes around 50 minutes to compress to around 5.5 GiB using not very severe compression options. The finished image ends up in a Windows shared folder mounted on the Pi so I tried WinRar and 7zip command line utilities on Windows to do the compressing. Still around 15 minutes. This seemed OK until I found out about a parallel compressor called pigz. On my i7 4790 PC with 16 GB RAM the images compress 11 GiB to a 5.5 GiB zip archive in around 2 minutes 30 seconds. I enabled OpenSSH on Windows 10 and after a bit of fooling around I got the Rpi to use sshpass to command the Windows PC to do the compression, so the whole thing -- dd backup - image shrink - image compress to zip, happens from one script on the Pi and takes around 25 minutes:
dd makes 30 GiB image on USB3 external hard drive: 12 mins
pishrink shrinks image to 11 GiB: 4.5 mins
move shrunk image to shared Windows folder on PC: 4.5 mins
pigz compress image to 5.5 GiB zip file: 2.5 mins
7za test zip file: 1 min
pigz for Windows