Installing Ubuntu on Raspberry Pi
July 10, 2020  |  

Ubuntu 20.04 has been finally certified for Raspberry Pi 4. This article explains how to install world’s most popular Linux distribution on your Pi.

Introduction

Raspberry Pi 4 have got some impressive hardware upgrades such as USB 3 support, real Gigabit Ethernet and up to 8 GB of RAM. These features attract many self-hosting enthusiasts who use Raspberry Pi 4 as a server, and it works pretty well for many use cases. This tiny computer can easily host things like personal websites, Nextcloud instances or even full Bitcoin nodes.

Raspberry Pi 4 features impressive production-ready hardware but having reliable hardware is only a part of a self-hosting equation. Hardware is useless without an operating system, and the default operating system for Raspberry Pi is Raspbian. Unfortunately, it lacks many tools that are often necessary to set up and maintain a server. Don’t get me wrong: Raspbian is a decent OS, it just has different goals and priorities.

Folks at Canonical saw that opportunity and decided to compliment Raspberry Pi 4 hardware with their enterprise-grade Ubuntu operating system. In my opinion, it’s one of the most important events in Raspberry Pi’s history. Finally, it’s possible to buy a cheap and reasonably performant server which is officially compatible with one of the most popular and well-tested operating systems on the market.

Prerequisites

  • Raspberry Pi 4
  • SD card (Ubuntu 20.04)
  • SD card or any USB-attached storage device (Ubuntu 20.10 or newer)

My recommendation is to use an A1-marked SD card. Those cards are usually faster when it comes to random access, and it can be a bottleneck for many kinds of workloads such as running an operating system. You might stumble upon A2 cards and assume that they would perform even better but its unlikely that you would notice any difference (except for the higher price, of course).

With the release of Ubuntu 20.10, it’s now possible to forget about SD cards and use faster and more reliable storage devices such as SSD drives. You can use any USB-attached storage, but don’t expect to see any significant performance gains. Fast SSDs also need fast CPUs in order to unlock their full capacity, so the only real benefits you might get are safety and durability. SSDs are more likely to survive power outages, and they generally last longer under sustained loads.

Step 1: Wipe Your SD Card

First, you have to find the device name of your SD card. My Dell XPS 13 has an internal card reader, so the SD cards always appear at /dev/mmcblk0 but it can be different if you use an external card reader. You should also make sure that your SD card is unmounted (umount it, if necessary).

sudo dd if=/dev/zero of=/dev/mmcblk0 bs=16M status=progress

Be patient, it can take a while. That’s why it’s good to use status=progress, we can see the progress and be assured that dd did not just hang for some reason. Tweaking block size can speed things up quite a bit but I’d advice against that unless you know exactly what you’re doing.

If you want to lower the chances that you left any traces of the old data, you might prefer to use if=/dev/urandom instead of if=/dev/zero.

Step 2: Download Ubuntu

The latest version of Ubuntu can be downloaded from the official website.

Ubuntu images ship in a compressed format so we need to unxz the downloaded file in order to extract the actual Ubuntu image that can be written on an SD card.

unxz --keep ubuntu-20.04-preinstalled-server-arm64+raspi.img.xz
ls -lh
668M ubuntu-20.04-preinstalled-server-arm64+raspi.img.xz
3.0G ubuntu-20.04-preinstalled-server-arm64+raspi.img

Step 3: Write Ubuntu Image on Your SD Card

In case you’re not familiar with dd: if is short of input file and of is short from output file.

sudo dd status=progress \
  if=ubuntu-20.04-preinstalled-server-arm64+raspi.img \
  of=/dev/mmcblk0

Finally, we should see something like that:

3200717312 bytes (3.2 GB, 3.0 GiB) copied, 649 s, 4.9 MB/s 
6255474+0 records in
6255474+0 records out
3202802688 bytes (3.2 GB, 3.0 GiB) copied, 649.853 s, 4.9 MB/s

That should be it. Let’s check what do we have now on that SD card:

lsblk --fs
NAME         FSTYPE  LABEL        UUID  FSAVAIL  FSUSE%  MOUNTPOINT
mmcblk0
├─mmcblk0p1  vfat    system-boot  xxx   191.5M   24%     /media/user/system-boot
└─mmcblk0p2  ext4    writable     xxx   762.2M   66%     /media/user/writable

You can re-insert your SD card in order to trigger auto-mounting, so you don’t have to mount partitions to directories manually.

It looks like we need two partitions: first one is used by Raspberry Pi to boot up, and the second one hosts the operating system and all the data owned by its users.

sudo fdisk -l
Disk /dev/mmcblk0: 29.7 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: 0x00000000

Device         Boot  Start     End Sectors  Size Id Type
/dev/mmcblk0p1 *      2048  526335  524288  256M  c W95 FAT32 (LBA)
/dev/mmcblk0p2      526336 6255439 5729104  2.7G 83 Linux

