--- title: "Cómo crear imágenes de servidor con Packer y QEMU" description: "Construye con Packer y QEMU una imagen qcow2 de Ubuntu 24.04 a partir de la cloud image oficial, aprovisiónala con scripts de shell y pruébala en local." category: "Automatización e IaC" language: es source: https://cubepath.com/es/docs/automatización-e-iac/construccion-de-imagenes-packer-para-servidores updated: 2026-09-25 --- # Cómo crear imágenes de servidor con Packer y QEMU Packer es una herramienta de HashiCorp que construye imágenes de máquina a partir de una plantilla en código: arranca una máquina temporal, la configura con scripts o herramientas como Ansible y guarda el resultado como imagen reutilizable. Así cada servidor nuevo parte de una base idéntica, ya actualizada y con tu software instalado, en lugar de configurarse a mano. En este tutorial usarás Packer con el builder QEMU para generar una imagen qcow2 de Ubuntu 24.04 a partir de la cloud image oficial de Canonical, y la arrancarás para comprobar que funciona. ## Requisitos previos Para seguir esta guía necesitas: - Un equipo o servidor con Ubuntu 24.04 LTS y un usuario no root con privilegios `sudo`. - Virtualización por hardware disponible (KVM). En un equipo físico suele bastar con tenerla activada en la BIOS; en un VPS depende de que el proveedor ofrezca virtualización anidada. El paso 1 te enseña a comprobarlo. - Al menos 4 GB de RAM y 15 GB libres en disco. - Conocimientos básicos de cloud-init, que se usa para dar acceso SSH a Packer durante la construcción. ## Paso 1: Comprobar KVM e instalar QEMU Packer arranca una máquina virtual durante la construcción. Sin KVM funcionaría con emulación por software, pero sería decenas de veces más lento. Instala `cpu-checker` y comprueba la aceleración: ```bash sudo apt update sudo apt install cpu-checker kvm-ok ``` ```output INFO: /dev/kvm exists KVM acceleration can be used ``` Si ves `KVM acceleration can NOT be used`, activa la virtualización en la BIOS o usa otra máquina antes de continuar. Instala QEMU, las utilidades de discos y `xorriso`, que Packer usa para generar la ISO de configuración de cloud-init: ```bash sudo apt install qemu-system-x86 qemu-utils xorriso ``` Añade tu usuario al grupo `kvm` para usar `/dev/kvm` sin `sudo` y vuelve a abrir la sesión: ```bash sudo usermod -aG kvm "$USER" ``` Tras volver a entrar, comprueba que perteneces al grupo: ```bash id -nG | grep -w kvm ``` ## Paso 2: Instalar Packer Instala Packer desde el repositorio oficial de HashiCorp. Descarga la clave del repositorio: ```bash sudo install -m 0755 -d /etc/apt/keyrings curl -fsSL https://apt.releases.hashicorp.com/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/hashicorp-archive-keyring.gpg ``` Añade el repositorio: ```bash echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/hashicorp-archive-keyring.gpg] https://apt.releases.hashicorp.com $(. /etc/os-release && echo "$VERSION_CODENAME") main" | sudo tee /etc/apt/sources.list.d/hashicorp.list ``` Instala Packer: ```bash sudo apt update sudo apt install packer ``` Comprueba la instalación: ```bash packer version ``` ```output Packer v1.14.2 ``` ## Paso 3: Preparar el proyecto Crea el directorio del proyecto con una carpeta para los scripts de aprovisionamiento: ```bash mkdir -p ~/packer-ubuntu/scripts && cd ~/packer-ubuntu ``` La cloud image de Ubuntu no tiene contraseña para ningún usuario: espera que cloud-init le inyecte claves o credenciales en el primer arranque. Packer aprovecha esto adjuntando un pequeño CD con etiqueta `cidata` (el formato NoCloud de cloud-init) que fija una contraseña temporal para el usuario `ubuntu`. Crea la plantilla de ese `user-data`: ```bash nano user-data.pkrtpl ``` ```yaml #cloud-config password: ${password} chpasswd: expire: false ssh_pwauth: true ``` `${password}` se sustituye en tiempo de construcción con el valor de una variable de Packer, así la contraseña no queda escrita en ningún archivo del proyecto. Al final de la construcción se bloquea esa contraseña, de modo que la imagen final no permite entrar con ella. ## Paso 4: Escribir la plantilla de Packer Crea la plantilla principal: ```bash nano ubuntu.pkr.hcl ``` ```hcl packer { required_plugins { qemu = { source = "github.com/hashicorp/qemu" version = ">= 1.1.0" } } } variable "build_password" { type = string sensitive = true } variable "image_name" { type = string default = "ubuntu-2404-base" } source "qemu" "ubuntu" { iso_url = "https://cloud-images.ubuntu.com/noble/current/noble-server-cloudimg-amd64.img" iso_checksum = "file:https://cloud-images.ubuntu.com/noble/current/SHA256SUMS" disk_image = true disk_size = "10G" format = "qcow2" disk_compression = true accelerator = "kvm" cpus = 2 memory = 2048 headless = true output_directory = "output-${var.image_name}" vm_name = "${var.image_name}.qcow2" cd_label = "cidata" cd_content = { "meta-data" = "instance-id: packer-build\nlocal-hostname: packer\n" "user-data" = templatefile("${path.root}/user-data.pkrtpl", { password = var.build_password }) } ssh_username = "ubuntu" ssh_password = var.build_password ssh_timeout = "15m" shutdown_command = "sudo shutdown -P now" } build { sources = ["source.qemu.ubuntu"] provisioner "shell" { execute_command = "chmod +x {{ .Path }}; sudo env {{ .Vars }} {{ .Path }}" environment_vars = ["DEBIAN_FRONTEND=noninteractive"] scripts = [ "scripts/base.sh", "scripts/cleanup.sh", ] } } ``` Las partes importantes: - `disk_image = true` indica que `iso_url` apunta a un disco ya instalado (la cloud image) y no a una ISO de instalación, así que no hace falta automatizar ningún instalador. - `iso_checksum = "file:..."` hace que Packer descargue el archivo `SHA256SUMS` oficial y verifique la imagen. - `disk_size` amplía el disco a 10 GB. cloud-init ampliará la partición raíz en el primer arranque de cada servidor. - `cd_content` genera la ISO `cidata` con `meta-data` y `user-data`; es lo que requiere `xorriso`. - `execute_command` ejecuta cada script como root con las variables de `environment_vars`, de modo que los scripts no necesitan `sudo` en cada línea. ## Paso 5: Escribir los scripts de aprovisionamiento El primer script espera a que cloud-init termine (si no, apt puede estar bloqueado), actualiza el sistema e instala los paquetes que quieres en todos tus servidores: ```bash nano scripts/base.sh ``` ```bash #!/usr/bin/env bash set -euo pipefail cloud-init status --wait apt-get update apt-get -y full-upgrade apt-get -y install qemu-guest-agent ufw fail2ban unattended-upgrades systemctl enable qemu-guest-agent ufw default deny incoming ufw default allow outgoing ufw allow OpenSSH ``` Las reglas de UFW quedan guardadas pero el cortafuegos no se activa aquí, para no cortar la conexión SSH de Packer. Actívalo en cada servidor con `sudo ufw enable` o desde tu herramienta de configuración. El segundo script deja la imagen limpia para que cada servidor creado a partir de ella se comporte como una instancia nueva: ```bash nano scripts/cleanup.sh ``` ```bash #!/usr/bin/env bash set -euo pipefail # Bloquear la contraseña temporal de la construcción passwd -l ubuntu # Quitar la configuración SSH con contraseña que generó cloud-init rm -f /etc/ssh/sshd_config.d/50-cloud-init.conf # Borrar claves de host SSH: cada servidor generará las suyas rm -f /etc/ssh/ssh_host_* # Vaciar el machine-id para que no se repita entre servidores truncate -s 0 /etc/machine-id # Reiniciar cloud-init para que vuelva a ejecutarse en el primer arranque cloud-init clean --logs --seed apt-get -y autoremove --purge apt-get clean ``` Borrar `machine-id` y las claves de host es imprescindible: si no, todos los servidores de la imagen tendrían la misma identidad y las mismas claves SSH. En Ubuntu 24.04, cloud-init regenera las claves de host y systemd un `machine-id` nuevo en el primer arranque. ## Paso 6: Inicializar y validar la plantilla Descarga el plugin de QEMU declarado en el bloque `packer`: ```bash packer init . ``` ```output Installed plugin github.com/hashicorp/qemu v1.1.4 in "/home/tu_usuario/.config/packer/plugins/github.com/hashicorp/qemu/packer-plugin-qemu_v1.1.4_x5.0_linux_amd64" ``` Da formato a los archivos y valida la sintaxis: ```bash packer fmt . packer validate -var "build_password=temporal" . ``` ```output The configuration is valid. ``` ## Paso 7: Construir la imagen Genera una contraseña aleatoria para la construcción y pásala como variable de entorno. Packer lee automáticamente las variables `PKR_VAR_`: ```bash export PKR_VAR_build_password="$(openssl rand -base64 18)" packer build . ``` La construcción descarga la cloud image (unos 600 MB), arranca la máquina, espera a SSH, ejecuta los scripts y apaga la máquina. Tarda entre 5 y 15 minutos según tu conexión y disco: ```output qemu.ubuntu: output will be in this color. ==> qemu.ubuntu: Retrieving ISO ==> qemu.ubuntu: Starting VM, booting disk image ==> qemu.ubuntu: Waiting for SSH to become available... ==> qemu.ubuntu: Connected to SSH! ==> qemu.ubuntu: Provisioning with shell script: scripts/base.sh ... ==> qemu.ubuntu: Provisioning with shell script: scripts/cleanup.sh ==> qemu.ubuntu: Gracefully halting virtual machine... ==> qemu.ubuntu: Converting hard drive... Build 'qemu.ubuntu' finished after 8 minutes 41 seconds. ==> Builds finished. The artifacts of successful builds are: --> qemu.ubuntu: VM files in directory: output-ubuntu-2404-base ``` Comprueba la imagen generada: ```bash qemu-img info output-ubuntu-2404-base/ubuntu-2404-base.qcow2 ``` ```output image: output-ubuntu-2404-base/ubuntu-2404-base.qcow2 file format: qcow2 virtual size: 10 GiB (10737418240 bytes) disk size: 1.1 GiB ... ``` ## Paso 8: Probar la imagen Arranca una copia de la imagen con cloud-init, tal como lo haría un proveedor, para confirmar que acepta tu clave SSH. Instala `cloud-image-utils`, que incluye `cloud-localds`: ```bash sudo apt install cloud-image-utils ``` Crea un disco de prueba que usa la imagen como base, sin modificarla: ```bash qemu-img create -f qcow2 -F qcow2 -b output-ubuntu-2404-base/ubuntu-2404-base.qcow2 prueba.qcow2 ``` Crea un `user-data` de prueba con tu clave pública. Si no tienes una, genérala con `ssh-keygen -t ed25519`: ```bash nano prueba-user-data.yaml ``` ```yaml #cloud-config ssh_authorized_keys: - ssh-ed25519 AAAA... tu_usuario@tu_equipo ``` Sustituye la línea por el contenido de tu `~/.ssh/id_ed25519.pub`. Genera la ISO de cloud-init y arranca la máquina con el puerto SSH redirigido al 2222 local: ```bash cloud-localds seed.iso prueba-user-data.yaml ``` ```bash qemu-system-x86_64 -enable-kvm -m 2048 -smp 2 -nographic \ -drive file=prueba.qcow2,if=virtio \ -drive file=seed.iso,format=raw,if=virtio \ -nic user,model=virtio-net-pci,hostfwd=tcp:127.0.0.1:2222-:22 ``` Cuando aparezca el prompt de login, conéctate desde otra terminal: ```bash ssh -p 2222 ubuntu@127.0.0.1 'systemctl is-enabled qemu-guest-agent; sudo ufw show added' ``` ```output enabled Added user rules (see 'ufw status' for running firewall): ufw allow OpenSSH ``` Apaga la máquina de prueba con `sudo poweroff` dentro de ella (o `Ctrl+A` y después `X` en la consola de QEMU) y borra los archivos de prueba: ```bash rm prueba.qcow2 seed.iso ``` ## Solución de problemas **`Waiting for SSH to become available...` hasta agotar el tiempo**: cloud-init no aplicó la contraseña. Comprueba que `xorriso` está instalado, que `cd_label` es exactamente `cidata` y que `user-data.pkrtpl` empieza por `#cloud-config`. Para ver la consola, cambia temporalmente `headless = false` en un equipo con escritorio o añade `-on-error=ask` a `packer build` para inspeccionar la máquina antes de que se borre. **`Could not access KVM kernel module: Permission denied`**: tu usuario no está en el grupo `kvm` en la sesión actual. Cierra sesión y vuelve a entrar. **`Could not get lock /var/lib/dpkg/lock-frontend`**: algún proceso de apt seguía activo. Asegúrate de que `base.sh` empieza con `cloud-init status --wait`. **Artefacto existente**: Packer se niega a sobrescribir `output-ubuntu-2404-base`. Bórralo o ejecuta `packer build -force .`. ## Conclusión Tienes una plantilla de Packer que genera, desde la cloud image oficial y verificada, una imagen qcow2 de Ubuntu 24.04 actualizada, con tus paquetes base y limpia para clonarse sin identidades repetidas. Como siguientes pasos, guarda la plantilla en Git y construye la imagen desde CI de forma periódica para tener siempre los parches al día, sustituye los scripts por el provisioner de Ansible cuando la configuración crezca, y sube la imagen resultante a tu hipervisor (por ejemplo, Proxmox con `qm importdisk`) o a tu proveedor.