Install Ubuntu in Read-Only Mode on the Turing RK1

4 min read

linux, arm

This article will cover how to install Ubuntu on the EMMC storage of the RK1, configure it in read-only mode and use the NVME as storage for Docker.

Why would you want to install an operating system in read-only mode? Well, this is useful if you want to keep the operating system on the EMMC while limiting the wear of the EMMC of your Turing RK1 module. We will also be using the NVME as persistent storage for Docker.

Installing Ubuntu

This is the easy step, it can be done through the Turing Pi's BMC, either the web or cli interface. I recommend using the Joshua's Ubuntu image. I have personally installed Ubuntu 24.10 to have to most up-to-date kernel.

Once the image is flashed, log into Ubuntu using SSH and do some basic configuration:

# Freeze the Kernel
# Only applicable if you are following along with the RK1
❯ sudo apt-mark hold linux-rockchip linux-image-rockchip linux-headers-rockchip linux-tools-rockchip

# Update packages
❯ sudo apt update
❯ sudo apt upgrade -y

# Change the hostname
❯ sudo hostnamectl set-homename nodeX # replace X with your node's number

# Set the timezone
❯ sudo dpkg-reconfigure tzdata

# Set your SSH key(s)
❯ vim .ssh/authorized_keys # *paste your keys* then exit

If you are running a x86 machine, use the official ISO to install Ubuntu and you can skip the apt-mark hold command.

Booting in read-only

Turing RK1

Next we will set Ubuntu to boot in read-only. This is done by editing the boot command line arguments.

❯ sudo vim /etc/kernel/cmdline

Simply replace rw with ro. You should have a line that looks something like this:

rootwait rw console=ttyS0,115200 console=tty1 cgroup_enable=cpuset cgroup_memory=1 cgroup_enable=memory

Next we have to update U-Boot:

❯ sudo u-boot-update

You can find mode details about how to configure the boot command line arguments over on ubuntu-rockship repository's wiki.

x86 machines

I have also tested this on an x86 virtual machine. The process is similar, but the kernel's command line arguments are not stored at the same place and we cannot use u-boot-update. Instead we have to update the grub configuration:

❯ sudo vim /etc/default/grub

Locate the line that starts with GRUB_CMDLINE_LINUX and add ro to the end of the line.

GRUB_CMDLINE_LINUX="ro"

Then update grub:

❯ sudo update-grub

Editing fstab

We also have to edit fstab by adding the ro option:

# <file system>                           <mount point> <type> <options>   <dump> <fsck>
UUID=1cf633ab-4f5f-42da-b347-31282732a446 /             ext4   defaults,ro 0      1

You have the option to reboot now and have a completely read-only system, or continue with the configuration.

Switching between ro and rw

To simplify the maintenance of the system, we will add two commands to switch between read-only and read-write modes. You can add those aliases in the /etc/bash.bashrc file:

alias ro='mount -o remount,ro /'
alias rw='mount -o remount,rw /'

You will notice that once you have switched to read-write mode, you cannot switch back to read-only as the "file system is busy". This is because the system is writing temporary files and logs.

Temporary file systems

The system will always want to write some temporary files, such as logs. For this, we will configure fstab to mount those directories using the tmpfs file system. You can add the following lines to your fstab:

tmpfs /tmp     tmpfs nosuid,nodev  0 0
tmpfs /run     tmpfs nosuid,noexec 0 0
tmpfs /var/log tmpfs nosuid,nodev  0 0
tmpfs /var/tmp tmpfs nosuid,nodev  0 0

Getting Docker working

Installing Docker

Let's install Docker first. You have to switch to read-write mode to install Docker. The installation of Docker can be done with one single command:

❯ curl -Ssl https://get.docker.com/ | bash

If you reboot now, you will notice that Docker does not start because it tries to write to /var/lib/docker which is in read-only:

❯ sudo journalctl -fu docker
node4 systemd[1]: Starting docker.service - Docker Application Container Engine...
node4 dockerd[1186]: chmod /var/lib/docker: read-only file system

To fix that and have Docker working on our system, we have to mount a persistent volume to /var/lib/docker. Again, this can be done using fstab.

Persisting /var/lib

I will use the device /dev/nvme0n1p1 as persistent storage for Docker. You may want to partition your device to your likings, but I will not cover this here. First, create a file system on the partition:

❯ sudo mkfs.ext4 /dev/nvme0n1p1

I will be mounting the NVME partition to /var/lib. This will allow us to run Docker and most other programs without any issues. We will even be able to run apt update! But not upgrade since that will write files somewhere else. Only the write to apt caches won't work and will show a warning, feel free to add a tmpfs for that if you want.

We will have to mount this directory to the /mnt directory and copy the current files from /var/lib to the new volume. If the directory /mnt does not exist, you can create it with sudo mkdir /mnt.

❯ sudo mount /dev/nvme0n1p1 /mnt
❯ sudo cp -r /var/lib/* /mnt
❯ sudo umount /mnt

Then, switch to read-write mode and add the volume to /etc/fstab. You can check out my guide about how to configure fstab properly.

UUID=d6fa8295-65be-4c38-b7c5-161a20097ac9 /var/lib ext4 defaults 0 1

Starting Docker

Switch back to read-only mode, and let's check if Docker starts! You can do this by simply rebooting, or manually as shown bellow:

❯ sudo systemctl daemon-reload
❯ sudo mount -a
❯ sudo systemctl start contaierd.service docker.service
❯ sudo docker run hello-world
Unable to find image 'hello-world:latest' locally
latest: Pulling from library/hello-world
478afc919002: Pull complete
Digest: sha256:5b3cc85e16e3058003c13b7821318369dad01dac3dbb877aac3c28182255c724
Status: Downloaded newer image for hello-world:latest

Hello from Docker!
This message shows that your installation appears to be working correctly.

Next steps

What's next? Well I would like to use my Turing Pi as a Docker Swarm cluster, but this requires persistent storage for volumes to be shared across all nodes. I will be looking into configuring Ceph on my nodes.