Skip to content

JMR-dev/dev_blog

Repository files navigation

dev-blog

A personal developer blog built to be fast, secure, and entirely self-hosted — no platform lock-in, no third-party runtime dependencies.

Stack

Site

  • Astro v6 — generates static HTML at build time, served via astro preview. Zero client-side JavaScript by default.
  • Markdown & MDX — posts live in src/content/blog/ as typed Content Collections with frontmatter validation.
  • RSS feed + sitemap — auto-generated via @astrojs/rss and @astrojs/sitemap.
  • Local fonts — Atkinson Hyperlegible served from src/assets/fonts/, no external font requests.
  • pnpm — fast, disk-efficient package management. Requires Node ≥ 22.

Testing

  • Vitest — unit and integration tests with v8 coverage.
  • Playwright — end-to-end tests against the running site.

Containers

The entire runtime is two containers communicating over a private bridge network.

Internet ──► Caddy :443 ──► Astro app :4321

App container

A two-stage Containerfile (Node 24 on Debian slim):

  1. Build stage — installs deps and runs astro build.
  2. Runtime stage — copies only dist/, node_modules, and config. Runs as a non-root astro user (UID 1001) with a read-only filesystem, all Linux capabilities dropped, and no-new-privileges enforced.

Caddy container

A custom Caddy build compiled with xcaddy, adding two plugins on top of the official image:

  • coraza-caddy — the Coraza WAF with the OWASP Core Rule Set (CRS v4.7.0) baked into the image. All traffic is inspected before it reaches the app.
  • caddy-dns/googleclouddns — ACME DNS-01 challenge provider, so TLS certificates are issued and renewed without opening port 80 or requiring a webroot.

Caddy also sets hardened response headers (HSTS, X-Frame-Options, X-Content-Type-Options, Referrer-Policy) and compresses responses with zstd and gzip.

Compose vs. production

  • Local / CI: compose.yaml (+ compose.override.yaml) spins up the full stack with podman compose up --build.
  • Production: Quadlet units in quadlet/ integrate the containers directly with systemd — no compose daemon required.

Infrastructure

All infrastructure is version-controlled and reproducible.

Layer Tool Details
Hosting Vultr VPS — 1 vCPU / 2 GB RAM, AlmaLinux 10, Seattle (sea) region. Reserved IPv4 and IPv6 addresses survive instance replacement. Daily automated backups.
DNS Google Cloud DNS Authoritative DNS for the site's domain. A service-account key is also used by Caddy's caddy-dns/googleclouddns plugin to complete ACME DNS-01 challenges for automatic TLS certificate issuance and renewal.
Cloud provisioning OpenTofu Manages the Vultr instance and reserved IPs as code. State stored remotely via a Cloudflare R2 backend.
Host configuration Ansible Roles: common, nftables (firewall), fail2ban (intrusion prevention), registry (private container registry), container_host (Quadlet + Podman setup).

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors