Installs Ubuntu Desktop amd64 from a local ISO or mounted installer source onto an external USB/removable drive using a UEFI removable bootloader. This was originally built for a Sabrent USB4 NVMe enclosure, but the v1 target is any external USB/removable drive.
This script is destructive. It repartitions and formats the target disk.
Use only Ubuntu ISOs or mounted installer sources that you trust. During finalization, this installer enters the copied system with root privileges to install boot packages, create the user, and configure GRUB. A malicious ISO or mounted source should be treated as root-code execution on the host.
Before running a real install, verify the ISO with Ubuntu's official download verification guide. At minimum, check the ISO SHA256 checksum against Ubuntu's published checksum for that release. For stronger authenticity checking, verify the signed SHA256SUMS file before trusting the checksum.
If you just want a bootable Ubuntu installer or live USB, use Ubuntu's official USB installation media docs. This project is for creating a full installed Ubuntu Desktop system on an external drive from an ISO, with portable UEFI/Secure Boot bootloader setup and safety checks.
I built this because some USB/NVMe enclosures can expose storage geometry that trips up the normal Ubuntu installer partitioning path. In my case, the goal was a fast USB4 NVMe enclosure that could boot the same development environment on my desktop or laptop, without constantly reconciling drift between two separate installs. This script aims to make that kind of portable, full-speed external Ubuntu install repeatable.
If you want Claude Code, Codex, OpenCode, or another local coding agent to walk you through the install, paste this into the agent:
Help me install Ubuntu 26.04 Desktop amd64 onto an external drive using https://github.com/lobo235/ubuntu-external-install. Download the repository, read AGENT_INSTALL_PROMPT.md and SECURITY.md, and follow them exactly. Ask me the required questions one at a time, identify the target disk safely, handle sudo/password prompts without asking me to reveal passwords in chat, run a dry-run first, use the safest target guards available, and do not run destructive commands until I explicitly confirm the dry-run plan.
The full agent prompt lives in AGENT_INSTALL_PROMPT.md.
The agent-guided flow does not require passwordless sudo. If your agent cannot handle interactive terminal prompts, such as sudo authentication, the destructive ERASE <disk> confirmation, or --prompt-password, it should prepare exact commands for you to run in your own terminal and ask you to report the result back.
Tested:
- Created a Ubuntu 26.04 Desktop amd64 install on a 128GB Samsung USB flash drive from native Linux.
- Booted that USB on a Dell laptop, reached the desktop, and logged in successfully.
Recognized source layouts:
- Ubuntu Desktop amd64 live ISO with
casper/filesystem.squashfs. - Ubuntu Desktop amd64 live ISO with layered
casper/minimal*.squashfs.
Unsupported in v1:
- Ubuntu Server/Subiquity ISOs.
- Non-Ubuntu distributions.
- BIOS/legacy boot.
- ARM or cross-architecture installs.
- Encryption, btrfs, ZFS, swap, or separate
/home. - Automatic ISO downloads.
The installer checks dependencies before destructive work and reports all missing commands it can detect.
On Ubuntu/Debian hosts, these packages are typically needed:
sudo apt install gdisk dosfstools e2fsprogs rsync util-linux udev coreutils findutilsshellcheck is optional and only used by tests if installed.
Prefer a stable target path under /dev/disk/by-id/.
sudo ./install-ubuntu-external.sh --dry-run \
--iso ~/Downloads/ubuntu-26.04-desktop-amd64.iso \
--target /dev/disk/by-id/usb-... \
--user alice \
--prompt-passwordIf the plan looks right:
sudo ./install-ubuntu-external.sh \
--iso ~/Downloads/ubuntu-26.04-desktop-amd64.iso \
--target /dev/disk/by-id/usb-... \
--user alice \
--prompt-passwordUse an ISO file:
sudo ./install-ubuntu-external.sh --iso ubuntu.iso --target /dev/disk/by-id/usb-... --user alice --prompt-passwordUse an already mounted installer filesystem:
sudo ./install-ubuntu-external.sh --source /mnt/ubuntu-installer --target /dev/disk/by-id/usb-... --user alice --prompt-passwordThe source must not live on the same disk as the target.
Copying the Ubuntu filesystem is usually the longest step. The installer prints an approximate source size and a time warning before this starts. With the default --copy-progress periodic, it prints a short status update every 30 seconds, which stays readable in both terminals and automation logs.
You can override that behavior:
--copy-progress detailed
--copy-progress neverdetailed uses rsync's live aggregate progress output. never prints only the starting warning and final rsync summary.
Useful target guards:
--expect-serial SERIAL
--expect-model TEXTBy default, the target must be a USB/removable whole disk. Advanced overrides:
--allow-non-usb
--allow-loop-targetMounted target filesystems cause an error by default. To let the script unmount target children:
--unmount-targetAutomation can skip the prompt with:
--yes--yes skips only the confirmation prompt. It does not skip safety validation.
A usable desktop install needs a user. Pass --user NAME, or let the script infer the sudo user when possible.
For normal installs, use:
--prompt-passwordFor automation:
--password-hash '...'Advanced modes:
--allow-locked-user
--no-userThese can produce a system that boots but is not practically usable until accounts are provisioned another way.
v1 is UEFI x86_64 only. By default it installs Ubuntu signed shim/GRUB packages and writes the removable fallback path without touching firmware NVRAM:
/EFI/BOOT/BOOTX64.EFI
Advanced users can pass --write-nvram, but portable external drives should normally avoid it.
The script can run from WSL2 if the Linux side has the needed block devices, mount features, and chroot support. It does not attach Windows disks for you.
Typical flow:
- In elevated PowerShell, identify the external disk carefully.
Get-Disk | Format-Table Number,FriendlyName,SerialNumber,Size,BusType,PartitionStyle,OperationalStatus
- Attach it to WSL:
wsl --mount --bare \\.\PHYSICALDRIVE<N>
- In WSL, confirm the target:
lsblk -o NAME,PATH,SIZE,MODEL,SERIAL,TRAN,TYPE,FSTYPE,MOUNTPOINTS
- Run the installer with
--dry-run. - Confirm the dry-run target disk, model, serial, size, and transport before running the real install.
- Shut down WSL or detach the disk from Windows before unplugging.
Do not select the Windows system disk.
The installer places this diagnostic script in the target system:
sudo ubuntu-external-report --output ubuntu-external-report.txtThe default report is intended to be safe enough for GitHub issues, but review it before uploading. It avoids full journals, full network details, raw hardware serials, SSH keys, command history, and arbitrary config dumps.
Only include more detail if requested and after review:
sudo ubuntu-external-report --include-network-details --output report.txt
sudo ubuntu-external-report --include-hardware-ids --output report.txt
sudo ubuntu-external-report --include-full-journal --output report.txtOpen a GitHub issue with:
- Ubuntu ISO filename/version.
- Host environment: WSL2 or native Linux, and distro/version.
- Target type: USB stick, Sabrent enclosure, or other external drive.
- Command used, with personal paths redacted.
- Failure phase: dry-run, partition, extract, finalize, first boot, login, networking.
- Whether Secure Boot was enabled.
ubuntu-external-reportoutput if the system booted far enough to run it.
Review reports before uploading. Do not include private paths, IP addresses, DNS domains, hardware serials, Wi-Fi SSIDs, SSH keys, or full journals unless you have checked them.
Run all local checks:
make checkOr run non-destructive tests directly:
tests/run.shUseful targets:
make lint
make test
make helpGitHub Actions runs make check on push and pull requests without downloading full Ubuntu ISOs. Loopback testing is available in tests/run-loopback.sh for local destructive test environments. Real USB boot validation remains manual; use tests/manual-boot-checklist.md.
Suggested GitHub description and topics are in .github/REPOSITORY_METADATA.md.