$ cd /var/lib/vz/template/iso
-weight: 500;">wget https://cloud-images.ubuntu.com/releases/noble/release/ubuntu-24.04-server-cloudimg-amd64.img
cd /var/lib/vz/template/iso
-weight: 500;">wget https://cloud-images.ubuntu.com/releases/noble/release/ubuntu-24.04-server-cloudimg-amd64.img
cd /var/lib/vz/template/iso
-weight: 500;">wget https://cloud-images.ubuntu.com/releases/noble/release/ubuntu-24.04-server-cloudimg-amd64.img
ls -lah /var/lib/vz/template/iso/ | grep ubuntu
ls -lah /var/lib/vz/template/iso/ | grep ubuntu
ls -lah /var/lib/vz/template/iso/ | grep ubuntu
qm create 501 \ --name ubuntu-template \ --memory 2048 \ --cores 4 \ --net0 virtio,bridge=vmbr0 \ --scsihw virtio-scsi-pci \ --ostype l26 \ --agent enabled=1 \ --vga serial0 \ --serial0 socket
qm create 501 \ --name ubuntu-template \ --memory 2048 \ --cores 4 \ --net0 virtio,bridge=vmbr0 \ --scsihw virtio-scsi-pci \ --ostype l26 \ --agent enabled=1 \ --vga serial0 \ --serial0 socket
qm create 501 \ --name ubuntu-template \ --memory 2048 \ --cores 4 \ --net0 virtio,bridge=vmbr0 \ --scsihw virtio-scsi-pci \ --ostype l26 \ --agent enabled=1 \ --vga serial0 \ --serial0 socket
qm set 501 --scsi0 local-zfs:0,import-from=/var/lib/vz/template/iso/ubuntu-24.04-server-cloudimg-amd64.img
qm set 501 --scsi0 local-zfs:0,import-from=/var/lib/vz/template/iso/ubuntu-24.04-server-cloudimg-amd64.img
qm set 501 --scsi0 local-zfs:0,import-from=/var/lib/vz/template/iso/ubuntu-24.04-server-cloudimg-amd64.img
qm resize 501 scsi0 +60G
qm resize 501 scsi0 +60G
qm resize 501 scsi0 +60G
qm set 501 --ide2 local-zfs:cloudinit
qm set 501 --ide2 local-zfs:cloudinit
qm set 501 --ide2 local-zfs:cloudinit
qm set 501 --boot order=scsi0
qm set 501 --boot order=scsi0
qm set 501 --boot order=scsi0
qm template 501
qm template 501
qm template 501
cd /var/lib/vz/snippets
nano cloud-init-config.yaml
cd /var/lib/vz/snippets
nano cloud-init-config.yaml
cd /var/lib/vz/snippets
nano cloud-init-config.yaml
#cloud-config
users: - name: kaf <-- Swap this with your own username groups: users, admin, -weight: 500;">docker -weight: 600;">sudo: ALL=(ALL) NOPASSWD:ALL shell: /bin/bash ssh_authorized_keys: - ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI... <-- Your public ssh key here packages: - fail2ban - ufw - ca-certificates - -weight: 500;">curl - gnupg - lsb-release - qemu-guest-agent package_update: true
package_upgrade: true write_files: - path: /etc/ssh/sshd_config.d/ssh-hardening.conf content: | PermitRootLogin no PasswordAuthentication no Port 22 KbdInteractiveAuthentication no ChallengeResponseAuthentication no MaxAuthTries 2 AllowTcpForwarding no X11Forwarding no AllowAgentForwarding no AuthorizedKeysFile .ssh/authorized_keys AllowUsers kaf <-- Swap this with your own username - path: /etc/-weight: 500;">docker/daemon.json content: | { "log-driver": "json-file", "log-opts": { "max-size": "10m", "max-file": "3" } } runcmd: # Fail2Ban setup - printf "[sshd]\nenabled = true\nport = ssh, 22\nbanaction = iptables-multiport" > /etc/fail2ban/jail.local - -weight: 500;">systemctl -weight: 500;">enable fail2ban - -weight: 500;">systemctl -weight: 500;">start fail2ban # UFW Firewall - ufw allow 22/tcp - ufw allow 80/tcp - ufw allow 443/tcp - ufw -weight: 500;">enable # Docker Installation (Docker v29 - DEB822 format) - mkdir -p /etc/-weight: 500;">apt/keyrings - -weight: 500;">curl -fsSL https://download.-weight: 500;">docker.com/linux/ubuntu/gpg -o /etc/-weight: 500;">apt/keyrings/-weight: 500;">docker.asc - chmod a+r /etc/-weight: 500;">apt/keyrings/-weight: 500;">docker.asc - | tee /etc/-weight: 500;">apt/sources.list.d/-weight: 500;">docker.sources > /dev/null <<EOF Types: deb URIs: https://download.-weight: 500;">docker.com/linux/ubuntu Suites: $(. /etc/os-release && echo "${UBUNTU_CODENAME:-$VERSION_CODENAME}") Components: stable Signed-By: /etc/-weight: 500;">apt/keyrings/-weight: 500;">docker.asc EOF - -weight: 500;">apt-get -weight: 500;">update - -weight: 500;">apt-get -weight: 500;">install -y -weight: 500;">docker-ce -weight: 500;">docker-ce-cli containerd.io -weight: 500;">docker-buildx-plugin -weight: 500;">docker-compose-plugin # Enable Docker daemon - -weight: 500;">systemctl -weight: 500;">enable -weight: 500;">docker - -weight: 500;">systemctl -weight: 500;">start -weight: 500;">docker # Add user to -weight: 500;">docker group - usermod -aG -weight: 500;">docker kaf <-- Swap this with your own username
#cloud-config
users: - name: kaf <-- Swap this with your own username groups: users, admin, -weight: 500;">docker -weight: 600;">sudo: ALL=(ALL) NOPASSWD:ALL shell: /bin/bash ssh_authorized_keys: - ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI... <-- Your public ssh key here packages: - fail2ban - ufw - ca-certificates - -weight: 500;">curl - gnupg - lsb-release - qemu-guest-agent package_update: true
package_upgrade: true write_files: - path: /etc/ssh/sshd_config.d/ssh-hardening.conf content: | PermitRootLogin no PasswordAuthentication no Port 22 KbdInteractiveAuthentication no ChallengeResponseAuthentication no MaxAuthTries 2 AllowTcpForwarding no X11Forwarding no AllowAgentForwarding no AuthorizedKeysFile .ssh/authorized_keys AllowUsers kaf <-- Swap this with your own username - path: /etc/-weight: 500;">docker/daemon.json content: | { "log-driver": "json-file", "log-opts": { "max-size": "10m", "max-file": "3" } } runcmd: # Fail2Ban setup - printf "[sshd]\nenabled = true\nport = ssh, 22\nbanaction = iptables-multiport" > /etc/fail2ban/jail.local - -weight: 500;">systemctl -weight: 500;">enable fail2ban - -weight: 500;">systemctl -weight: 500;">start fail2ban # UFW Firewall - ufw allow 22/tcp - ufw allow 80/tcp - ufw allow 443/tcp - ufw -weight: 500;">enable # Docker Installation (Docker v29 - DEB822 format) - mkdir -p /etc/-weight: 500;">apt/keyrings - -weight: 500;">curl -fsSL https://download.-weight: 500;">docker.com/linux/ubuntu/gpg -o /etc/-weight: 500;">apt/keyrings/-weight: 500;">docker.asc - chmod a+r /etc/-weight: 500;">apt/keyrings/-weight: 500;">docker.asc - | tee /etc/-weight: 500;">apt/sources.list.d/-weight: 500;">docker.sources > /dev/null <<EOF Types: deb URIs: https://download.-weight: 500;">docker.com/linux/ubuntu Suites: $(. /etc/os-release && echo "${UBUNTU_CODENAME:-$VERSION_CODENAME}") Components: stable Signed-By: /etc/-weight: 500;">apt/keyrings/-weight: 500;">docker.asc EOF - -weight: 500;">apt-get -weight: 500;">update - -weight: 500;">apt-get -weight: 500;">install -y -weight: 500;">docker-ce -weight: 500;">docker-ce-cli containerd.io -weight: 500;">docker-buildx-plugin -weight: 500;">docker-compose-plugin # Enable Docker daemon - -weight: 500;">systemctl -weight: 500;">enable -weight: 500;">docker - -weight: 500;">systemctl -weight: 500;">start -weight: 500;">docker # Add user to -weight: 500;">docker group - usermod -aG -weight: 500;">docker kaf <-- Swap this with your own username
#cloud-config
users: - name: kaf <-- Swap this with your own username groups: users, admin, -weight: 500;">docker -weight: 600;">sudo: ALL=(ALL) NOPASSWD:ALL shell: /bin/bash ssh_authorized_keys: - ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI... <-- Your public ssh key here packages: - fail2ban - ufw - ca-certificates - -weight: 500;">curl - gnupg - lsb-release - qemu-guest-agent package_update: true
package_upgrade: true write_files: - path: /etc/ssh/sshd_config.d/ssh-hardening.conf content: | PermitRootLogin no PasswordAuthentication no Port 22 KbdInteractiveAuthentication no ChallengeResponseAuthentication no MaxAuthTries 2 AllowTcpForwarding no X11Forwarding no AllowAgentForwarding no AuthorizedKeysFile .ssh/authorized_keys AllowUsers kaf <-- Swap this with your own username - path: /etc/-weight: 500;">docker/daemon.json content: | { "log-driver": "json-file", "log-opts": { "max-size": "10m", "max-file": "3" } } runcmd: # Fail2Ban setup - printf "[sshd]\nenabled = true\nport = ssh, 22\nbanaction = iptables-multiport" > /etc/fail2ban/jail.local - -weight: 500;">systemctl -weight: 500;">enable fail2ban - -weight: 500;">systemctl -weight: 500;">start fail2ban # UFW Firewall - ufw allow 22/tcp - ufw allow 80/tcp - ufw allow 443/tcp - ufw -weight: 500;">enable # Docker Installation (Docker v29 - DEB822 format) - mkdir -p /etc/-weight: 500;">apt/keyrings - -weight: 500;">curl -fsSL https://download.-weight: 500;">docker.com/linux/ubuntu/gpg -o /etc/-weight: 500;">apt/keyrings/-weight: 500;">docker.asc - chmod a+r /etc/-weight: 500;">apt/keyrings/-weight: 500;">docker.asc - | tee /etc/-weight: 500;">apt/sources.list.d/-weight: 500;">docker.sources > /dev/null <<EOF Types: deb URIs: https://download.-weight: 500;">docker.com/linux/ubuntu Suites: $(. /etc/os-release && echo "${UBUNTU_CODENAME:-$VERSION_CODENAME}") Components: stable Signed-By: /etc/-weight: 500;">apt/keyrings/-weight: 500;">docker.asc EOF - -weight: 500;">apt-get -weight: 500;">update - -weight: 500;">apt-get -weight: 500;">install -y -weight: 500;">docker-ce -weight: 500;">docker-ce-cli containerd.io -weight: 500;">docker-buildx-plugin -weight: 500;">docker-compose-plugin # Enable Docker daemon - -weight: 500;">systemctl -weight: 500;">enable -weight: 500;">docker - -weight: 500;">systemctl -weight: 500;">start -weight: 500;">docker # Add user to -weight: 500;">docker group - usermod -aG -weight: 500;">docker kaf <-- Swap this with your own username
qm clone 501 106 --name ubuntu-vm02
qm clone 501 106 --name ubuntu-vm02
qm clone 501 106 --name ubuntu-vm02
qm set 106 --cicustom "user=local:snippets/cloud-init-config.yaml"
qm set 106 --cicustom "user=local:snippets/cloud-init-config.yaml"
qm set 106 --cicustom "user=local:snippets/cloud-init-config.yaml"
qm set 106 --ipconfig0 ip=10.160.0.61/24,gw=10.160.0.1
qm set 106 --ipconfig0 ip=10.160.0.61/24,gw=10.160.0.1
qm set 106 --ipconfig0 ip=10.160.0.61/24,gw=10.160.0.1
qm set 106 --ipconfig0 ip=dhcp
qm set 106 --ipconfig0 ip=dhcp
qm set 106 --ipconfig0 ip=dhcp
qm -weight: 500;">start 106
qm -weight: 500;">start 106
qm -weight: 500;">start 106
-weight: 600;">sudo cat /var/log/cloud-init.log | grep -i error
-weight: 600;">sudo cat /var/log/cloud-init.log | grep -i error
-weight: 600;">sudo cat /var/log/cloud-init.log | grep -i error
-weight: 600;">sudo tail -100 /var/log/cloud-init-output.log
-weight: 600;">sudo tail -100 /var/log/cloud-init-output.log
-weight: 600;">sudo tail -100 /var/log/cloud-init-output.log - Store YAML templates in Proxmox
- Reference them by path when cloning VMs
- Reuse the same config across multiple clones
- Easily -weight: 500;">update configurations in one place - SSH hardening (no root login, no password auth)
- Fail2Ban (brute-force protection)
- UFW firewall (allows SSH, HTTP, HTTPS)
- Docker (latest version with proper logging)
- User with -weight: 600;">sudo access and SSH key authentication - Faster: No need to generate ISOs—just reference a file
- Reusable: Same YAML for multiple clones
- Maintainable: Update one file, all new clones use the latest config
- Cleaner: No ISO files cluttering your storage
- Flexible: Mix and match different YAML templates for different VM roles - SSH keys only (no password logins)
- SSH hardening config applied
- Fail2Ban running to prevent brute-force attacks
- UFW firewall enabled
- Docker installed and ready to use