Meta

Colofón

Cómo está construido este sitio, qué decisiones tiene detrás y por qué. Si un portafolio de infraestructura no explica su propia infraestructura, algo falla.

Arquitectura

Un único contenedor Node que sirve tanto el sitio público como el panel de administración. Sin framework de frontend, sin paso de compilación y sin CDN de terceros: el HTML sale ya renderizado del servidor y el navegador sólo recibe lo que va a usar.

texto
navegador
    │  HTTPS
    ▼
reverse proxy (Caddy/Traefik) ── TLS automático
    │  HTTP en loopback
    ▼
contenedor forgekloud (Node 26, usuario sin privilegios, FS de sólo lectura)
    ├── Fastify ── rutas públicas + /admin
    ├── SQLite (WAL) ── volumen persistente
    └── /uploads ── imágenes validadas por bytes

Decisiones y por qué

SQLite en vez de Postgres. Un solo escritor, lecturas concurrentes y un respaldo que consiste en copiar un fichero. Postgres aquí sería un servicio más que mantener, actualizar y monitorizar sin ganar nada.

Sin build step. El CSS y el JavaScript se sirven tal cual se escriben. No hay bundler que actualizar ni node_modules de 400 MB en la imagen final.

Markdown sin HTML crudo. El renderizador tiene html: false, así que no existe vector de XSS almacenado ni hace falta un sanitizador.

Páginas editables sin desplegar. El contenido de estas páginas vive en SQLite como una lista ordenada de bloques, no en el código. Cambiar una cifra o añadir una sección no exige tocar un fichero ni reiniciar el contenedor.

Resaltado de sintaxis en servidor. Un tokenizador propio de ~150 líneas cubre bash, YAML, HCL, Dockerfile, JSON, SQL y algunos más. El cliente recibe HTML ya coloreado: cero JavaScript de resaltado.

Seguridad

  • Sesión en JWT HS256 firmado, en cookie httpOnly + SameSite=Strict + Secure.
  • Contraseña con scrypt (KDF de memoria dura), nunca en texto plano.
  • Token CSRF derivado del identificador de sesión en todo formulario que escribe.
  • Rate limiting en el login: 5 intentos por ventana de 15 minutos.
  • Subidas validadas por magic bytes, no por extensión ni por el Content-Type que declare el cliente. SVG está excluido a propósito.
  • El contenido de los bloques se valida contra un esquema al guardar y se escapa al renderizar: lo que se escribe en el panel nunca llega crudo al navegador.
  • Cabeceras: Content-Security-Policy sin unsafe-inline, X-Content-Type-Options, Referrer-Policy y X-Frame-Options.
  • Contenedor con no-new-privileges, sistema de ficheros de sólo lectura y usuario no root.

Diseño

Sistema propio construido a partir de patrones observados en sitios de infraestructura y observabilidad, no copiado de ninguno: rampa de superficies neutra-cálida, un único acento (ámbar de forja) contrapesado con un frío para datos, monoespaciada reservada a telemetría y código, y retícula bento asimétrica. Tipografías Space Grotesk, IBM Plex Sans e IBM Plex Mono, autoalojadas.

Rendimiento

Sin JavaScript de terceros, sin fuentes remotas y sin peticiones fuera del propio dominio. El sitio se lee y se navega entero con JavaScript desactivado; el JS sólo añade el menú móvil, la animación del terminal y el botón de copiar código.