前言
在 Proxmox VE 上创建虚拟机通常是一个重复劳动:挂载 ISO、手动安装系统、配置 SSH、装 QEMU Guest Agent……每次都要走一遍。
Pumba98/proxmox-packer-templates 这个开源项目用 HashiCorp Packer 把这一切自动化了。你只需要配置几个变量,一条命令就能生成一个开箱即用、Cloud-Init 就绪的 VM 模板。
这套教程围绕 Alpine Linux 最新版展开,讲清楚每一步是做什么、为什么这样做。即使你之前没用过 Packer,也能照着操作。
💡 本教程仅围绕 Alpine Linux,但同一项目也支持 Ubuntu、Debian、AlmaLinux、Rocky Linux、Windows Server 等多个系统。
项目结构速览
这个项目的设计思路非常清晰:
proxmox-packer-templates/
├── config.pkr.hcl # Packer 插件配置(声明要用哪些插件)
├── generic.pkr.hcl # 通用构建器(所有 OS 共享的构建逻辑)
├── variables.pkr.hcl # 全部可配置变量的定义(含默认值)
├── alpine-3.24.pkrvars.hcl # Alpine 3.24 的变量配置(ISO、启动命令、安装后脚本)
├── alpine-3.23.pkrvars.hcl # Alpine 3.23
├── alpine-3.22.pkrvars.hcl # Alpine 3.22
├── alpine-3.21.pkrvars.hcl # Alpine 3.21
├── http/alpine/answers # Alpine 安装器的自动应答文件
└── ...其他 OS 的 pkrvars.hcl核心思想:通用构建逻辑和 OS 特定配置分离。
generic.pkr.hcl定义了"如何创建一台 VM"——连 Proxmox API、分配 CPU/内存/磁盘、挂载 ISO、执行启动命令、等待 SSH 连接、运行 provisioner。- 各个
.pkrvars.hcl文件定义了"装哪个 ISO、怎么应答安装向导、装完后跑什么命令"。 http/alpine/answers是 Alpine 的setup-alpine安装脚本的自动应答文件,实现完全无人值守安装。
前置准备
在开始之前,你需要确认以下条件:
| 项目 | 要求 |
|---|---|
| Proxmox VE | 一台运行的 PVE(作者测试于 PVE 9.1.2,通常 7.x/8.x 也能用) |
| Packer CLI | 版本 1.14+(后面会装) |
| 能连 PVE | 你的电脑需要通过 HTTPS 访问 PVE 的管理口(通常是 8006 端口) |
| Proxmox 用户 | 有 API 权限的用户(家庭实验环境直接用 root@pam + 密码最简单) |
💡 认证方式怎么选? 家庭实验环境直接用
root@pam+ 登录密码即可,实测完全没问题,也是本文采用的方式。如果是生产环境或多人共用的 PVE,建议创建专用 API Token(PVE Web 界面 → 数据中心 → 权限 → API 令牌),仅授予VM.Template.Create、VM.Allocate等必要权限——Token 可以设过期时间,泄露后能单独撤销。
步骤 1:安装 Packer
如果你还没有 Packer,先安装它。
Ubuntu/Debian:
curl -fsSL https://apt.releases.hashicorp.com/gpg | sudo gpg --dearmor -o /usr/share/keyrings/hashicorp-archive-keyring.gpg
echo "deb [signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] https://apt.releases.hashicorp.com $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/hashicorp.list
sudo apt update && sudo apt install packermacOS:
brew tap hashicorp/tap
brew install hashicorp/tap/packer验证安装:
packer version步骤 2:下载项目
git clone https://github.com/Pumba98/proxmox-packer-templates.git
cd proxmox-packer-templates或者也可以直接下载 ZIP:
wget https://github.com/Pumba98/proxmox-packer-templates/archive/refs/heads/master.zip
unzip master.zip
cd proxmox-packer-templates-master步骤 3:初始化 Packer 插件
项目使用的 Proxmox 插件需要先下载:
packer init config.pkr.hcl这条命令会读取 config.pkr.hcl 中声明的 hashicorp/proxmox 插件并安装到本地。
步骤 4:配置变量(最关键的一步)
这一步是整个教程的核心,也是最容易出错的地方。你需要创建一个自己的连接配置文件,告诉 Packer 怎么连上你的 PVE。
每个变量都要填对,下面逐个说明它的值到哪里去找。
4.1 先搞清楚每个变量的值在哪里找
| 变量 | 含义 | 去哪里找 |
|---|---|---|
proxmox_host | PVE 的 IP + 端口 | 就是你平时打开 PVE 网页时浏览器地址栏里的地址,例如网页是 https://10.20.20.3:8006,这里就填 "10.20.20.3:8006"(不带 https://) |
proxmox_user | PVE 登录用户 | PVE 网页登录框里的用户名 + 领域。用 root 密码登录的就是 "root@pam" |
proxmox_password | 登录密码 | 你登录 PVE 网页用的那个密码 |
node | PVE 节点名 | ⚠️ 最容易填错!打开 PVE 网页,看左侧树状列表里「数据中心」下面那个名字,那才是节点名。它不一定叫 pve——比如我的就叫 local |
proxmox_insecure_tls | 跳过证书校验 | 固定填 true(原因见下面的踩坑记录) |
disk_storage_pool 等 | 存储池名 | PVE 网页左侧展开你的节点,下面列出的存储名称(如 local、local-lvm),见 4.2 |
vmid | 模板的 VM ID | 自己定一个没被占用的号,习惯上模板用 9000+;不填会自动分配 |
⚠️
node节点名千万别照抄教程写pve。节点名是装 PVE 时设置的主机名,每台机器都可能不一样。确认方法有两个:
- PVE Web 界面左侧「数据中心」下面显示的名字;
- SSH 到 PVE 上执行
cat /etc/hostname。填错节点名,构建时会报
unable to find node之类的错误。
4.2 存储池名怎么确认
在 PVE Web 界面左侧展开你的节点,能看到一个或多个存储(数据库图标),常见有两种情况:
- 只有一个
local(dir 类型):三个存储池全填"local",前提是它启用了Disk image、ISO image内容类型(点开该存储 → 内容 里能看到)。 - 有
local+local-lvm:ISO 只能放local,磁盘和 Cloud-Init 盘放local-lvm:
disk_storage_pool = "local-lvm"
cloud_init_storage_pool = "local-lvm"
iso_storage_pool = "local"4.3 创建连接配置文件
在项目目录下创建 credentials.pkrvars.hcl(把注释里说明的值换成你自己查到的):
cat > credentials.pkrvars.hcl << 'EOF'
# PVE 地址与端口(浏览器地址栏里的 IP:端口,不带 https://)
proxmox_host = "10.20.20.3:8006"
# 登录认证(和登录 PVE 网页用的一样)
proxmox_user = "root@pam"
proxmox_password = "你的PVE登录密码"
# PVE 节点名(看 Web 界面左侧「数据中心」下面的名字,不一定是 pve!我的是 local)
node = "local"
# 跳过自签名证书校验(必须加,否则报 TLS 错误,见下文踩坑记录)
proxmox_insecure_tls = true
# 存储池(按 4.2 查到的实际情况填)
disk_storage_pool = "local"
cloud_init_storage_pool = "local"
iso_storage_pool = "local"
# 可选:指定模板的 VM ID(不填自动分配)
# vmid = 9000
EOF💡 这个文件里有你的 PVE 密码,不要提交到 Git 或分享给别人。 更安全的做法是用 API Token(
proxmox_token = "packer@pve!token=xxxx..."替代proxmox_password),家庭环境用密码就够了。
4.3.1 实际成功的配置实例
下面是我实测构建成功(Alpine / Ubuntu 26.04 / Debian 13 三个模板全部通过)时用的真实配置,环境是:PVE 地址 10.20.20.3:8006、节点名 local、单 local dir 存储池:
# Proxmox 节点地址与端口
proxmox_host = "10.20.20.3:8006"
# 认证信息(root@pam + 登录密码)
proxmox_user = "root@pam"
proxmox_password = "********" # 换成你自己的 PVE 登录密码
# PVE 节点名称(我的 PVE 左侧「数据中心」下显示的是 local,不是 pve)
node = "local"
# 跳过 TLS 证书校验(注意变量名前缀是 proxmox_,一个字母都不能少)
proxmox_insecure_tls = true
# 存储池设置(我只有一个 local dir 存储池,三个全填 local)
disk_storage_pool = "local"
cloud_init_storage_pool = "local"
iso_storage_pool = "local"照着这个格式,把地址、密码、节点名、存储池换成你在 4.1、4.2 里查到的值即可。
4.4 踩坑记录:TLS 证书报错(几乎必遇)
如果没写 proxmox_insecure_tls = true,构建会秒退并报错:
Build 'build.proxmox-iso.vm' errored after 51 milliseconds:
Post "https://10.20.20.3:8006/api2/json/access/ticket":
tls: failed to verify certificate: x509: certificate is not trusted原因是 PVE 用的是自签名 HTTPS 证书,Packer 默认会严格校验证书,通不过就直接失败。
关键点:这个项目里跳过证书校验的变量名是 proxmox_insecure_tls,不是别的。 我当时踩了好几个坑才找对:
- ❌ 写
insecure_skip_tls_verify = true→ 无效,这是 Packer 插件底层的参数名,不是本项目暴露的变量名 - ❌ 写
proxmox_skip_tls_verify = true→ 无效,变量名对不上,Packer 静默忽略 - ❌ 命令行传
-var "proxmox_insecure_skip_tls_verify=true"→ 直接报Undefined -var variable - ❌ 去改
generic.pkr.hcl手动加insecure_skip_tls_verify→ 报Attribute redefined,因为项目第 25 行本来就有这行 - ✅ 唯一正确做法:在你的 pkrvars 文件里写
proxmox_insecure_tls = true
为什么是这个名字? 打开项目的 generic.pkr.hcl,能看到这一行:
insecure_skip_tls_verify = var.proxmox_insecure_tls右边的 var.proxmox_insecure_tls 才是项目作者定义、允许你在 pkrvars 文件里赋值的变量名。以后遇到类似「不知道该用什么变量名」的情况,通用排查方法就是:去 generic.pkr.hcl 里搜插件参数名,看它等号右边 var. 后面跟的是什么,或者直接翻 variables.pkr.hcl 看全部变量定义。
⚠️ 另一个低级但致命的坑:拼写错误。 我有一次把变量写成了
roxmox_insecure_tls(少了开头的p),Packer 对认不出来的变量不报错、直接忽略,结果 TLS 错误照旧。改完配置还是报同样的错时,先逐字检查变量名有没有打错。
💡 走代理的用户注意:如果你的终端开着代理(HTTP_PROXY/HTTPS_PROXY),访问内网 PVE 的请求可能被代理接管导致证书错误。先执行
export no_proxy="10.20.20.3,localhost,127.0.0.1"(换成你的 PVE IP)或临时unset http_proxy https_proxy HTTP_PROXY HTTPS_PROXY再构建。
方法 B(可选):环境变量
不想把密码写进文件?用环境变量,Packer 会自动读取 PKR_VAR_ 开头的变量:
export PKR_VAR_proxmox_host="10.20.20.3:8006"
export PKR_VAR_proxmox_user="root@pam"
export PKR_VAR_proxmox_password="你的密码"
export PKR_VAR_node="local"
export PKR_VAR_proxmox_insecure_tls=true步骤 5:选择 Alpine 版本
项目根目录下有多个 Alpine 版本的 .pkrvars.hcl 文件:
alpine-3.24.pkrvars.hcl✅ 最新版(推荐)alpine-3.23.pkrvars.hclalpine-3.22.pkrvars.hclalpine-3.21.pkrvars.hcl
Alpine 镜像选的是 alpine-virt 类型——这是最精简的 Alpine 变体,适合虚拟机场景,没有图形界面和无关驱动。
🔍
alpine-3.24.pkrvars.hcl里引用的是alpine-virt-3.24.1-x86_64.iso,镜像从dl-cdn.alpinelinux.org官方 CDN 下载。
步骤 6:执行构建
一切就绪后,执行构建命令:
packer build -var-file="credentials.pkrvars.hcl" -var-file="alpine-3.24.pkrvars.hcl" .⚠️ 注意最后的
.不能丢,它表示在当前目录找generic.pkr.hcl作为构建定义。
命令拆解:
-var-file="credentials.pkrvars.hcl"—— 你的 Proxmox 连接信息(步骤 4 创建的)-var-file="alpine-3.24.pkrvars.hcl"—— Alpine 3.24 的 ISO、启动命令、安装后脚本.—— Packer 构建源文件目录
构建过程中你会看到类似这样的输出:
proxmox-iso.vm: output will be in this color.
==> proxmox-iso.vm: Starting VM
==> proxmox-iso.vm: Starting HTTP server on 127.0.0.1:8178
==> proxmox-iso.vm: typing boot command...
==> proxmox-iso.vm: Waiting for SSH to become available...
==> proxmox-iso.vm: Connected via SSH!
==> proxmox-iso.vm: Provisioning with shell script: /tmp/packer-shell...
proxmox-iso.vm: (1/1) Installing cloud-init
proxmox-iso.vm: (2/1) Installing ...
==> proxmox-iso.vm: Converting VM to template...
Build 'proxmox-iso.vm' finished after 2 minutes 34 seconds.看到 Converting VM to template... 并成功结束,说明 Alpine VM 模板已经创建好了。
💡 第一构建会比较慢(约 3~5 分钟),因为需要下载 ISO、等待 Alpine 自动安装、再跑安装后脚本。后续构建如果有缓存会快很多。
步骤 7:验证模板
到 Proxmox Web 界面确认:
- 登录 PVE Web 界面
- 在左侧节点下找到你指定的 VM ID(比如
9000) - 你会看到状态显示为 Template(模板图标)
- 点进 Hardware 可以看到:1 个 vCPU / 2GB 内存 / SCSI 磁盘
你也可以用 qm 命令验证:
ssh root@pve qm list输出应该看到 9000 这个 VM,且状态是 running(template 实际上是 stopped,但显示为模板状态)。
🔍 模板里默认装了:
qemu-guest-agent(QEMU 通信代理)、cloud-init(云初始化)、sudo,并创建了wheel组带免密码 sudo。
步骤 8:从模板创建新 VM
有了模板,创建新 VM 只需要几秒钟:
方式一:PVE Web 界面
- 选中模板 VM → 右键 Clone
- 填写新 VM 名称和 ID
- 选择 Full Clone(完整克隆)或 Linked Clone(链接克隆)
- 点击 Clone
方式二:命令行
qm clone 9000 101 --name my-alpine-vm --full配置 Cloud-Init
克隆出来的 VM 需要配置 Cloud-Init 才能获取 IP 和 SSH 密钥:
- 在 PVE Web 上选中新 VM → Cloud-Init 选项卡
- 设置:
- User —— 默认的登录用户名(如
alpine) - Password —— 登录密码
- SSH public key —— 贴上你的 SSH 公钥
- User —— 默认的登录用户名(如
- 点 Regenerate Image
- 回到 Hardware,给 VM 加上 Cloud-Init 驱动(如果还没加的话)
⚠️ 注意:Alpine 默认 root 账号是被锁定的(
passwd -l root),这是安全设计。你需要通过 Cloud-Init 设置一个普通用户,或者开启 root 密码登录。
首次启动
qm start 101
# 等一会看看 IP
qm guest exec 101 -- ip addr show后台原理:Packer 到底做了什么?
理解原理有助于你排查问题或自定义。整个过程分阶段:
阶段 1:启动 VM 并挂载 ISO
Packer 通过 Proxmox API 创建一个新 VM,挂载 Alpine ISO,设置从 ISO 启动。
阶段 2:自动按键安装(Boot Command)
Packer 模拟键盘输入,执行一系列按键序列(定义在 alpine-3.24.pkrvars.hcl 的 boot_command 中):
- 以
root登录(Alpine Live 环境的默认 root 无密码) - 启用网卡并获取 DHCP IP
- 从 Packer 的内置 HTTP 服务器下载
answers应答文件 - 执行
setup-alpine -f answers自动安装到磁盘 - 重启后依次设置
qemu-guest-agent、sudo,创建packer用户
阶段 3:Provisioner(安装后脚本)
SSH 连入新安装的系统,执行 provisioner 列表中的命令:
provisioner = [
"apk add --no-cache cloud-init util-linux e2fsprogs-extra",
"setup-cloud-init",
"echo \"datasource_list: ['NoCloud']\" > /etc/cloud/cloud.cfg.d/02-datasource.cfg",
"echo \"PasswordAuthentication yes\" > /etc/ssh/sshd_config.d/01-password-auth.conf",
"passwd -l root",
"deluser --remove-home packer"
]这一步安装了 cloud-init,配置从 NoCloud 数据源读取初始化信息,锁定 root 账号,删除临时创建的 packer 用户。
阶段 4:转换为模板
所有操作完成后,Packer 调用 Proxmox API 将 VM 转换为模板。从此你可以从这个模板快速克隆任意数量的 Alpine VM。
常见问题
Q:构建报 TLS 证书错误,秒退?
见上文 4.4 踩坑记录。一句话:在你的 credentials.pkrvars.hcl 里加 proxmox_insecure_tls = true,注意变量名一个字母都不能错。
Q:构建过程中 PVE 界面显示"未找到 CloudInit 驱动器"?
这是正常现象,不用管。 Packer 此时刚拉起 VM,正挂着原生 ISO 做系统安装,Cloud-Init 盘是构建的最后一步(Adding a cloud-init cdrom)才附加的。耐心等终端打印 Converting VM to template 和 Build finished,再刷新 PVE 界面就能看到模板带着 Cloud-Init 驱动器了。
Q:构建时卡在 “Waiting for SSH to become available…”
Alpine 的安装和重启需要时间,这是正常的。等待时间取决于你的硬件性能。如果等了超过 10 分钟,可能是:
- ISO 下载失败(检查网络)
boot_command按键序列和实际安装界面不匹配answers文件中的网络配置不对
Q:能不能自定义模板参数(CPU、内存、磁盘大小)?
可以。在 credentials.pkrvars.hcl 中覆盖即可:
cpu_cores = 4
memory = 4096
disk_size = "20G"
disk_storage_pool = "local-lvm"Q:生成的模板最小磁盘是多大?
默认 5G。如果你想缩小,可以改 disk_size = "2G",但注意 Alpine 安装本身约占 1~2GB。
Q:Alpine 版本更新了怎么办?
项目作者用 Renovate Bot 自动检测 Alpine 新版。你也可以手动改 alpine-3.24.pkrvars.hcl 里的 iso_url 和 iso_checksum 指向新版 ISO。
Q:PVE 和构建机不在同一网络怎么处理?
如果 PVE 在你的数据中心,构建机在本机,PVE 不能直接访问你机器的 HTTP 服务器来获取 answers 文件。项目 README 里提供了两种 SSH 端口转发方案:
- 如果你的电脑能 SSH 到 PVE:
ssh -N -R 127.0.0.1:8000:127.0.0.1:8000 root@pve - 同时设置
packer_http_interface = "127.0.0.1"和packer_http_port = 8000
Q:想再装其他系统的模板(Ubuntu、Debian……)?
连接配置一个字都不用改,只换第二个 -var-file 即可。比如实测过的 Ubuntu 26.04 和 Debian 13:
packer build -var-file="credentials.pkrvars.hcl" -var-file="ubuntu-26.04.pkrvars.hcl" .
packer build -var-file="credentials.pkrvars.hcl" -var-file="debian-13.pkrvars.hcl" .项目根目录 ls *.pkrvars.hcl 就能看到全部可选系统。注意给每个模板分配不同的 vmid(或者留空自动分配),Ubuntu/Debian 的构建时间比 Alpine 长不少(要下载更大的 ISO、跑完整安装器),耐心等即可。
总结
到这里你已经学会了:
- ✅ 使用 Packer 在 Proxmox 上自动化构建 Alpine Linux VM 模板
- ✅ 每个连接变量(地址、用户、节点名、存储池)到 PVE 界面哪里去查
- ✅ 解决几乎必遇的自签名证书 TLS 报错(
proxmox_insecure_tls = true) - ✅ 理解 Packer 的构建流程:ISO → 自动安装 → 配置 → 模板
- ✅ 从模板快速克隆并配置 Cloud-Init
- ✅ 换一个
.pkrvars.hcl文件就能构建 Ubuntu、Debian 等其他系统模板
这个流程最大的价值在于可重复和版本化。以后不管是要部署多台 VM,还是需要统一的镜像基线,用 Packer 构建模板都比手动安装可靠得多。
项目地址:github.com/Pumba98/proxmox-packer-templates
实战验证
本教程的步骤在两套真实环境里都完整跑通过:
实测一:Linux 构建机 + Alpine 3.24
环境:
- Proxmox VE 9.1.7,单节点
- Packer 1.14.0(Proxmox Plugin v1.2.3)
- Alpine
alpine-virt-3.24.1-x86_64.iso - VM 规格:1 vCPU / 1GB 内存 / 2GB 磁盘
构建耗时: 约 2 分钟
构建日志关键片段:
build.proxmox-iso.vm: Creating VM
build.proxmox-iso.vm: Starting VM
build.proxmox-iso.vm: Starting HTTP server on port 8910
build.proxmox-iso.vm: Waiting 10s for boot
build.proxmox-iso.vm: Typing the boot command
build.proxmox-iso.vm: Waiting for SSH to become available...
build.proxmox-iso.vm: Connected to SSH!
build.proxmox-iso.vm: Provisioning with shell script...
build.proxmox-iso.vm: (153/153) Installing findmnt
build.proxmox-iso.vm: OK: 144.1 MiB in 227 packages
build.proxmox-iso.vm: Stopping VM
build.proxmox-iso.vm: Converting VM to template
build.proxmox-iso.vm: Adding a cloud-init cdrom in storage pool local
Build 'build.proxmox-iso.vm' finished after 1 minute 57 seconds.
A template was created: 922模板内容:
- Alpine Linux 3.24(virt 精简版)
- QEMU Guest Agent 已安装并启用
- Cloud-Init 已安装并配置(NoCloud 数据源)
sudo已安装,wheel组免密码 sudo- root 账号已锁定(安全)
- SSH 密码认证已开启
- Cloud-Init CDROM 已附加
实测二:macOS 构建机 + Ubuntu 26.04 / Debian 13
环境:
- 构建机:MacBook Air(Apple Silicon,macOS + Homebrew 安装的 Packer,Proxmox Plugin v1.2.4)
- Proxmox VE:
10.20.20.3:8006,节点名local(就是这台机器让我发现节点名不一定叫pve) - 认证:
root@pam+ 密码 - 存储:单
localdir 存储池,三个 storage_pool 全填"local"
这一轮实测踩过的所有坑(正是本文 4.4 节踩坑记录的来源):
- 首次构建报
tls: failed to verify certificate: x509 certificate is not trusted—— 自签名证书问题 - 依次试了
insecure_skip_tls_verify、proxmox_skip_tls_verify、命令行-var传参、手改generic.pkr.hcl,全部失败(分别是变量名不对、Undefined -var variable、Attribute redefined) - 最终在
generic.pkr.hcl里查到insecure_skip_tls_verify = var.proxmox_insecure_tls,确认正确变量名是proxmox_insecure_tls - 改对变量名后还报错——检查发现手滑打成了
roxmox_insecure_tls,少个p,Packer 静默忽略不报错 - 修正拼写后一次构建成功;构建中途 PVE 显示"未找到 CloudInit 驱动器"是正常过程,收尾阶段自动附加
- Ubuntu 26.04 模板构建成功后,复用同一个
credentials.pkrvars.hcl、只换 var-file 又构建了 Debian 13 模板,零改动直接成功
两轮实测覆盖了 Linux 和 macOS 构建机、密码和 Token 两种认证、Alpine/Ubuntu/Debian 三种系统——按本文步骤操作即可复现。