Note that those partitions do not take up all the free space on the SD card. The second partition will be expanded automatically once you boot up your Raspberry Pi.

Step 4: Set Up Wi-Fi (Optional)

The official image knows nothing about your Wi-Fi and that’s a pain in the ass, especially if you have a few Pi boards. Making your own custom Ubuntu image with Wi-Fi settings baked in can simplify setting up new machines or giving a fresh start to the existing ones.

First, let’s set up Wi-Fi (change /media/user/ to your actual mount path):

nano /media/user/system-boot/network-config
network:
  version: 2
  ethernets:
    eth0:
      dhcp4: true
      dhcp4-overrides:
        route-metric: 100
      optional: true
  wifis:
    wlan0:
      dhcp4: true
      dhcp4-overrides:
        route-metric: 200
      optional: true
      access-points:
        "your_access_point_name":
          password: "your_strong_password"

Note that both network interfaces are optional and eth0 has a higher priority (route-metric is set to lower value). In my opinion, this is the best configuration for most of the use cases. If you need only one of those interfaces, just remove the other one.

Raspberry Pi will read this config file only once, so it’s a good idea to double-check the config before powering on your Pi with this boot drive.

Step 5: Create Customized Image (Optional)

And the last step is to clone our customized Ubuntu to a separate img file for later use. We don’t want to clone the whole SD card, just the part with the actual data, so:

sudo fdisk -l

This tool can show us a lot of useful info about all the block storage devices. We’re interested in sector size and end sector in particular.

Disk /dev/mmcblk0: 29.7 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: 0x87c6153d

Device         Boot  Start     End Sectors  Size Id Type
/dev/mmcblk0p1 *      2048  526335  524288  256M  c W95 FAT32 (LBA)
/dev/mmcblk0p2      526336 6255439 5729104  2.7G 83 Linux

Sector size: 512 bytes

End sector: 6255439

6255439 * 512 = 3202784768 bytes or ~3.2 GB

Looks legit. Check your output and put your own numbers, if necessary. So, let’s create our custom image:

sudo dd status=progress bs=512 count=6255439 \
  if=/dev/mmcblk0 \
  of=ubuntu-20.04-custom-network-config.img
ls -lah
668M ubuntu-20.04-preinstalled-server-arm64+raspi.img.xz
3.0G ubuntu-20.04-preinstalled-server-arm64+raspi.img
3.0G ubuntu-20.04-custom-network-config.img

Step 6: First Boot

First, let’s unmount the SD card:

sudo umount /media/user/system-boot
sudo umount /media/user/writable

Now, remove that SD card and insert it into your Raspberry Pi 4 board.

If your SD card already “knows” your Wi-Fi (see step 4), you don’t need to use wires in order to start using your Raspberry Pi. Let’s connect to it:

ssh ubuntu@ubuntu

You’ll need to change your password immediately. The default one is ubuntu.

Let’s see how our partitions look now:

lsblk --fs
NAME         FSTYPE  LABEL        UUID  FSAVAIL  FSUSE%  MOUNTPOINT
mmcblk0                                                                              
├─mmcblk0p1  vfat    system-boot  xxx   154.7M   39%     /boot/firmware
└─mmcblk0p2  ext4    writable     xxx   25.3G    8%      /
sudo fdisk -l
Disk /dev/mmcblk0: 29.74 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: xxx

Device          Boot   Start       End   Sectors   Size  Id  Type
/dev/mmcblk0p1  *       2048    526335    524288   256M   c  W95 FAT32 (LBA)
/dev/mmcblk0p2        526336  62333918  61807583  29.5G  83  Linux
df -h
Filesystem      Size  Used  Avail  Use%  Mounted on
/dev/mmcblk0p1  253M   98M   155M   39%  /boot/firmware
/dev/mmcblk0p2   29G  2.5G    26G    9%  /

All fine, I guess we’re done here.

Step 7: Updating Software

Outdated software is a security threat, and you can also miss on new features and performance optimizations if you don’t keep your OS up do date. First, let’s check for new software updates:

sudo apt update
Hit:1 http://ports.ubuntu.com/ubuntu-ports focal InRelease
Hit:2 http://ports.ubuntu.com/ubuntu-ports focal-updates InRelease
Hit:3 http://ports.ubuntu.com/ubuntu-ports focal-backports InRelease
Hit:4 http://ports.ubuntu.com/ubuntu-ports focal-security InRelease
Reading package lists... Done
Building dependency tree       
Reading state information... Done
74 packages can be upgraded. Run 'apt list --upgradable' to see them.

Looks like our Ubuntu needs some updating, let’s do it:

sudo apt upgrade

Conclusion

Raspberry Pi 4 + Ubuntu is a match made in heaven. Ubuntu is as easy to install as the official Raspbian distribution, and it can offer you more stability, convenience and a wider array of professional tools that are necessary to set up and maintain a server.