
Por qué migré mi blog de Ghost en Proxmox a Hugo en Cloudflare Pages
Después de un buen tiempo alojando mi blog en Ghost en mi laboratorio, decidí rediseñar por completo la arquitectura y migrar hacia Hugo usando el theme PaperMod y desplegado directamente desde Cloudflare Pages
No es que no me guste Ghost, de hecho, es una plataforma increíble, rápida y muy intuitiva de utilizar para crear publicaciones. Durante años lo estuve corriendo en una VM con Ubuntu dentro de Proxmox, orquestado con Docker Compose y detrás de un Nginx Proxy Manager. Sin embargo, como Cloud Engineer siempre busco la manera de optimizar recursos y aplicar la filosofía Devops a mis propios proyectos.
Mantener Ghost me implicaba destinar RAM y CPU 24/7 para un backend Node.js y una base de datos MySQL, además de preocuparme por las actualizaciones de seguridad de los contenedores y consumir el ancho de banda de mi red local.
Con esta migración a Hugo como ecosistema de sitios estáticos logré 3 objetivos fundamentales:
- Adopción de GitOps: Ahora mi blog es un repositorio en GitHub. Cada artículo es un archivo Markdown. Un simple
git pushdispara un pipeline de CI/CD en Cloudflare que compila y publica la web en segundos. - Rendimiento global y Seguridad: Al no haber base de datos ni backend, la superficie de ataque es nula. El sitio ahora es HTML estático puro servido desde la CDN global de Cloudflare, lo que significa tiempos de carga en milisegundos sin importar desde dónde nos visiten.
- Optimización del Homelab: Pude apagar la VM de Ghost, liberando recursos valiosos en mi Proxmox que ahora puedo destinar a levantar más nodos de Kubernetes y seguir experimentando.
En este post te cuento como estructuré este nuevo entorno, por qué el theme PaperMod es ideal para documentar proyectos y como configure el despliegue automático.
Parte 1: Entorno de Desarrollo Local con Docker en Proxmox
Para mantener mi servidor limpio y no instalar dependencias directamente en el sistema operativo, decidí usar contenedores para el entorno de desarrollo local. Levanté una máquina virtual básica en Proxmox y utilicé Docker Compose para ejecutar el motor de Hugo.
De esta forma, si mañana decido cambiar de equipo o recrear el entorno, solo necesito clonar el repositorio y levantar el contenedor.
1. Preparando el directorio
Lo primero es crear la carpeta donde va a vivir todo el código fuente de nuestro blog y preparar el archivo que va a orquestar nuestro contenedor.
mkdir hugo-test
cd hugo-test
nano docker-compose.yml
2. El archivo Docker Compose
Para la imagen de Docker, utilizo la versión ext-alpine de Hugo, que incluye las dependencias extendidas necesarias para compilar temas modernos como PaperMod.
Pegamos la siguiente configuración en nuestro docker-compose.yml:
services:
hugo:
image: floryn90/hugo:ext-alpine
container_name: hugo-test-local
user: "1000:1000"
command: server --bind=0.0.0.0 --baseURL=http://192.168.52.236:1313/ --buildDrafts --buildFuture --disableFastRender
ports:
- "1313:1313"
volumes:
- .:/src
working_dir: /src
restart: unless-stopped
Puntos clave de esta configuración:
user: "1000:1000": Fuerzo al contenedor a a que herede mi identidad. Esto es útil para que Docker no tenga problemas de permisos al escribir archivos en el volumen mapeado.command: Le indico a Hugo que inicie su servidor de desarrollo. El parámetro crítico acá es--baseURL=http://192.168.52.236:1313/(la IP local de mi VM en Proxmox). Si no configuramos esto, Hugo asume que estamos enlocalhost, y al intentar acceder desde otra PC en la misma red, los enlaces se romperían.
3. Configuración inicial de Hugo
En la raíz del proyecto, creamos el archivo principal de configuración de Hugo. Para este proyecto, elegí la sintaxis TOML, que es limpia y muy fácil de leer.
Creamos el archivo hugo.toml:
baseURL = 'http://example.com/'
languageCode = 'es-ar'
title = 'Hugo Blog'
theme = 'PaperMod'
# Agregamos los parámetros globales del sitio
[params]
author = "Chicho"
defaultTheme = "auto" # Permite que el sitio cambie entre modo claro/oscuro según el sistema del usuario
[taxonomies]
category = "categories"
tag = "tags"
4. Control de Versiones y el Tema PaperMod
Como toda la infraestructura va a estar gestionada mediante GitOps, inicializamos el repositorio de Git. En lugar de descargar el código del tema PaperMod y mezclarlo con nuestro contenido, la mejor práctica en Hugo es agregarlo como un submódulo de Git. Esto nos permite mantener el tema actualizado fácilmente en el futuro.
git init
git add .
git commit -m "Commit inicial del blog"
git branch -M main
# Agregamos PaperMod como submódulo en la carpeta themes
git submodule add https://github.com/adityatelange/hugo-PaperMod.git themes/PaperMod
Con esta estructura base, ya estamos listos para ejecutar docker compose up -d y empezar a darle forma al contenido y a las carpetas internas del blog.
Parte 2: Estructurando el Contenido y Escribiendo en Markdown
A diferencia de Ghost , donde el contenido se guarda en una base de datos relacional y se formatea con editores visuales, en Hugo todo es transparente y vive en tu sistema de archivos, creas un archivo markdown y Hugo lo renderiza y lo publica sin depender de una base de datos.
1. El Árbol de Directorios
Dentro de nuestro directorio hugo-test, necesitamos crear un par de carpetas clave. Hugo tiene reglas estrictas sobre dónde busca los textos y dónde busca los recursos multimedia (como las capturas de pantalla de nuestras topologías).
# Creamos la carpeta para los artículos del blog
mkdir -p content/posts
# Creamos la carpeta para los recursos estáticos (imágenes, logos)
mkdir -p static/images
-
content/posts/: Aquí van a vivir todas nuestras publicaciones y artículos escritos en formato Markdown (.md). -
static/: Esta carpeta es fundamental. Todo lo que pongas acá adentro (imágenes, unfavicon.ico, PDFs) Hugo lo va a tomar y copiar de forma literal a la raíz del sitio web al momento de compilar.
2. Escribiendo nuestra primer publicación
Con las carpetas listas, vamos a crear nuestro primer post. En Hugo, cada archivo Markdown necesita un bloque de metadatos en la parte superior llamado Front Matter. Esto le dice al motor cómo clasificar, fechar y renderizar el artículo.
Vamos a documentar, por ejemplo, como listar los nodos de Kubernetes con kubectl. Creamos el archivo:
nano content/posts/kubernetes-k3s-cluster.md
Y le pegamos el siguiente contenido:
---
title: "como listar los nodos de Kubernetes con kubectl"
date: 2026-03-05
draft: false
categories: ["Kubernetes", "Virtualización"]
tags: ["K3s", "Linux", "Homelab", "DevOps"]
---
# Que es kubcetl?
Acá arranca todo el contenido de nuestro post escrito en Markdown puro. Las ventajas de esto es que podemos integrar bloques de código fácilmente y mantener el control total del formato:
```bash
kubectl get nodes
Detalles críticos que aprendí sobre el Front Matter:
date: Hugo (escrito en Go) es extremadamente estricto con el formato de la fecha. Si usás un formato inválido como2024-XX-XX, la compilación explota por un error de parseo. Siempre usáYYYY-MM-DD.draft: Si está entrue, Hugo no lo va a publicar en producción. Es ideal para ir armando publicaciones largas durante la semana sin que nadie los vea.- Imágenes: Si quisiera agregar una foto de la arquitectura del clúster, simplemente la guardo en
static/images/k3s-arquitectura.pngy en el Markdown la llamo con. Hugo hace el enrutamiento solo.
3. ¡Levantando el servidor!
Ya tenemos la infraestructura como código, el tema PaperMod y un post de prueba. Es hora de ver a Docker y Hugo en acción. Volvemos a la raíz de nuestro proyecto y ejecutamos:
docker compose up -d
Si vamos a nuestro navegador y entramos a la IP de nuestra VM en el puerto 1313 (ej: http://192.168.52.236:1313), vamos a ver nuestro blog corriendo a una velocidad tremenda, con soporte nativo para modo oscuro y nuestro artículo de K8s publicado y categorizado automáticamente.
Además, gracias al volumen que mapeamos en el docker-compose.yml (- .:/src), cualquier cambio que hagamos en los archivos .md o en el hugo.toml se va a reflejar en el navegador casi en tiempo real mediante Hot Reloading, sin necesidad de reiniciar el contenedor.
Parte 3: Despliegue GitOps con GitHub y Cloudflare Pages
Tener el blog corriendo en un contenedor de Docker local está perfecto para desarrollar, pero el objetivo de esta migración era apagar la VM de Ghost y sacar el tráfico público de mi red local.
Para lograrlo, implementé un pipeline de CI/CD súper sencillo y gratuito utilizando GitHub y Cloudflare Pages.

1. El Repositorio Privado en GitHub
Decidí que el repositorio de Hugo sea privado para gestionar el codigo fuente de manera segura. A diferencia de GitHub Pages (que te obliga a tener el repo público en cuentas gratuitas), Cloudflare Pages permite conectar repositorios privados sin costo.
Una vez que tuve mi estructura lista localmente, empujé el código a GitHub:
git add .
git commit -m "Estructura inicial lista para Cloudflare Pages"
git push origin main
2. Configurando Cloudflare Pages (El truco de la versión)
Dentro del panel de Cloudflare, la configuración es casi automática, pero hay un par de detalles críticos que aprendí a los golpes:
-
Fui a Workers & Pages -> Pestaña Pages -> Connect to Git.
-
Autoricé a Cloudflare a leer mi repositorio privado
hugo-blog-it. -
En la configuración de compilación (“Set up builds and deployments”), utilicé estos parámetros:
-
Framework preset: Hugo
-
Build command:
hugo --minify(el flag--minifycomprime el HTML/CSS para que cargue aún más rápido). -
Build output directory:
public
-
⚠️ La Variable de Entorno Crítica:
Por defecto, la imagen de compilación de Cloudflare usa una versión de Hugo muy antigua. Temas modernos como PaperMod requieren versiones recientes y fallarán al compilar si no se los indicamos.
Para solucionarlo, desplegué la sección Environment variables (advanced) y agregué:
-
Variable name:
HUGO_VERSION -
Value:
0.157.0(O la versión que estés usando localmente).
Al darle a “Save and Deploy”, Cloudflare clona el repo, descarga el binario de Hugo en milisegundos, compila el sitio y lo distribuye a su CDN global.
3. DNS y Nginx Proxy Manager
Una vez que el sitio compiló en verde, fui a la pestaña Custom Domains en Cloudflare Pages y agregué mi subdominio (en mi caso, devops.chicho.com.ar).
Como Cloudflare ya gestionaba mi zona DNS, creó automáticamente el registro CNAME necesario. Lo genial de esta arquitectura es que el tráfico de mi blog ahora es absorbido por la red de Cloudflare. Las peticiones web ya no llegan a mi IP pública, lo que libera de carga a mi Nginx Proxy Manager en Proxmox, dejándolo dedicado exclusivamente a enrutar de forma segura mis servicios y contenedores internos.
4. El toque final: Comentarios Serverless
Una de las cosas que perdemos al migrar de Ghost a Hugo es la base de datos para gestionar comentarios. Sin embargo, en el mundo del código abierto siempre hay soluciones o casi siempre :)
Implementé Giscus, un sistema que utiliza las “Discussions” de GitHub para alojar los comentarios.
Creé un repositorio público vacío en GitHub (solo para almacenar las discusiones), instalé la app de Giscus, y pegué el script resultante en un archivo layouts/partials/comments.html dentro de mi proyecto de Hugo.
PaperMod lo inyecta automáticamente al final de cada post. Cero bases de datos, cero spam gestionado por mí, y los usuarios (que en este antro suelen ser todos perfiles técnicos) pueden comentar logueándose directamente con su cuenta de GitHub.
Conclusión
Migrar de un CMS dinámico a un generador de sitios estáticos no es solo un cambio estético. Es una mejora fundamental en la postura de seguridad (cero superficie de ataque en el backend o en base de datos), en el rendimiento (tiempos de respuesta en milisegundos globales) y en la eficiencia de recursos de mi homelab.
Ahora, escribir una nueva publicacion sobre mis despliegues en Azure, Docker o Kubernetes es tan simple como abrir VS Code, escribir en Markdown y hacer un git push. La nube se encarga del resto.