[Mac] How to Shrink Raspberry Pi SD Card Image Size Using a Docker Container with PiShrink Installed

(この記事には日本語版があります)

You can use the dd command to backup the entire Raspberry Pi SD card on Mac. If you simply read the entire SD card with dd, a huge image file for the entire capacity of the SD card will be created, including the unused area. This squeezes your storage capacity and takes a long time to restore to an SD card. So I tried to reduce the size of the image file using a Docker container with a tool called PiShrink. This article explains the procedure.

Environments

  • Mac: 10.14.6
  • Raspberry Pi 3 Model B+
  • Docker for Mac(docker desktop): 2.1.0.4
  • Docker container with PiShrink installed
    • I used turee/pishrink-docker this time. Several similar containers have been published on the Docker Hub, but unverified.

Deleting unnecessary applications, software packages

Before creating an SD card image, delete unnecessary applications and software packages. Insert the SD card you want to create an image file into your Raspberry Pi and power it.

Deleting unnecessary applications

sudo apt-get purge libreoffice wolfram-engine sonic-pi scratch

Be careful not to accidentally delete the apps you are using!

Deleting unnecessary software packages

sudo apt-get clean
sudo apt-get autoremove

Creating an SD card image file

Shut down the Raspberry Pi and remove the SD card. Connect the SD card to your Mac. Then, run diskutil list command on your command line to find the location of the SD card device file.

$ diskutil list
 /dev/disk0 (internal):
    #:                       TYPE NAME                    SIZE       IDENTIFIER
    0:      GUID_partition_scheme                         251.0 GB   disk0
    1:                        EFI EFI                     314.6 MB   disk0s1
    2:                 Apple_APFS Container disk1         250.7 GB   disk0s2
~ ~ ~ snip ~ ~ ~ 
 /dev/disk3 (external, physical):
    #:                       TYPE NAME                    SIZE       IDENTIFIER
    0:     FDisk_partition_scheme                        *32.0 GB    disk3
    1:             Windows_FAT_32 boot                    46.0 MB    disk3s1
    2:                      Linux                         32.0 GB    disk3s2

In the above example, it's OK to use the path /dev/disk3 where the SD card is mounted on. On the other hand, You can use another device file named /dev/rdisk3 which is also created for the SD card. Reading and writing via /dev/rdiskX is faster than via /dev/diskX since /dev/rdiskX is not buffered, but /dev/diskX is buffred on the other hand("r" for "rdisk" means "raw").

$ ls /dev/rdisk*
 /dev/rdisk0   /dev/rdisk0s2 /dev/rdisk1s1 /dev/rdisk1s3 /dev/rdisk2   /dev/rdisk2s2 /dev/rdisk3s1
 /dev/rdisk0s1 /dev/rdisk1   /dev/rdisk1s2 /dev/rdisk1s4 /dev/rdisk2s1 /dev/rdisk3   /dev/rdisk3s2
$ sudo dd if=/dev/rdisk3 of=./XXXX.img bs=1m

The dd command installed on Mac doesn't show any progress while it is running, but typing Ctrl + T on the terminal will show the number of bytes processed at that time.

Compressing the SD card image file

Let's compress the image file using a tool called PiShrink. Install and start Docker Desktop for Mac before proceeding. There are several Docker containers with PiShrink installed in the Docker Hub. I used turee/pishrink-docker this time.

$ docker pull turee/pishrink-docker
 Using default tag: latest
 latest: Pulling from turee/pishrink-docker
 Digest: sha256:31e16c7d382b7a96e28cd92458a0cc1bf1e9b88bad116b68fd7dd4ca95fbf6b8
 Status: Image is up to date for turee/pishrink-docker:latest
 docker.io/turee/pishrink-docker:latest

It is supported to mount a volume of the host machine to the Docker container when executing docker run, but an error occurred when PiShrink tried to run with the directory of the backed-up SD card image file which is in the host filesystem.

$ docker run --rm --privileged=true -v (pwd):/workdir turee/pishrink-docker pishrink XXXX.img Warning: Unable to open /workdir/XXXX.img read-write (Permission denied).  /workdir/XXXX.img has been opened read-only.
 Warning: Unable to open /workdir/XXXX.img read-write (Permission denied).  /workdir/XXXX.img has been opened read-only.
 mount: /tmp/tmp.nDyP6lWYv6: WARNING: device write-protected, mounted read-only.
 Creating new /etc/rc.local
~ ~ ~ snip ~ ~ ~
  Couldn't find valid filesystem superblock.
 resize2fs: Operation not permitted while trying to open /dev/loop0
 ERROR: resize2fs failed...
 mount: /tmp/tmp.nDyP6lWYv6: can't read superblock on /dev/loop0.
 mv: cannot stat '/tmp/tmp.nDyP6lWYv6/etc/rc.local.bak': No such file or directory
 umount: /tmp/tmp.nDyP6lWYv6: not mounted.
 losetup: /dev/loop0: detach failed: No such device or address

To avoid this error, we need to copy the original image file to the container and compress it, then copy the compressed file to the host side again. If only one argument (the path of the original source image file) is specified for pishrink command, the original file is overwritten with the compressed file.

$ docker cp XXXX.dmg 32f4176aa68a:/root/XXXX.img
$ docker exec -it 32f4176aa68a bash
 root@32f4176aa68a:/workdir# cd /root/
 root@32f4176aa68a:~# pishrink XXXX.img 
 Creating new /etc/rc.local
 rootfs: 114911/950272 files (0.5% non-contiguous), 938002/3793920 blocks
 resize2fs 1.44.1 (24-Mar-2018)
~ ~ ~ snip ~ ~ ~ 
 Begin pass 4 (max = 12048)
 Updating inode references     XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
 The filesystem on /dev/loop0 is now 937412 (4k) blocks long.

 Shrunk XXXX.img from 30G to 3.7G
 root@32f4176aa68a:~# exit
 exit
$ docker cp 32f4176aa68a:/root/XXXX.img XXXX-shrink.img

Finally, let's compress the pishrinked-image file withxz command. Although it depends on the contents of the SD card image, the pishrinked-file that was 3.7GB was compressed to about 1.1GB.

$ xz XXXX.img 
 load: 2.88  cmd: xz 30200 running 58.35u 0.42s

As with the dd command, typing Ctrl + T while the xz command is running will display the progress. If only the compression source file name is specified in the xz command arguments, the original file will be replaced with the compressed file. The name of the file will be the same as the original file but the .xz extension will be added to its tail.

Writing compressed SD card image file

The .xz file can be directly written to an SD card using Etcher.

Booting Raspberry Pi with compressed SD card image

The partition will be expanded to the full free space on the SD card at the first boot.

Conclusion

The procedure to reduce the size of the SD card image file of Raspberry Pi using a tool called PiShrink installed in Docker container have explained. Smaller image files save storage space and require less writing time, so I recommend that you compress after creating a backup from a large SD card.

References