Background
I frequently work with different Raspberry Pi images. I may get a Raspberry Pi OS installation setup just the way I want and then need that SD Card to try out Ubuntu Server on the Pi or want a fresh image for some other reason. While high quality SD Cards (V30, U3) have come down in price quite a bit, they are still not "cheap" enough for me to use a new one for every experiment. Besides who can keep track of what is on all those little cards!
Disk Imaging to the Rescue
If you work with the Raspberry Pi much you have heard of the utility dd. If you are not familiar I would highly recommend it. It's a command line utility and it has the potential to really break a system if you tell it to write an image to the wrong place, like your root directory. Because of this I think some are scared to use it, but with a little understanding and care, it's a very powerful, quick, and easy to use tool.
Easy Mode
The easiest way to use this tool is to take your SD Card out of your Pi and stick it in your Linux computer. If your system auto mounts removable storage then you should unmount the disk. Figure out the device name (lsblk, df, etc), make sure you understand this step as it's important to be using the correct disk for read and write. Now we copy the entire SD Card disk image to our local storage. This will copy the partition table and all the partitions on the disk. We will need all of it to be able to write this to an SD Card and boot the image again latter.
sudo dd status="progress" if=/dev/sdX of=perfect-pi-image.img
- You need root since we are manipulating disk images.
- dd -- disk duplicator, disk destroyer, old old wooden ship. No one knows any more.
- status="progress" this is optional, I always use it because otherwise you get very little feedback from the program.
- if=/dev/sdX where x is the disk letter identified, no partition number since we want the entire disk image. if stands for input file, that's the file we want to take in and write.
- of=perfect-pi-image.img This is our output file, since we are writing to disk we can just give a file name, in this scenario we are writing a file in the current working directory of our command line.
Depending on the size of the image and the speed of the disks this can take a while, that is why I use the status flag, at least you know it's still working.
You now have a perfect copy of your SD Card, you can use it for whatever you want. When you are ready to go back to your perfect-pi-image.img you reverse the if and of arguments.
sudo dd status="progress" if=perfect-pi-image.img of=/dev/sdX
This simply takes our perfect-pi-image.img file and writes it the SD Card. Once the process finishes I like to run a sync on the SD Card just to be sure everything is written.
sudo sync /dev/sdX
Then remove the SD Card and put it in the Pi and power it up.
The Catch
The above method works great especially for work in the short term. There are some drawbacks though. The biggest one is that the image will need to be written to the exact same size SD Card or larger, a smaller card wont work, even if there is free space in the img file. Because we are re-using the same partition table, the partitions have to be complete. Also, these images tend to be large, the Pi will automatically resize the root file system to the entire SD Card on first boot. If you dd a 64G SD Card the file size will be 64G, even though it's likely a large amount of that space is unused.
Shrinking
The way to keep our images as small as possible is to shrink them first. The method I use for now is to simply use a GUI program like GParted or KDE Partition Manager. I am researching command line options because those tend to work best for me in the long term. Before you run dd, we can use one of those programs to shrink the root partition of the Pi (don't worry about shrinking boot). This will save space on our local storage for backup but it will also allow us to write the image to a smaller/different brand SD Card. As long as the card is big enough to fit the shrunken image we can write to it.
Writing a Shrunken Image
You could just
run the same commands with the shrunken image sudo dd status="progress" if=perfect-pi-image.img of=/dev/sdX
.
The downside of this is that dd is going to write to the entire device
regardless of the input file size, we don't want to write 60G of zeros to the
SD Card for a 4G Pi image. Fortunately dd has an option to specify how many
blocks to write, we can tell it to write our 4G image to that 64G card and then
stop. This will ensure the entire partition table and partitions get written to
the card but then won't bother writing all the empty space remaining. First we
need to know how many blocks our image is. We use fdisk to get this info.
sudo fdisk -l perfect-pi-image.img
Disk perfect-pi-image.img: 3.98 GiB, 4277140992 bytes, 8353791 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disklabel type: dos
Disk identifier: 0x1f899d8e
Device Boot Start End Sectors Size Id Type
perfect-pi-image.img1 8192 532479 524288 256M c W95 FAT32 (LBA)
perfect-pi-image.img2 532480 8353791 7821312 3.7G 83 Linux
Using this info we can see that our last partition ends at block 8353791. With that info we can use the dd to write the image to our SD Card.
sudo dd status="progress" count=8353791 if=perfect-pi-image.img of=/dev/sdX
We added the count option, this option tells dd how many blocks to write. By default the block size for dd is 512 bytes, we can see that matches our disk block size as well. So we simply input the End sector for the last partition for our disk and once dd has copied that block it will exit. This will save a whole bunch or time and is one less write to the SD Card which is only good for it. You should be able to now sync the device like above and boot the card.
Expanding the Image
Now that the device has booted we can run sudo raspi-config
select
"Advanced Options" --> "Expand Filesystem" and the root file system will be
expanded and useable on the next boot.