Problema

Mantener un homelab en casa implica crear máquinas virtuales, instalar servicios y actualizar configuraciones de forma manual. Cada vez que se añade un nuevo contenedor o se cambia una versión de Ubuntu, el proceso se vuelve repetitivo y propenso a errores. El patrón típico es:

  1. Provisión de VMs en Proxmox (a menudo con cloud‑init).
  2. Instalación de Docker y despliegue de stacks mediante docker-compose.yml.
  3. Configuración de accesos externos (por ejemplo, Cloudflare Tunnel).

Sin una capa de automatización, reproducir el entorno en otro nodo o restaurarlo después de un fallo requiere volver a ejecutar cada paso a mano. Además, la gestión de secretos y la separación entre entornos de desarrollo y producción suele quedar a medias, lo que complica la evolución del laboratorio.

Causa

Los principales factores que generan esta fricción son:

  • Ausencia de definición declarativa: los recursos se crean con clicks en la UI de Proxmox o con scripts ad‑hoc, lo que impide versionar la infraestructura.
  • Descoordinación entre IaC y despliegue de contenedores: Terraform puede crear la VM, pero el stack Docker se lanza manualmente, rompiendo la cadena de entrega continua.
  • Gestión de secretos dispersa: claves de API, tokens de Cloudflare y credenciales de bases de datos se guardan en archivos locales sin cifrado ni control de acceso.
  • Flujos de CI/CD poco estructurados: algunos usuarios usan solo “push → deploy” sin revisiones, mientras que otros requieren aprobaciones manuales, lo que lleva a decisiones inconsistentes.

Estas causas aparecen tanto en entornos de producción ligera como en laboratorios personales, y la solución debe ser lo suficientemente genérica para adaptarse a ambos.

Solución

Una arquitectura basada en tres capas permite cubrir todo el ciclo de vida:

  1. IaC con Terraform/OpenTofu para describir la infraestructura de Proxmox (hosts, redes, discos).
  2. Ansible para configurar el SO de la VM (instalar Docker, crear usuarios, aplicar hardening) y para desplegar los archivos docker‑compose.yml.
  3. Pipeline CI/CD (GitHub Actions, Gitea Actions o similar) que orquesta el flujo:
    • Detecta cambios en el repositorio.
    • Ejecuta terraform apply en modo plan‑only para validar.
    • Si la validación pasa, dispara Ansible contra la VM objetivo.
    • Opcionalmente, solicita aprobación manual antes del despliegue a producción.

Herramientas recomendadas

Capa Herramienta Por qué
IaC Terraform (o OpenTofu) Provider oficial para Proxmox, estado remoto fácil de almacenar en Git.
Config Ansible Idempotente, sin agente, excelente para gestionar Docker y archivos de composición.
CI/CD GitHub Actions (o Gitea Actions) Integración nativa con repositorios, permite secret management con SOPS.
Secrets SOPS + Git‑crypt o HashiCorp Vault (modo dev) Cifrado de archivos YAML/JSON, integración directa con GitHub Secrets.

Flujo típico

  1. Repositorio Git contiene:
    • terraform/ con módulos Proxmox.
    • ansible/ con playbooks y plantillas docker-compose.yml.
    • sops/ con archivos cifrados de secretos.
  2. Push a rama main dispara el workflow.
  3. Job “plan” ejecuta terraform plan y publica el plan como artefacto.
  4. Job “approve” (opcional) espera aprobación manual.
  5. Job “apply” corre terraform apply -auto-approve.
  6. Job “configure” llama a Ansible: ansible-playbook -i inventory.yml site.yml.
  7. Job “notify” envía mensaje a Discord/Telegram con el resumen del despliegue.

Este enfoque mantiene todo bajo control de versiones, permite reconstruir el homelab en minutos y brinda una pista de auditoría clara.

Cuándo aplicar esta solución

Ideal cuando:

  • Se gestionan varias VMs en Proxmox y se desea reproducir el entorno en otro nodo.
  • Los stacks Docker cambian con frecuencia (pruebas de nuevas versiones, experimentos).
  • Se quiere practicar procesos de CI/CD que luego puedan trasladarse a entornos profesionales.

No recomendable si:

  • El laboratorio consta de una única VM y el overhead de Terraform/Ansible supera el beneficio.
  • No se dispone de un repositorio remoto o de un servidor de CI que pueda ejecutar los jobs.

En esos casos, scripts bash simples pueden ser suficientes, pero perderás la capacidad de versionar y escalar.

