Home custom_rpi_image


Building a custom PI image Ubuntu image


  • A small SD card (The larger the drive the longer the copy will take)
  • A larger external drive
  1. Flash an SD card with the desired operating system as normal with the Rpi imager
  2. Boot a testing pi with the SD and perform the base configurations
  3. Plug the USB and external drive into a linux machine Identify the drives with the following command:
    sudo fdisk -l
  4. Once you have identified your drives, mount the larger drive to the computer.
  5. Once mounted, run the following to create the ISO file on the external drive
    sudo dd bs=4M if=/dev/mmcblk0 of='/media/ah34/New Volume/piubuntu2201.img' status=progress
  6. Shrink the ISO with the pishrink
    # sudo pishrink -d <image to shrink> <shrunk image> 
    # Example
    sudo ./tools/PiShrink/pishrink source destination
  7. The new image can be written to the pi with the Rpi imager

Using a custom image for Raspberry Pi’s

After flashing a pi, mount the “writable” partition of the SD card and run the following and make changes to any files that you want to modify for configurations.

import argparse
import os
import requests
import ipaddress
import re
import sys

if __name__ == "__main__":
    parser = argparse.ArgumentParser()
    parser.add_argument('-i','--ip_address', help='The IP address you want to set for the machine', required=True)
    parser.add_argument('-s','--subnet', help='The IP address you want to set for the machine', required=True)
    parser.add_argument('-d','--dns_server', help='The IP address you want to set for the machine', required=True)
    parser.add_argument('-g','--gateway', help='The IP address you want to set for the machine', required=True)
    parser.add_argument('-n','--hostname', help='The hostname you want to set for the machine', required=True)
    parser.add_argument('-m','--mntpoint', help='The mount point of your SD card. Do not add a trailing / to the path', required=True)
    parser.add_argument('-u', '--username', help='The user name of the user you wish to add SSH keys to', required=True)
    parser.add_argument('-o', '--operating_system', help='Distribution for template files', choices=['ubuntu'], required=True)
    arggroup = parser.add_mutually_exclusive_group()
    arggroup.add_argument('-k','--ssh_pubkey_file', help='A public key SSH file')
    arggroup.add_argument('-gh', '--github_pubkeys', help='Public keys from a Github account that you want to allow onto the machine')
    args = parser.parse_args()
    # Check the mount point
    if not os.path.exists(args.mntpoint):
        exit(1, f'The mount point provided is not valid.\nMOUNT POINT:\n{args.mntpoint}')
    # Retrieve the public key
    if args.github_pubkeys:
        r = requests.get(f'https://github.com/{args.github_pubkeys}.keys')
        if r.status_code == 200:
            pubkey = r.content.decode()
            exit(1, 'Could not find the Github user')
        if not os.path.isfile(args.ssh_pubkey_file):
            exit(1, 'Could not find the public key file')
        with open(args.ssh_pubkey_file, 'r') as file:
            pubkey = file.read()

    # Validate IP, subnet and DNS server
    except ValueError:
        exit(1, 'The IP provided is not valid IP')
    except ValueError:
        exit(1, 'The subnet provided is not valid')
    except ValueError:
        exit(1, 'The DNS server provided is not valid IP')
    # Read the resolve template
    with open(f'templates/{args.operating_system}/resolv.conf', 'r') as dns_template:
        file_contents = dns_template.read()
        text = re.sub('<DNSSERVER>', args.dns_server, file_contents)
    # Write the resolv file
    with open(f'{args.mntpoint}/etc/resolv.conf', 'w') as dns_target:
    # Read the interface file template
    with open(f'templates/{args.operating_system}/interfaces', 'r') as interface_template:
        file_contents = interface_template.read()
        text = re.sub('<IPADDRESS>', args.ip_address, file_contents)
        text = re.sub('<SUBNETMASK>', args.subnet, text)
        text = re.sub('<GATEWAY>', args.gateway, text)
        text = re.sub('<DNSSERVER>', args.dns_server, text)
    # Write the interfaces file
    with open(f'{args.mntpoint}/etc/network/interfaces', 'w') as interface_target:
    # Write the hostname
    with open(f'{args.mntpoint}/etc/hostname', 'w') as interface_target:
    # Write the hosts file
    with open(f'templates/{args.operating_system}/hosts', 'r') as hosts_template:
        hosts = hosts_template.read()
        # Sort between hostnames and FQDN
        if args.hostname in '.':
            text = re.sub('<HOSTNAME>', f"{str(args.hostname).split('.')[0]} {args.hostname}", hosts)
            text = re.sub('<HOSTNAME>', f'{args.hostname}', hosts)    
        with open(f'{args.mntpoint}/etc/hosts', 'w') as hosts_target:
    # Check for .ssh folder in the users home dir
    if not os.path.exists(f'{args.mntpoint}/home/{args.username}/.ssh'):
    # Write the authorized_keys file
    with open(f'{args.mntpoint}/home/{args.username}/.ssh/authorized_keys', 'w') as authed_keys:

    # Set RO permissions on authorized_keys file
    os.chmod(f'{args.mntpoint}/home/{args.username}/.ssh/authorized_keys', 0o444)
    print("Template files written successfully. Eject the SD and happy pi'ing")

It can be run with something like this:

sudo image_customizer.py --ip_address --subnet --dns_server --gateway --hostname  --mntpoint  --github_pubkeys --operating_system --username 
This post is licensed under CC BY 4.0 by the author.