How I built voxelbrew.sh
Fourth site, first one that does not suck
Recent developments in the tech world inspired me to start a blog. Now is as
good a time as any: for the first time I can realistically juggle my
responsibilities and my hobbies and see my side projects through. I have
tried this before. I used to run two Angular-based sites, cchulo.labs and
cchulo.dev, both now defunct. My name is not very memorable unless you’re
fluent in Spanish, and Angular was a heavy choice for a blog and portfolio.
Later I tried React, and after that I switched to Astro, which is what
takobits.dev was written in. Beyond the fact that the site was named after a
pet that had passed away (RIP Tako), I did not have much motivation outside of
work to work on side projects, much less document them.
cchulo.labs
cchulo.labs ran on the stack I was using at work at the time: Angular,
a .NET Core 2.1 backend, MongoDB, Apache, and a small cloud VM.
It was clunky, it was heavy, and I had decided it should be a PWA.
I found Progressive Web Apps exciting back then, but a blog was the
wrong application for one. Life got busy and the site went unmaintained
for more than a year.
cchulo.dev
cchulo.dev came many years later. .labs felt like a bad fit, so I moved to
a .dev top-level domain. The stack was similar, with a few changes: I cut out
MongoDB and stuck to Markdown files, I went with nginx over Apache, and I used
Angular Material. I ended up not liking the result. I spent a lot of time
studying color theory, trying to figure out which colors best described my
tastes and personality, and then trying to fit a palette into the Material spec
that made sense. I have an artistic side, but with life’s responsibilities it
tends to get neglected. cchulo.dev went through a couple of years of low
maintenance before it, too, faded out.
takobits.dev
takobits.dev came a few years after that. At the time I was researching React
and finding out how bare-bones it is. It’s basically the Arch Linux of web
frameworks: you start from scratch. Next.js speeds some things up, but you
still need libraries for even the most basic things, and an update to any one
of them can break the whole site.
Then I saw Astro. It was featured in one of Fireship’s old “in 100 seconds” videos. It was simple, and it produced fast static sites, which is exactly what a portfolio blog needs. I credit Fireship with getting me out of my Angular and React rut, introducing me to other frameworks, and getting me familiar with SSR concepts like hydration and the performance trade-offs of statically compiled sites. It turns out that plain HTML, CSS, and JS is still king for the most part.
So the stack for takobits.dev was Astro, nginx for both reverse proxy but also
serving the web site directly, a cloud VM, and, for the first time, Cloudflare. Why?
I shared the site on the Astro Discord and immediately my site and server were
getting spammed like crazy. Cloudflare is genuinely useful for adding and
managing rate limits from an easy dashboard, and it’s free. I could have done it
in nginx, but I’d rather not have my tiny server taxed by the traffic at all.
But again, the design of the site, the fact that its namesake was gone, and the lack of time killed it…
…in comes voxelbrew.sh
Over the last few years I have become well versed in containerization and security practices, so the stack is similar but with notable differences.
It consists of Astro, nginx, Docker for local development, Podman for the remote deployment, and Cloudflare Tunnel, all running as a set of Podman quadlets. Tailscale gives me private access to the server for deployments, and Cloudflare sits in front of the site to filter out bad traffic.
Everything runs on an AlmaLinux server under a service account that cannot
elevate its privileges. The account is confined to its own home directory with
a very limited PATH, and the only thing it is allowed to do is run its own
quadlets.
Tailscale runs on the host with a tag that prevents it from initiating
connections to my home network, while my home network can initiate connections
to the server. This is what lets CI/CD on my self-hosted Forgejo instance, in
my homelab cluster, push changes straight to the site: a push to main builds
the image, a runner on the tailnet rolls it out, and the site is live a couple
of minutes later.
Cloudflare Tunnel runs as a container on a private Podman network alongside the site, so nothing on the server listens on a public interface.
flowchart LR
browser([browser]) --> cloudflare[Cloudflare] --> tunnel[tunnel]
runner[Forgejo runner] --> tailscale[Tailscale] --> ssh[ssh]
subgraph server
tunnel --> nginx
ssh --> quadlets[Podman quadlets] --> nginx
end
Quadlets changed how I run containers. I was tempted by k3s a while back, but quadlets make so much more sense for lightweight deployments like this one.
On top of that, Ansible playbooks and having everything containerized give me reproducible environments I can deploy anywhere, and even scale should I ever need to. For this project, I probably won’t.
The mug
The inspiration for the site is craftz.dog by Takuya Matsuyama. It’s the site of a YouTuber I came across years ago, in an ASMR- style video where he sits in a coffee shop and knocks out the whole thing line by line.
I’m a coffee addict, I’m into 3D rendering, and I’m into voxel art, so the
branding for my site became a voxelized coffee cup. Like craftz.dog, I lazy-
load a .glb model with three.js after the page has finished loading, so it
never gets in the way of the content.
Mr. Matsuyama graciously open-sourced his site. I will open source mine at some point, though it is not in any shape for that yet. Maybe in a couple of weekends, as I keep refining things. There is not much point in keeping it secret; with today’s AI tools anyone can replicate a website from screenshots. But there are still people who want to learn and know the technical details, and I’d like to be useful to them.
Speaking of which
What is different this time? With the help of AI tools I was able to accelerate not only development but deployment as well. What would have taken me several weeks took at most two afternoons. My takeaway from this and other AI-assisted projects: AI is still most useful in the hands of someone who knows exactly what they want. For this project I knew exactly what stack I wanted, I knew how I was going to deploy it and from where, and I knew what security precautions to take. Those are all specs, and I fed them to the model.
I know some readers will reach for the vibe coding label. That’s okay; there is nothing I can do to dissuade them. But I do not blindly accept an LLM’s output. I always review it.
Vibe coders typically complain that they exhaust their tokens quickly, and that does happen when you vibe code. Because I know exactly what I want and give the LLM specific prompts, I hardly run into that. It makes sense, too: most vibe coders do not yet know what they want, so the activity becomes prompting the model until things just work and they are happy with the result. That’s not really what I do.
My workflow is to sketch what I want, choose the stack, hand over links to the documentation, and have the model build one piece at a time while I review the output. It is worth noting that manual intervention is becoming less frequent. Models like Claude Fable still need it, but I would estimate they one-shot a prompt around 80% of the time, especially when the prompt is detailed.
Anyway, my point is that technical knowledge is still worth learning. My theory is that LLMs will be cheaper to run in the long run when knowledgeable people operate them.
Now I have a site design I am pleased with, in a style I like, and the time that would have gone to fighting the tooling can go to writing. The words on this blog are still my own. I use AI to help me proofread, but I will never have it create art on my behalf. I am a firm believer that art is something only humans can make. I have my own thoughts on that, but they are best left for another post.
As for the voxelbrew.sh stack itself, there will be a part two with a deeper dive into each service, along with a look at open sourcing the project.