Código

# terraform/main.tf – ejemplo de recurso Proxmox VM
terraform {
  required_providers {
    proxmox = {
      source  = "Telmate/proxmox"
      version = "~> 2.9"
    }
  }
  backend "local" {}
}

provider "proxmox" {
  pm_api_url      = var.proxmox_url
  pm_user         = var.proxmox_user
  pm_password     = var.proxmox_password
  pm_tls_insecure = true
}

resource "proxmox_vm_qemu" "ubuntu_vm" {
  name        = "homelab-${var.vm_name}"
  target_node = var.proxmox_node
  iso         = "local:iso/ubuntu-22.04-server-cloudimg-amd64.img"
  cores       = 2
  memory      = 4096
  scsihw      = "virtio-scsi-pci"
  bootdisk    = "scsi0"

  disk {
    slot    = 0
    size    = "20G"
    type    = "scsi"
    storage = "local-lvm"
  }

  network {
    model  = "virtio"
    bridge = "vmbr0"
  }

  cloudinit {
    user = "ubuntu"
    password = var.vm_password
    sshkeys = file(var.ssh_pubkey_path)
  }
}
# ansible/playbook.yml – instalación de Docker y despliegue Compose
- hosts: homelab
  become: true
  vars_files:
    - ../sops/secrets.yml  # cifrado con SOPS
  tasks:
    - name: Instalar paquetes requeridos
      apt:
        name: [docker.io, docker-compose]
        state: present
        update_cache: true

    - name: Copiar docker‑compose.yml
      copy:
        src: ../docker-compose.yml
        dest: /opt/app/docker-compose.yml
        owner: root
        mode: '0644'

    - name: Levantar stack
      command: docker compose -f /opt/app/docker-compose.yml up -d
      args:
        chdir: /opt/app
# .github/workflows/homelab.yml – pipeline CI/CD
name: Homelab Deploy
on:
  push:
    branches: [main]

jobs:
  plan:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Setup Terraform
        uses: hashicorp/setup-terraform@v2
      - name: Terraform Init & Plan
        run: |
          terraform -chdir=terraform init
          terraform -chdir=terraform plan -out=plan.out
      - name: Upload plan
        uses: actions/upload-artifact@v4
        with:
          name: tfplan
          path: terraform/plan.out

  apply:
    needs: plan
    if: github.event_name == 'workflow_dispatch' || github.ref == 'refs/heads/main'
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Setup Terraform
        uses: hashicorp/setup-terraform@v2
      - name: Download plan
        uses: actions/download-artifact@v4
        with:
          name: tfplan
          path: terraform
      - name: Terraform Apply
        run: terraform -chdir=terraform apply -auto-approve plan.out

  configure:
    needs: apply
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Install Ansible
        run: sudo apt-get update && sudo apt-get install -y ansible
      - name: Run Ansible Playbook
        env:
          ANSIBLE_HOST_KEY_CHECKING: false
        run: ansible-playbook -i ansible/inventory.yml ansible/playbook.yml

Verificación

  1. Estado de Terraform: terraform -chdir=terraform show debe listar la VM con los atributos esperados.
  2. Conexión SSH: ssh ubuntu@<ip_vm> debe aceptar la clave pública definida en cloud‑init.
  3. Docker Compose: docker ps dentro de la VM debe mostrar los contenedores declarados en docker-compose.yml.
  4. Pipeline: En la página de Actions, cada job debe terminar con “Success”. Un mensaje en Discord confirma la finalización.

Notas adicionales

  • Persistencia del estado: aunque el backend local funciona, migrar a un bucket S3 o a GitHub Encrypted Secrets evita colisiones cuando varios usuarios editan la infraestructura.
  • Versionado de imágenes: usa etiquetas fijas (myapp:1.2.3) en los archivos Compose para que los despliegues sean reproducibles.
  • Rollback rápido: guarda la salida de terraform plan como artefacto; si algo falla, ejecuta terraform apply -destroy con el mismo plan para volver al estado previo.
  • Secretos en Ansible: SOPS permite cifrar secrets.yml con tu GPG key. En GitHub Actions, agrega la clave privada como secret y descifra en tiempo de ejecución.
  • Escalado a Kubernetes: cuando el laboratorio crezca, sustituye el playbook de Docker Compose por un manifiesto Helm y conecta Argo CD al mismo repositorio; la transición es casi transparente porque la capa de IaC sigue siendo Terraform.