Este sitio es en sí mismo un proyecto: una web multilingüe y autoalojada, construida desde cero en Rust. La producción corre en un VPS Linux; el homelab aloja el repositorio Forgejo y el runner de CI que compila y despliega en cada push a main.
Rust fue elegido de forma deliberada. El runtime no tiene recolector de basura, ni asignación de memoria oculta, y arranca en milisegundos. Para un sitio de contenido como este, eso significa que el servidor es consistentemente rápido — bajo carga o en frío — y el binario es lo suficientemente pequeño para vivir cómodamente dentro de un contenedor Podman.
El framework es Axum, una librería HTTP ergonómica construida sobre el runtime asíncrono de Tokio.
Todo el contenido vive en archivos Markdown planos, organizados por idioma y sección. En el arranque, el servidor lee cada archivo exactamente una vez:
--- al principio de cada archivo, antes de pasar el contenido al renderizador Markdown.translations.txt por idioma (pares clave = valor) se parsean en una tabla de consulta.Las plantillas HTML se embeben directamente en el binario en tiempo de compilación con la macro include_str!() de Rust. No hay motor de plantillas — solo una convención {{ PLACEHOLDER }} y llamadas a .replace() en el momento de la petición. El resultado es un servidor con cero I/O de archivos en tiempo de ejecución por petición.
flowchart TD
classDef visitor fill:#f9f9f9,stroke:#333,stroke-width:2px;
classDef infra fill:#e1f5fe,stroke:#01579b,stroke-width:2px;
classDef runtime fill:#e8f5e9,stroke:#2e7d32,stroke-width:2px;
classDef source fill:#fff3e0,stroke:#ef6c00,stroke-width:2px;
classDef pipeline fill:#ede7f6,stroke:#4527a0,stroke-width:2px;
classDef cicd fill:#fce4ec,stroke:#880e4f,stroke-width:2px;
Visitor([Navegador])
subgraph VPS [VPS — Linux]
NGINX["NGINX · terminación TLS"]
Static["Archivos estáticos · CSS · favicon"]
subgraph Container [Contenedor Podman]
Router["Router Axum\n/{lang}/blog · /services · /projects · /cv"]
Handler["Handler de petición"]
Templates["Plantillas HTML\ninclude_str! — compiladas en el binario"]
AppState["Arc<AppState> — caché compartida de solo lectura"]
end
end
subgraph Homelab [Homelab — origen CI/CD]
Forgejo["Forgejo\nGit + CI"]
ForgejoRunner["Forgejo runner"]
end
subgraph Pipeline [Pipeline de arranque — se ejecuta una vez al iniciar]
MDFiles[("Archivos Markdown\nresources/en · es · fr")]
FrontMatter["Parser de frontmatter\nautor · fecha"]
Cmark["pulldown-cmark\nMarkdown → HTML"]
Prebuilt["HTML pre-construido\nlistas · nav dropdowns · traducciones"]
end
Visitor -->|HTTPS| NGINX
NGINX --> Router
Router --> Handler
Handler --> Templates
Handler --> AppState
Templates -->|Respuesta HTML| Visitor
Static -->|ServeDir| Visitor
Forgejo -->|push dispara| ForgejoRunner
ForgejoRunner -->|compilar + desplegar| VPS
MDFiles --> FrontMatter
FrontMatter --> Cmark
Cmark --> Prebuilt
Prebuilt -->|cargado en| AppState
class Visitor visitor;
class NGINX infra;
class Router,Handler,Templates,AppState runtime;
class Static source;
class MDFiles,FrontMatter,Cmark,Prebuilt pipeline;
class Forgejo,ForgejoRunner cicd;
El sitio sirve inglés, español y francés desde el mismo binario. El prefijo de URL (/en/, /es/, /fr/) determina el idioma. El contenido está organizado en directorios por idioma y las plantillas llevan marcadores {{ T_CLAVE }} que se resuelven en el momento de renderizar. Los slugs en inglés actúan como clave canónica — los demás idiomas recurren a la versión inglesa si falta una traducción.
Desplegado como contenedor Podman en un VPS Linux. NGINX termina el TLS y hace reverse proxy hacia el contenedor. El pipeline de entrega vive en el homelab: el código fuente está alojado en una instancia Forgejo autogestionada, y un runner de Forgejo recoge cada push, compila el binario y despliega el contenedor actualizado en el VPS. Sin proveedor cloud. Sin servicio de CI gestionado. Sin base de datos.
Cada cambio pasa por un entorno de staging antes de llegar a producción. El mismo runner de Forgejo que gestiona los despliegues en producción también gestiona el staging — un contenedor separado con la misma imagen, accesible internamente para revisión.
Promocionar a producción es un git merge a main. El runner detecta el push, compila el binario de release y redespliega el contenedor de producción en el VPS.
sequenceDiagram
actor Dev as Desarrollador
participant Repo as Forgejo (homelab)
participant Runner as Forgejo runner
participant Staging as Contenedor staging
participant Prod as VPS — Producción
Dev->>Repo: push a rama staging
Repo->>Runner: disparar pipeline CI
Runner->>Runner: cargo build --release
Runner->>Staging: desplegar contenedor
Runner-->>Dev: staging listo para revisión
Note over Dev,Staging: validar en staging
Dev->>Repo: git merge staging → main
Repo->>Runner: disparar pipeline CI (main)
Runner->>Runner: cargo build --release
Runner->>Prod: desplegar contenedor Podman
Runner-->>Dev: producción actualizada
Rust · Axum · pulldown-cmark · tower-http · Forgejo CI · Mermaid.js