Shrink an image of a single board computer like the Raspberry PI for a smaller SD card

Posted on 26. January 2024

When you back up a sd card and attempt to flash the image to a second card, it can happen that you will be greeted by the message "Too small". This can happen because two sd cards from even the same vendor could have slightly different sizes.

In this tutorial, I will show you how to shrink an image to a specific size, so it can fit on a smaller sd card.

But be aware: You will also need a little bit of math for this one.

1. Backup sd card

At first, backup the sd card to an image with dd to a file called my-image.img.

sudo dd if=/dev/sda of="my-image.img" bs=1M status=progress

If you are not sure which device is the correct one, list them by running sudo fdisk -l.

2. Prepare image

Now create a pseudo-device wit the help of losetup so the image is mountable.

sudo losetup --show -f -P my-image.img

The image is now available under /dev/loop0.

Before resizing the file system, run a file system check on the partition you want to resize.

As I want to resize the second partition, I need to pass /dev/loop0p2 as device. You can check for the correct device by running sudo fdisk -l /dev/loop0.

sudo e2fsck -f /dev/loop0p2

3. Resize the file system and partition

While a partition is a logically independent section of a physical storage device, a file system is a method or structure used to organize and store data on a storage device. The file system will be created in a partition which sets the start and end. Since we want to shrink the file system and partition, we must first shrink the file system before shrinking the partition.
If you want to expand the file system and partition, it would be the other way around: First partition, then file system.

3.1. Resize the file system

By running following command, the file system on /dev/loop0p2 will be resized to 5 gigabytes.

sudo resize2fs /dev/loop0p2 5G

3.2. Resize partition

Since resizepart of parted works different, we need to do a little bit of math since resizepart takes the end of the partition as parameter and not its target size.

First, we need to get the size of the partition in bytes by multiplying the block count by the block size.

# Command
sudo tune2fs -l /dev/loop0p2 | grep 'Block count\|Block size'

# Output:
Block count: 1310720
Block size:     4096

Now multiply the block count by the block size:

1310720 blocks * 4096 bytes = 5368709120 bytes

Now we need to get the start sector of our partition. Please notice the string p2 in grep which stands again for the second partition.

# Input
sudo fdisk -l /dev/loop0 | grep 'p2\|Sector size\|Device'

# Output
Sector size (logical/physical): 512 bytes / 512 bytes
Device       Boot  Start      End  Sectors  Size Id Type
/dev/loop0p2      532480 62333951 61801472 29.5G 83 Linux

Since we need the new end position of the partition as sector, we need to divide the result of block size * block count through the sector size.

Size in bytes / Sector size = sectors
5368709120 bytes / 512 bytes sector size = 10485760 sectors

Afterward, add the result to the start sector and subtract 1.

Sectors + Start - 1 = End sector new
10485760 + 532480 - 1 = 11018239

Now finally resize the partition:

sudo parted /dev/loop0 unit s resizepart 2 11018239

3.3. Cleanup

Remove the pseudo-device since we do not need it anymore.

sudo losetup -D my-image.img

4. Truncate the image

Now we need to get some information about the partition layout, sectors and the block size by running fdisk.

# Input
sudo fdisk -l my-image.img

# Output
Disk my-image.img: 29.72 GiB, 31914983424 bytes, 62333952 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: 0xb438c57d

Device        Boot  Start      End  Sectors  Size Id Type
my-image.img1        8192   532479   524288  256M  c W95 FAT32 (LBA)
my-image.img2      532480 11018239 10485760    5G 83 Linux

Take a closed look at the partition table type labeled as Disklabel type.

While Raspberry PIs use the Master Boot Record as partition table labeled DOS, Radxa boards for example use the GUID Partition Table labeled as GPT. This is important because the GPT backups the table at the end of the disk. If we cut of the end of the disk image handled by GPT, we need to add some space to re-add it again by adding at least 16.384 bytes of empty space.

4.1. Master Boot Record (DOS)

(End sector + 1) * Sector size = truncate length
(11018239 + 1) * 512 = 5641338880 sectors

Now, truncate the image...

sudo truncate -s 5641338880 my-image.img

... and flash it to the new sd card.

sudo dd if=my-image.img of=/dev/sda bs=1M status=progress

4.2. GUID Partition Table (GPT)

The GPT needs a minimum length of 16.384 bytes. So the amount of sectors which needs to be added at the end relies on the sector size.

Sector size 512 bytes -> 16,384/512 = 32 sectors
Sector size 4.096 bytes -> 16.384/4.096 = 4 sectors

Since I have a sector size of 512 bytes (see 4.), I need to add a minimum of 4 sectors plus additionally 1 because sectors start at 1 and not at 0. Since the truncate command takes the amount of bytes as argument, multiply the count of sectors by the sector size.

(last END + 4 + 1) * sector size = truncate_length
(11018239 + 4 + 1) * 512 = 5.641.340.928 bytes

Now run truncate with the target length

truncate -s 5641340928 my-image.img

and fix the GPT of the disk image by running gdisk.

sudo gdisk my-image.img

Now flash the image to the new sd card.

sudo dd if=my-image.img of=/dev/sda bs=1M status=progress

Afterward, move the backup GPT to the end of the disk, where it belongs.

sgdisk --move-second-header /dev/sda

5. The end

That's it. Now you can boot from the newly created SD card.

Made with ♥️ and Gatsby © 2024