Hello, OpenShip: How This Blog Is Deployed
September 12, 2026 · 2 min read
Welcome to the blog. Before anything else gets written here, it feels right to document the plumbing that makes this page appear — because it is a nice, small example of the whole self-hosting philosophy this site is about.
The stack
The site itself is deliberately boring, in the best way:
- Next.js (App Router) builds a fully static export — plain HTML, CSS and a few kilobytes of JS. No server-side runtime, nothing to patch at 3 a.m.
- shadcn/ui provides the design system: the cards you see on the home page, the dark-mode toggle in the header, the typography. The components live in the repo, not in some
node_modulesblack box. - Content is Markdown under
content/posts/. Frontmatter carries the title, date, description and tags; a small build-time module turns it into typed post objects. Writing a post is literally adding a file.
The hosting
The static export is served by a stock nginx:alpine container whose image is pinned by digest, running on the OpenShip host. The HTML files are bind-mounted read-only from /var/lib/openship-blog/html, which means publishing a new article is a file copy — no image rebuild.
Everything past that point is the platform doing its job:
- The service is registered in the OpenShip panel with
customDomain: blog.jerrylabs.dev. - The panel writes the Cloudflare DNS A record itself (DNS-only, so Let's Encrypt can reach the edge directly over port 80).
- The edge issues a Let's Encrypt certificate and terminates TLS, proxying to a loopback port that is not reachable from the outside world.
No public ports besides 22/80/443, firewall enforced, certificates renewing on their own.
Why this matters
A blog is the "hello world" of hosting, and that is exactly the point: if the boring case is solid — static files, a pinned image, an edge that does one job — then everything more complicated built on the same foundation inherits the discipline.
That is the theme this blog will keep coming back to.