This project builds a bootable Ubuntu Server image from scratch using debootstrap
, preconfigured for deployment on Linode as a custom image. It allows you to create a lightweight, reproducible, and portable system with preinstalled tools and custom files.
This project was built to solve a real-world cost and upload limitation:
- Linode only allows custom images up to 6GB.
- Attempting to create an image from an existing VM (e.g. 20GB backups) fails.
We built this repo to:
- ✅ Reduce monthly VM costs by avoiding the snapshot/image storage model.
- ✅ Bypass Linode’s 6GB image size limit using a minimal
debootstrap
build. - ✅ Include SSH access, preinstalled tools, user, and personal files inside the image.
This image was tested successfully on a Linode VM using the LISH console:
- Ubuntu 22.04 LTS (Jammy Jellyfish) base
systemd
andopenssh-server
pre-installed- Netplan configured for DHCP (default interface:
eth0
) - Root login via password enabled
- Optional non-root user with password
- Python 3, Git, Vim, Curl, Nano, Htop installed
- File injection into
/root/
from the host system - Gzipped image ready for Linode upload
- Ubuntu or Debian host system
- Root access
debootstrap
,rsync
,gzip
,mount
Install dependencies (if missing):
sudo apt install debootstrap rsync gzip
sudo ./build-image.sh
This script automates the creation of a bootable image. It:
- Creates a 6GB raw image file
- Mounts and bootstraps Ubuntu with
debootstrap
- Installs system packages, configures hostname, fstab, netplan, and SSH
- Adds a non-root user and root password
- Injects files from your
/root/
directory into the image - Compresses the image to
linode-fixed.img.gz
Go to the Linode Cloud Manager → Images and upload linode-fixed.img.gz
.
- Create a new Linode
- Choose "Custom Image"
- Select
linode-fixed.img.gz
- Deploy the instance
A complete bash script that builds and configures the bootable image automatically. You can run it directly or use it as a base for your own variations.
An alternative to the script above — a detailed step-by-step manual guide with command-by-command instructions. Ideal if you want to learn the process or debug.
A quick reference for what to do after booting your image on Linode, especially if:
- Networking is not working
- You get
Permission denied
with SSH
If networking doesn't come up:
ip a # Identify interface (e.g. enp0s4)
cat <<EOF > /etc/netplan/01-netcfg.yaml
network:
version: 2
ethernets:
enp0s4:
dhcp4: true
EOF
netplan generate
netplan apply
ip link set enp0s4 up
If SSH login fails with “Permission denied”:
sed -i 's/#\?PermitRootLogin.*/PermitRootLogin yes/' /etc/ssh/sshd_config
sed -i 's/#\?PasswordAuthentication.*/PasswordAuthentication yes/' /etc/ssh/sshd_config
# Add them manually if they don't exist
echo "PermitRootLogin yes" >> /etc/ssh/sshd_config
echo "PasswordAuthentication yes" >> /etc/ssh/sshd_config
systemctl restart ssh
linode-image-builder/
├── build-image.sh # Automated image creation script (raw image → debootstrap → compressed .gz)
├── manual-steps.md # Manual step-by-step breakdown (every command in order, with comments)
├── post-boot.md # What to do after you boot the image on Linode
├── images/ # Screenshots and documentation images
│ └── boot-success.png
├── LICENSE # MIT License
├── README.md # Project description, usage, and structure
- This image does not include a bootloader (GRUB). Linode uses its own kernel.
- Make sure your root password and SSH config allow login after boot.
- Test everything via LISH before relying on SSH access.
Made with ❤️ by Athanasios Sersemis MIT License