aj-printstudios.de

A static business-card site for a workshop in rural Bavaria. Astro, Cloudflare Tunnel, self-hosted on an HP T630 thin client.

Astro Tailwind Cloudflare Tunnel Caddy Self-Hosting
aj-printstudios.de preview

What it is

A business-card site for Anne, who runs a small laser-engraving and 3D-printing workshop in the Bavarian Rhön. One woman, three machines, one workshop. The site is meant to convey exactly that without slipping into marketing-speak: no “vision”, no “your solution”, just what she does, how she works, how to reach her.

You can look at it live at aj-printstudios.de.

Why static

For a site that should still run the same way five years from now, a database is mostly a future maintenance liability. No WordPress, no plugin hell, no “your database has been updated, please log in and verify” every three weeks.

Instead: HTML, CSS, images. Static, fast, safe. Built once, served as-is. There’s nothing to hack at runtime because nothing runs at runtime.

What’s underneath

  • Astro as a static site generator. Markdown content, components in Astro’s own .astro format, everything compiled to plain HTML at build time.
  • Tailwind CSS for styling.
  • Astro’s image pipeline automatically converts photos to WebP and serves multiple resolutions for different screens. Helps Lighthouse scores and saves bandwidth.
  • astro-icon with the Tabler set for the few icons used. Inline SVG, no icon font.
  • Self-hosted fonts via @fontsource-variable. No Google Fonts from a CDN, no third-party tracking through the back door.

Builds finish in under 30 seconds, the whole dist/ directory is around 2 MB.

Hosting

Everything runs on an HP T630 thin client in the home lab. Debian 13, no cloud VPS, no managed hosting. The T630 is a fanless mini-PC originally meant for office desktops. More than enough for a static site, and it sips a few watts at idle. Three components work together:

  1. Cloudflare Tunnel terminates HTTPS at the Cloudflare edge and routes traffic through an outbound tunnel to the server. The server itself isn’t directly reachable from the internet: no open ports, no DDoS risk, TLS handled automatically, no Let’s Encrypt theater.
  2. Caddy in Docker as an internal reverse proxy. Serves the static files and sets cache headers. Astro’s hashed assets get max-age=31536000, immutable, HTML gets a much shorter TTL so updates show up quickly.
  3. systemd keeps the cloudflared service running across reboots.

The domain runs through Cloudflare DNS. No monthly hosting bill, no provider lock-in, no surprise contract renewals or price hikes.

Deploy without CI

GitHub Actions can’t reach the server inside the LAN, so no classic CI/CD. Instead, a single bash script ./push does four things in sequence:

  1. Build (npm run build)
  2. rsync the dist/ files to the server with an ssh key
  3. git push origin <branch>
  4. curl smoke test to verify the live URL still returns 200

At a few pushes a month, this is less maintenance than any auto-deploy with a Tailscale bridge would be. If frequency ever ramps up, the bridge can be added later without touching anything else.

Things learned along the way

  • Strip the theme’s default configs before you deploy. Sitemap and robots.txt otherwise point to the theme’s default domain. Looked weird in the search tools for three days before that one surfaced.
  • Delete demo content, don’t just hide it. AstroWind ships a dozen demo pages as showcase material. If they end up in the build, they show up in the sitemap and Google ends up indexing “/landing/click-here-buy-now”. The entire homes/ and landing/ directories have to go, not just be unlinked from navigation.
  • Cloudflare’s Cache TTL setting has to be set explicitly to “Respect Existing Headers” or Cloudflare overrides the cache headers Caddy sets with its own defaults. Then the browser ends up stuck on stale images for up to four hours.
  • Cloudflare’s AI Audit prepends a content-signals policy to the robots.txt with disallow rules for AI bots. The exact opposite of what you want if you’re trying to be visible in ChatGPT, Claude, and Perplexity.
  • Lighthouse Mobile glitches sometimes and throws NO_LCP, which then drags the other performance metrics down with it. It’s a Lighthouse bug on very lightweight pages, not a UX problem. You can tell because all the other metrics (FCP, CLS, Speed Index) stay green.

As of today: Lighthouse 100/96/100/92 (Performance/Accessibility/Best Practices/SEO), running since launch without incident.