This site is itself a project: a self-hosted, multilingual website built from scratch in Rust. Production runs on a Linux VPS; the homelab hosts the Forgejo repository and the CI runner that builds and deploys on every push to main.
Rust was chosen deliberately. The runtime has no garbage collector, no hidden memory allocation, and starts in milliseconds. For a content site like this, that means the server is consistently fast — under load or cold — and the binary is small enough to sit comfortably inside a Podman container.
The framework is Axum, an ergonomic HTTP library built on Tokio's async runtime.
All content lives in plain Markdown files, organised by language and section. At startup, the server reads every file exactly once:
--- block at the top of each file before the content is passed to the Markdown renderer.translations.txt files (key = value pairs) are parsed into a lookup table.HTML templates are embedded directly into the binary at compile time with Rust's include_str!() macro. There is no template engine — just a {{ PLACEHOLDER }} convention and .replace() calls at request time. The result is a server with zero runtime file I/O per request.
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([Browser])
subgraph VPS [VPS — Linux]
NGINX["NGINX · TLS termination"]
Static["Static files · CSS · favicon"]
subgraph Container [Podman container]
Router["Axum router\n/{lang}/blog · /services · /projects · /cv"]
Handler["Request handler"]
Templates["HTML templates\ninclude_str! — compiled into binary"]
AppState["Arc<AppState> — shared read-only cache"]
end
end
subgraph Homelab [Homelab — CI/CD origin]
Forgejo["Forgejo\nGit + CI"]
ForgejoRunner["Forgejo runner"]
end
subgraph Pipeline [Startup pipeline — runs once at boot]
MDFiles[("Markdown files\nresources/en · es · fr")]
FrontMatter["Frontmatter parser\nauthor · date"]
Cmark["pulldown-cmark\nMarkdown → HTML"]
Prebuilt["Pre-built HTML\nlist pages · nav dropdowns · translations"]
end
Visitor -->|HTTPS| NGINX
NGINX --> Router
Router --> Handler
Handler --> Templates
Handler --> AppState
Templates -->|HTML response| Visitor
Static -->|ServeDir| Visitor
Forgejo -->|push triggers| ForgejoRunner
ForgejoRunner -->|build + deploy| VPS
MDFiles --> FrontMatter
FrontMatter --> Cmark
Cmark --> Prebuilt
Prebuilt -->|loaded into| 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;
The site serves English, Spanish, and French from the same binary. URL prefix (/en/, /es/, /fr/) determines the language. Content is organised into per-language directories and templates carry {{ T_KEY }} placeholders resolved at render time. English slugs act as the canonical key — other languages fall back to the English version if a translation is missing.
Deployed as a Podman container on a Linux VPS. NGINX terminates TLS and reverse-proxies traffic to the container. The delivery pipeline lives on the homelab: source code is hosted on a self-managed Forgejo instance, and a Forgejo runner picks up every push, builds the binary, and deploys the updated container to the VPS. No cloud provider. No managed CI service. No database.
Every change goes through a staging environment before reaching production. The same Forgejo runner that handles production deployments manages staging too — a separate container running the same image, reachable internally for review.
Promoting to production is a git merge into main. The runner detects the push to main, builds the release binary, and redeploys the production container on the VPS.
sequenceDiagram
actor Dev as Developer
participant Repo as Forgejo (homelab)
participant Runner as Forgejo runner
participant Staging as Staging container
participant Prod as VPS — Production
Dev->>Repo: push to staging branch
Repo->>Runner: trigger CI pipeline
Runner->>Runner: cargo build --release
Runner->>Staging: deploy container
Runner-->>Dev: staging ready for review
Note over Dev,Staging: validate on staging
Dev->>Repo: git merge staging → main
Repo->>Runner: trigger CI pipeline (main)
Runner->>Runner: cargo build --release
Runner->>Prod: deploy Podman container
Runner-->>Dev: production updated
Rust · Axum · pulldown-cmark · tower-http · Forgejo CI · Mermaid.js