Custom Auto install proxmox with zfs and offline packages

jonnybravos123

New Member
Feb 9, 2026
8
1
3
I must say this has been one of the most confusing things I have done this last month. How do i create an offline custom proxmox installation with offline packages where I install proxmox on nvme01n and then give an amount of disks raidz10 (zfs). Have people figured this out? Ai is hallucinating on this and I see alot of useless post on this matter. I do understand that it has its limits but I am soon done. It hasnt been an easy process and I have no idea if there is an official way doing this. But I am building an image with docker -> trixie. Then In same folder I will need post install script, packages and a build script where I use the proxmox 9 iso and also an answer toml script. In the answer toml I install proxmox on nvme. Then it builds the iso takes answer toml, packages and post install/first boot script. Then I am stuck here. if you have question regarding this setup please ask. I will answer you. Even though myself I am confused on how I made it work partly but it took alot of trial and error.
 
Hello.

I think offline is not a concern. The installer uses ISO packages.
Personally, I use the ability to fetch the toml configuration by an URL, very practical.
Did you read all the doc ? https://pve.proxmox.com/wiki/Automated_Installation
I have read it multiple times. I have some specific requirements. We do not want to use the url. It should be plug and play from an usb. We made it work with proxmox auto install fetch assistan using from-source. We used debians trixie as base for it to work with proxmox 9 . I must say it was alot of trial and error though. There aint many good guides on this.
 
Last edited:
I have read it multiple times. I have some specific requirements. We do not want to use the url. It should be plug and play from an usb. We made it work with proxmox auto install fetch assistan using from-source. We used debians trixie as base for it to work with proxmox 9 . I must say it was alot of trial and error though. There aint many good guides on this.
Ok. Can you be more specific on your problem please ?
 
Ok. Can you be more specific on your problem please ?
How do you install zfs raidz10 ? I need to install proxmox on nvme01 which I have done. Then I have an post install script downloading everything but I cant make zfs work. Or I made it work before but not in consistent way when I run my script. The script is a bit AI generated. Everything works but not here in between. It silently fails. I have done this commands manually and it works it just not working at the time first boot script runs. This seems to not run or silently fail:
zfs create zdata/var
rsync -avHPSAX /var/ /zdata/var/


Bash:
# Install GUI components from local packages
# Install GUI components from local packages
echo "Installing XFCE4, Chromium, and LightDM from local repository..."
DEBIAN_FRONTEND=noninteractive apt-get install -y \
    --allow-unauthenticated \
    xfce4 \
    chromium \
    lightdm

echo "Cleaning up temporary APT configuration..."
rm -f "$TEMP_SOURCES"

# Restore original repositories
echo "Restoring original repositories..."
if [ -f /etc/apt/sources.list.disabled ]; then
    mv /etc/apt/sources.list.disabled /etc/apt/sources.list
