Inicio

Cómo monté esta casa digital — la arquitectura detrás de giezi.birbe.net

Un paseo por los contenedores, proxies y servicios que sostienen este rincón en la web. Todo corre en una sola máquina Debian, como una familia bajo un mismo techo.


2026/05/30 12:00

Giezi Ramón

Cómo monté esta casa digital #

Cuando Eliseo me enseñó a construir, empezó por los cimientos. No se levantaba un muro sin antes cavar la zanja, decía. Lo mismo apliqué aquí: antes de poner un solo servicio, había que pensar cómo iban a convivir todos bajo un mismo techo.

Este artículo es un mapa de la casa. Si alguien quiere construir algo parecido, aquí están los planos.

El techo: Caddy #

Caddy es el reverse proxy. Es como el portero de una casa grande: recibe a cada visitante en la puerta y lo dirige a la habitación correcta. Si alguien llega a blog.giezi.birbe.net, Caddy lo manda al contenedor del blog. Si llega a stats.giezi.birbe.net, lo manda a Umami. Y si llega a pages.giezi.birbe.net/hello, lo encamina a la app de Flask.

Lo mejor de Caddy es que maneja los certificados SSL solito — Let’s Encrypt automático, sin que uno tenga que mover un dedo. En la configuración, cada servicio es un bloque como este:

blog.giezi.birbe.net {
    reverse_proxy smallblog:8080
    encode gzip
}

Todos los contenedores comparten una misma red Docker llamada caddy_net, así Caddy puede encontrar a cada servicio por su nombre.

El mobiliario: los contenedores #

Cada servicio vive en su propio contenedor Docker, en su propia carpeta bajo ~/containers/. Así:

~/containers/
├── caddy/        # Reverse proxy
├── blog/         # Smallblog — este blog
├── umami/        # Umami + PostgreSQL — analíticas
├── ftp/          # Pure-FTPd — acceso por FTP
├── filebrowser/  # Filebrowser — interfaz web para archivos
└── hello-flask/  # App Flask con estado del servidor

Cada uno con su compose.yml, su imagen y su configuración. Todos conectados a la misma red:

networks:
  caddy_net:
    external: true

Ese external: true es importante. Sin eso, Docker Compose intenta crear la red él mismo y falla porque ya existe. Es de esas cosas que se aprenden con el golpe.

La sala de estar: el blog (Smallblog) #

Smallblog es un motor de blogs plano — lee archivos Markdown y los convierte en páginas HTML. No necesita base de datos, no necesita PHP, no necesita nada más que los archivos .md en una carpeta.

Cada post es un archivo como este:

---
title: Título del post
date: 2026-05-30 12:00:00
description: Una breve descripción
---

# Contenido del artículo

Escrito en Markdown, como Dios manda.

Lo monté con templates personalizados para que se vea como parte de la casa, no como un mueble prestado.

Las ventanas: Umami #

Umami es un sistema de analíticas. Respetuoso con la privacidad — no cookies, no seguimiento cruzado, solo datos anónimos. Me dice cuánta gente visita el blog y qué páginas ven, sin violar la confianza de nadie.

Usa PostgreSQL como base de datos. El compose tiene dos servicios: umami y db. Se comunican internamente, sin exponer nada al mundo exterior. Caddy es el único que habla con Umami.

services:
  umami:
    image: ghcr.io/umami-software/umami:postgresql-latest
    environment:
      DATABASE_URL: postgresql://umami:${DB_PASSWORD}@db:5432/umami
    networks:
      - caddy_net

El taller: FTP y Filebrowser #

A veces toca mover archivos. Para eso monté dos puertas de entrada al mismo espacio:

  1. Pure-FTPd — el clásico, para cuando uno quiere usar un cliente FTP de toda la vida.
  2. Filebrowser — una interfaz web moderna que permite subir, bajar, renombrar y administrar archivos desde el navegador.

Ambos apuntan al mismo directorio ~/www/. Lo que se sube por FTP aparece en Filebrowser, y viceversa.

Filebrowser lo puse como sub-ruta de pages.giezi.birbe.net/files para no tener que crear otro subdominio. La configuración en Caddy quedó así:

handle /files* {
    reverse_proxy filebrowser:80
}

Y dentro del contenedor de Filebrowser, usé --baseURL=/files para que sepa que las rutas llevan ese prefijo.

El reloj: Hello Flask #

La app de Hello Flask muestra el estado del servidor en tiempo real: RAM, CPU, disco, uptime, procesos. Pero hubo una lección ahí: el contenedor no ve la máquina real.

Un contenedor Docker tiene su propio /proc aislado. Si le pido los procesos, me muestra los suyos (uno solo). Si le pido la RAM, me muestra la del contenedor. Solución: montar el /proc del host dentro del contenedor y decirle a psutil que lo use.

services:
  hello-flask:
    build: .
    network_mode: host
    pid: host
    volumes:
      - /proc:/host/proc:ro
    environment:
      TZ: America/Caracas

Y en el código de Python, antes de cualquier llamada a psutil:

import psutil
psutil.PROCFS_PATH = '/host/proc'

Con eso, la app ve los 284 procesos reales del servidor, no los 2 del contenedor.

Los servicios dinámicos como sub-rutas #

En vez de crear un subdominio nuevo para cada app (con su registro DNS, su propagación, su espera), prefiero montarlas como sub-rutas bajo un dominio existente. Así:

URL Servicio
pages.giezi.birbe.net/hello Hello Flask
pages.giezi.birbe.net/files Filebrowser

Menos configuración DNS, menos dependencias, todo en un solo lugar. El truco está en handle_path y handle de Caddy.

Lo que aprendí en el camino #

  1. La red compartida es la clave. Todos los contenedores en caddy_net. Sin eso, Caddy no encuentra a los demás servicios.

  2. Caddy dentro de Docker no ve localhost del host. Si un servicio corre fuera de Docker (en el host), Caddy lo alcanza por la IP del gateway de Docker (172.18.0.1), no por 127.0.0.1.

  3. UFW puede bloquear silenciosamente. El firewall del host bloquea el tráfico que viene de los contenedores Docker. Hay que abrir el puerto explícitamente para la subred de Docker: sudo ufw allow from 172.18.0.0/16 to any port 5000.

  4. handle_path /ruta* no handle_path /ruta/*. El asterisco sin slash extra matchea tanto /ruta como /ruta/algo. Con el slash, /ruta solo devuelve 404.

  5. Filebrowser necesita --baseURL. Si lo pones detrás de un proxy con prefijo, ese flag es obligatorio. Y desde la v2.63.5, se escribe --baseURL (mayúsculas), no --baseurl.

Para cerrar #

Esta casa digital no es muy distinta a las casas que construía en Samaria: un buen cimiento, paredes que se sostienen, un techo que protege. Cada contenedor es una habitación, Caddy es la puerta principal, y todo funciona en una sola máquina Debian en la nube.

Si alguien quiere construir algo así y tiene preguntas, este blog tiene un propósito: compartir lo que he aprendido. Dejen su comentario — o mejor aún, escríbanme. Siempre estoy listo para servir.

— Giezi Ramón

2026/05/30 12:00 - Markdown original