Building Nordhub: My Own Static Site Host From Scratch
Why I Built This
I tried Vercel and liked the experience — the push-to-deploy workflow, the instant previews, the simplicity. But I kept thinking: could I build something like this myself? One server, one place for all my sites, full control.
So I started. No grand plan, no business case. Just curiosity. Can I make git push trigger a build and serve the result?
Turns out I could. And once I had the basics working, I kept going. I added custom domains, rollbacks, environment variables, team accounts. At some point it stopped being an experiment and became something I actually use every day.
The EU angle came later. I'm based in Sweden, and with all the Schrems II noise and companies scrambling to keep data in Europe, it made sense to host this on a European server. Nordhub runs on a VPS in Helsinki, Finland. Your data stays in the EU, and GDPR is handled out of the box.
The Stack
I went with tools I wanted to learn or already liked:
- Bun as the runtime — fast, built-in TypeScript support, great DX
- Hono for the API — lightweight, fast, feels like Express but modern
- React + Vite for the dashboard
- PostgreSQL with Drizzle ORM for the database
- Caddy as the reverse proxy — automatic HTTPS, zero config TLS
- Docker for build sandboxing (more on this below)
The whole thing is a monorepo managed with Turborepo. The API serves both the REST endpoints and the static sites via subdomain routing — yoursite.nordhub.se points to the right deployment directory on disk.
How a Deploy Works
The flow is straightforward:
- You connect a GitHub repo and pick a branch
- Nordhub registers a webhook on your repo
- Every push triggers the webhook, which kicks off a build
- The builder clones your repo, runs
npm install, runs your build command - The output gets copied to
sites/{siteId}/{deploymentId}/ - The
activeDeploymentIdon your site record gets updated - Caddy serves the files — your site is live
Rollbacks work by simply switching activeDeploymentId back to a previous deployment. The files are already there. One database update and you're back to the old version.
The Hard Part: Running Untrusted Code Safely
This was the biggest challenge by far. When someone pushes code and you run npm install && npm run build on your server, you're executing arbitrary code. That's terrifying.
The solution: every build runs inside an isolated Docker container.
docker run --rm \
--network none \
--memory 2g \
--cpus 2 \
--user 1001:1001 \
nordhub-builder
Here's what makes it safe:
- Non-root user — the build process runs as UID 1001, not root
- No network during build — network is only enabled during
npm install(to fetch packages), then disabled before the actual build runs - Memory and CPU limits — 2GB RAM, 2 CPUs max. No crypto mining on my server
- Container destroyed after each build — nothing persists between builds
- 10-minute timeout — if your build hangs, it gets killed
- 100MB output limit — can't fill the disk with garbage
I also sanitize build logs so environment variable values never leak. If you have API_KEY=secret123, the logs show API_KEY=***.
Getting the Docker-in-Docker setup working with proper volume mounts for the build output was probably the most frustrating debugging session of the whole project. But once it clicked, it's been rock solid.
Building With Claude
I want to be honest about this: most of Nordhub was built with Claude as my coding partner. Not as a "generate the whole thing" tool, but as a pair programmer I could bounce ideas off and move fast with.
The workflow usually looked like this: I'd describe what I wanted — "add environment variables to sites, encrypted in the database" — and we'd work through it together. I'd steer the architecture decisions, Claude would write the implementation, and I'd review and adjust.
Some things it was great at:
- Scaffolding new features end-to-end (API route + DB schema + frontend)
- Writing the boring but necessary stuff (validation, error handling, rate limiting)
- Catching edge cases I hadn't thought about
Some things I had to drive:
- Architecture decisions (how should orgs work? what's the permission model?)
- Production debugging (Docker networking, DNS issues, VPS-specific stuff)
- Knowing when to stop adding features
Building with AI didn't make the project trivial — I still had to understand everything and make the decisions. But it made it possible to ship in a week what would've taken me a month.
What's Next
Nordhub is live at nordhub.se and I'm using it to host my own sites. It's free during this early phase. The roadmap includes deploy previews, a CLI tool, simple analytics, and build caching.
If you're a developer in Europe and want a simple place to host your static sites without dealing with US cloud providers, give it a try. And if something breaks, tell me — I'll fix it.