fi
for f in /etc/apt/sources.list.d/*.disabled; do
    if [ -f "$f" ]; then
        mv "$f" "${f%.disabled}"
    fi
done

apt-get update

# Create proxmox user
echo "Creating proxmox user..."
if ! id "proxmox" &>/dev/null; then
    useradd -m -s /bin/bash proxmox
    echo "proxmox:CHANGE_ME_STRONG_PASSWORD" | chpasswd
    pveum user add proxmox@pve --password CHANGE_ME_STRONG_PASSWORD
    # Add proxmox user to sudo group
    usermod -aG sudo proxmox
fi

# Configure LightDM to auto-start
systemctl enable lightdm

# Configure permissions for chromium so proxmox user can open it
chown -R proxmox:proxmox /home/proxmox

echo "Offline GUI installation completed successfully!"
echo "System will start with graphical interface on next boot."

# ZFS setup
echo "Creating ZFS pool with 4 disks"

# Wipe the 4 data disks to clear old ZFS pools
echo "Wiping old ZFS labels and partitions from data disks..."
for d in /dev/sda /dev/sdb /dev/sdc /dev/sdd; do
  echo "Wiping $d..."
  zpool labelclear -f "$d" 2>/dev/null || true
  wipefs -a "$d"
  sgdisk --zap-all "$d"
  dd if=/dev/zero of="$d" bs=1M count=256
  SECTORS=$(blockdev --getsz "$d")
  dd if=/dev/zero of="$d" bs=1M seek=$((SECTORS/2048-256)) count=256
done
echo "Disk wiping complete."

zpool create -f -o ashift=13 zdata mirror /dev/sda /dev/sdb mirror /dev/sdc /dev/sdd
zfs create zdata/var
rsync -avHPSAX /var/ /zdata/var/
mv /var /varold
zfs set mountpoint=/var zdata/var

exit 0
 
If I understand well, you do not use Proxmox Automated installation ?
What do you use for installation automation ?
My autoinstall creates local-lvm storage on the nvme drive. That is where proxmox is installed. Then it does zfs setup after every package is installed,users setup, ui enabled etc. Apparently this was the only way to setup both.

Code:
[global]
keyboard = "en"
country = "en"
timezone = "UTC"
fqdn = "pve01.example.com"
root_password = "password"
mailto = "root@example.com"

[network]
source = "from-dhcp"

[disk-setup]
filesystem = "ext4"


disk-list = ["nvme0n1"]

[first-boot]
source = "from-iso"

Do you need full setup? It can be a bit long with the scripts
 
If I understand well, you do not use Proxmox Automated installation ?
What do you use for installation automation ?
Everything is done in docker. Build an image using debian trixie as base combining autoinstall and then postinstall for downloading packages and setting up zfspool and local-lvm. Proxmox iso 9.1 is used
Code:
#!/bin/bash

set -e

ISO_SOURCE="/data/custom-ve_9.1-1.iso"
ANSWER_FILE="/data/answer.toml"
POSTHOOK_SCRIPT="/data/post-install-hook.sh"
OUTPUT_DIR="/data/output"
PACKAGE_DIR="/tmp/packages"
WORK_DIR="/tmp/iso-work"
AUTO_INSTALL_MOD_ISO="/tmp/custom-ve_9.1-1-mod.iso"
OUTPUT_ISO="${OUTPUT_DIR}/custom-ve_9.1-1-custom.iso"
ISO_URL="http://iso/custom-ve_9.1-1.iso"

mkdir -p "$OUTPUT_DIR"
mkdir -p "$WORK_DIR"

# Download base ISO
if [ ! -f "$ISO_SOURCE" ]; then
    echo "$(date --rfc-3339=sec) INFO | Downloading base ISO"
    wget -q "$ISO_URL" -O "$ISO_SOURCE"
    if [ $? -ne 0 ]; then
        echo "$(date --rfc-3339=sec) ERROR | Downloading base ISO failed"
        exit 1
    fi
fi

echo ""
echo "=========================================="
echo "Step 1: Configuring auto-installer..."
echo "=========================================="

# Inject auto-installer configuration
echo "Injecting auto-installer configuration into ISO..."
auto-install-assistant prepare-iso "$ISO_SOURCE" \
  --fetch-from iso \
  --answer-file "$ANSWER_FILE" \
  --on-first-boot "$POSTHOOK_SCRIPT" \
  --output "$AUTO_INSTALL_MOD_ISO"

if [ $? -ne 0 ]; then
    echo "ERROR: Failed to configure auto-installer!"
    exit 1
fi

echo "Auto-installer configuration complete"

echo ""
echo "=========================================="
echo "Step 2: Downloading GUI packages..."
echo "=========================================="

# Run the package download script
/workspace/download-packages.sh

# Verify packages were downloaded
PACKAGE_COUNT=$(ls -1 ${PACKAGE_DIR}/download/*.deb 2>/dev/null | wc -l)
echo "Downloaded $PACKAGE_COUNT packages"

if [ "$PACKAGE_COUNT" -eq 0 ]; then
    echo "ERROR: No packages were downloaded!"
    exit 1
fi

echo ""
echo "=========================================="
echo "Step 3: Extracting original ISO..."
echo "=========================================="

ISO_EXTRACT_DIR="${WORK_DIR}/iso-extract"
ISO_NEW_DIR="${WORK_DIR}/iso-new"
mkdir -p "$ISO_EXTRACT_DIR"
mkdir -p "$ISO_NEW_DIR"

echo "Extracting ISO with xorriso..."
xorriso -osirrox on -indev "$AUTO_INSTALL_MOD_ISO" -extract / "$ISO_EXTRACT_DIR/"

echo "Copying ISO contents to working directory..."
cp -rT "$ISO_EXTRACT_DIR/" "$ISO_NEW_DIR/"

chmod -R u+w "$ISO_NEW_DIR"

echo ""
echo "=========================================="
echo "Step 4: Adding packages to ISO..."
echo "=========================================="

echo "Copying packages to ISO filesystem..."
mkdir -p "$ISO_NEW_DIR/packages"
cp -r "$PACKAGE_DIR/download" "$ISO_NEW_DIR/packages/"
cp "$PACKAGE_DIR/Packages.gz" "$ISO_NEW_DIR/packages/"

echo "Packages added to ISO:"
du -sh "$ISO_NEW_DIR/packages"

echo "Adding VM images to ISO"
mkdir -p "$ISO_NEW_DIR/vm-images"
touch "$ISO_NEW_DIR/vm-images/test.qcow2"

echo ""
echo "=========================================="
echo "Step 5: Rebuilding ISO with proper boot config..."
echo "=========================================="

echo "Analyzing original ISO boot structure..."
xorriso -indev "$ISO_SOURCE" -report_el_torito as_mkisofs 2>/dev/null | tee /tmp/iso-boot-params.txt

echo "Creating new ISO with preserved boot configuration..."
cd "$ISO_NEW_DIR"

xorriso -as mkisofs \
  -r -V "CUSTOM_VE" \
  -o "$OUTPUT_ISO" \
  -J -joliet-long \
  -c '/boot/boot.cat' \
  -b '/boot/grub/i386-pc/core.img' \
  -no-emul-boot \
  -boot-load-size 4 \
  -boot-info-table \
  --grub2-boot-info \
  -eltorito-alt-boot \
  -e '/efi.img' \
  -no-emul-boot \
  -boot-load-size 16384 \
  --grub2-mbr --interval:local_fs:0s-15s:zero_mbrpt,zero_gpt,zero_apm:"$AUTO_INSTALL_MOD_ISO" \
  --protective-msdos-label \
  -partition_cyl_align off \
  -partition_offset 0 \
  -apm-block-size 2048 \
  .

cd "$WORK_DIR"

echo "=========================================="
echo "Step 6: Making ISO hybrid bootable..."
echo "=========================================="

if command -v isohybrid &> /dev/null; then
    echo "Making ISO hybrid bootable for USB..."
    isohybrid --uefi "$OUTPUT_ISO" 2>/dev/null || echo "Note: isohybrid warning (can be ignored)"
else
    echo "isohybrid not available, skipping USB hybrid boot..."
fi

chmod 644 "$OUTPUT_ISO"

echo "Cleaning up working directory..."
rm -rf "$WORK_DIR"

echo ""
echo "=========================================="
echo "ISO Build Complete!"
echo "=========================================="
echo "Output: $OUTPUT_ISO"
echo "Packages included: $PACKAGE_COUNT"
echo "ISO size: $(du -h "$OUTPUT_ISO" | cut -f1)"
echo ""
echo "The ISO is now ready for offline installation!"
echo ""
echo "Boot parameters used (from original ISO analysis):"
cat /tmp/iso-boot-params.txt 2>/dev/null || echo "Boot analysis not available"

# Deploy to support-preproduction
echo "Uploading ISO to support-preproduction..."

echo "Uploading to ${SPP_USER}@${SPP_HOST}:/var/www/url/"
scp "$OUTPUT_ISO" ${SPP_USER}@${SPP_HOST}:/var/www/url

if [ $? -eq 0 ]; then
    echo " Successfully deployed to support-preproduction"
else
    echo " Failed to deploy to support-preproduction"
    exit 1
fi

Bash:
FROM debian:trixie

# Install all required packages for ISO manipulation and package management
RUN apt-get update && apt-get install -y \
    xorriso \
    isolinux \
    syslinux-utils \
    genisoimage \
    wget \
    curl \
    dpkg-dev \
    apt-utils \
    openssh-client \
    && rm -rf /var/lib/apt/lists/*

# Download and install proxmox-auto-install-assistant
RUN wget https://enterprise.proxmox.com/debian/proxmox-release-trixie.gpg -O /etc/apt/trusted.gpg.d/proxmox-release-trixie.gpg \
    && echo "deb http://download.proxmox.com/debian/pve trixie pve-no-subscription" > /etc/apt/sources.list.d/pve-install-repo.list \
    && apt-get update \
    && apt-get install -y proxmox-auto-install-assistant \
    && rm -rf /var/lib/apt/lists/*

# Copy build scripts
COPY download-packages.sh /workspace/download-packages.sh
COPY build-proxmox-iso.sh /workspace/build-proxmox-iso.sh

# Make scripts executable
RUN chmod +x /workspace/*.sh

# Set working directory
WORKDIR /workspace

# Default command
CMD ["/workspace/build-proxmox-iso.sh"]
 
Last edited:
O
I seperated my answer into a new answer so it looked cleaner
OK so the answer was :
I build a custom bootable iso from Proxmox Automated Installation, with more packages, and a first-boot hook that does some stuff.

And :
The first-boot hook fails silently when doing this :
zfs create zdata/var
rsync -avHPSAX /var/ /zdata/var/

Why do you need to overcomplicate, I spent 30mn in order to understand you :-)


Now, I invite you to add some debug in your script. For example :

zfs create zdata/var 2>&1 > /root/1st_boot_script_log.txt
find /zdata 2>&1 >> /root/1st_boot_script_log.txt
rsync -avHPSAX /var/ /zdata/var/ 2>&1 >> /root/1st_boot_script_log.txt

It will help.
 
O

OK so the answer was :
I build a custom bootable iso from Proxmox Automated Installation, with more packages, and a first-boot hook that does some stuff.

And :
The first-boot hook fails silently when doing this :
zfs create zdata/var
rsync -avHPSAX /var/ /zdata/var/

Why do you need to overcomplicate, I spent 30mn in order to understand you :-)


Now, I invite you to add some debug in your script. For example :

zfs create zdata/var 2>&1 > /root/1st_boot_script_log.txt
find /zdata 2>&1 >> /root/1st_boot_script_log.txt
rsync -avHPSAX /var/ /zdata/var/ 2>&1 >> /root/1st_boot_script_log.txt

It will help.
You are right. Thank you for the input. I will definitely try your debugging in my script. I havent had a chance to test it yet because I am currently trying to resolve a unmet dependency problem with xfce4 chromium/firefox-esr for my offline install now when I have cloned my friends repo to my machine. Spent some time on it since you last answered me. Worked fine when me and my friend worked on the offline install. So I am taking a step back to try to to understand more. As soon as I have solved it, I will try it :)
 
  • Like
Reactions: ghusson
Hi,

the much more easier solution than building your own ISO might be to use the first-boot script in combination with a separate named partition on the USB flash drive.

So the first-boot script could do something like:
Code:
mount -o ro /dev/disk/by-label/my-extra-pkgs /mnt
apt install -y /mnt/*.deb
umount /mnt

And the full flow would be:

1. Prepare first-boot script which mounts a partition by name and install all packages on it as above
2. Prepare auto-installation ISO with first-boot script
3. Write the prepared ISO to a USB flash drive
4. Use (g)parted/fdisk/gdisk/your favorite partitioning tool to create another partition on the USB flash drive
5. Create a new ext4 filesystem on the partition, e.g. mkfs.ext4 /dev/sdc5 -L my-extra-pkgs
6. Mount the new partition, write your packages to it
7. Boot the USB flash drive, after the installation, on the first boot, the packages should now be installed automatically

You can also create a feature request on our bugtracker, if you'd like to see proper support for additional packages: https://bugzilla.proxmox.com/
 
  • Like
Reactions: Johannes S
I feel so inadequate....

for years, I just run a postinstall script that sits on a nfs mount. yes, I could have done more automation but in the end it seemed the better part of valor to just type mount followed by /path/to/mount/postinstall.sh

maybe if I was doing 100 installs a day I'd look for more automation, but I have new installs every few months...
 
  • Like
Reactions: Johannes S
Hi,

the much more easier solution than building your own ISO might be to use the first-boot script in combination with a separate named partition on the USB flash drive.

So the first-boot script could do something like:
Code:
mount -o ro /dev/disk/by-label/my-extra-pkgs /mnt
apt install -y /mnt/*.deb
umount /mnt

And the full flow would be:

1. Prepare first-boot script which mounts a partition by name and install all packages on it as above
2. Prepare auto-installation ISO with first-boot script
3. Write the prepared ISO to a USB flash drive
4. Use (g)parted/fdisk/gdisk/your favorite partitioning tool to create another partition on the USB flash drive
5. Create a new ext4 filesystem on the partition, e.g. mkfs.ext4 /dev/sdc5 -L my-extra-pkgs
6. Mount the new partition, write your packages to it
7. Boot the USB flash drive, after the installation, on the first boot, the packages should now be installed automatically

You can also create a feature request on our bugtracker, if you'd like to see proper support for additional packages: https://bugzilla.proxmox.com/

O

OK so the answer was :
I build a custom bootable iso from Proxmox Automated Installation, with more packages, and a first-boot hook that does some stuff.

And :
The first-boot hook fails silently when doing this :
zfs create zdata/var
rsync -avHPSAX /var/ /zdata/var/

Why do you need to overcomplicate, I spent 30mn in order to understand you :-)


Now, I invite you to add some debug in your script. For example :

zfs create zdata/var 2>&1 > /root/1st_boot_script_log.txt
find /zdata 2>&1 >> /root/1st_boot_script_log.txt
rsync -avHPSAX /var/ /zdata/var/ 2>&1 >> /root/1st_boot_script_log.txt

It will help.
Forgot to update but its solved
The rsync mostly succeeds, set -e treats any non‑zero exit code as a fatal error and stops the script.
rsync often returns exit code 23
So adding
rsync -avz /var/ /zdata/var/ || true
Makes it work. I will post the full project here soon where vms are included in the autoinstall and then network setup. I will also try the suggestion from proxmox staff and post here.