AB.
← All insights
3 min read

Building my website, part 1: why I built it and the tools I chose.

The first in a series about building this site from scratch: why I wanted to make it myself, the tools I picked, and how they fit together.

Why build it myself?

I've come a long way in my career without ever really having the tinkerer mindset. My GitHub is full of unfinished projects. Maybe it was life, maybe confidence; probably the same reasons as anyone's.

But after 3+ years in the field, I finally have that itch. I'm more confident in my programming, and I've started asking myself new questions: What can I make? How can I make it? Which tools will make my life easier?

So this site is my playground. I want to build for the love of it, experiment, and share what I learn along the way, in case it helps someone else.

This is part 1 of a series. Here I'll cover the tools I chose and how they fit together. The later parts get into the problems I ran into, because there were quite a few.

The toolbox

  • Laravel (PHP 8.4): the backend. It handles routing, the database, logins and the pages you're reading now.

  • React + TypeScript, via Inertia.js: for the interactive parts. Inertia lets React pages talk to Laravel directly, without me building a separate API.

  • Tailwind CSS: styling straight in the markup, which keeps the design consistent.

  • Vite: builds the frontend, and reloads the page instantly while I'm working on it.

  • FrankenPHP: the web server. It bundles Caddy and PHP into a single program, which makes it a great fit for a container.

  • PostgreSQL on Neon: a hosted database, so there's no database server for me to look after.

  • Pest: the testing framework. Every feature gets tests that run automatically.

  • Docker and Docker Compose: the app runs in a container, both on my machine and in production.

  • GitHub Actions and GitHub Container Registry: the CI/CD pipeline. It tests every change, then packages it into a container image.

How it all fits together

Every change starts with a git push, and from there it's automated:

git push to main
     ↓
CI: code style · linting · type-checked build · tests
     ↓  (only if everything passes)
CD: build the production Docker image
     ↓
image published, tagged with the exact commit
     ↓
deploy to the live site

The rule I care most about: a broken commit can never become a deployable image. If a single test fails, nothing gets published. Every image is also tagged with the commit it came from, so I always know exactly what's running, and I can roll back if I need to.

One image, everywhere

The Docker image is built in stages. One stage installs the PHP packages, another builds the frontend, and the final stage keeps only the results on top of FrankenPHP. Node, Composer and the build tools never make it into the final image, so it's smaller and has less that can go wrong.

Locally I run that same production image, with my code mounted into it so my edits show up instantly. That way my laptop runs the same PHP version, extensions and web server as the live site, and "it works on my machine" actually means something.

The catch: Windows

I build all this on Windows 10 Home, with Docker Desktop running Linux through WSL 2. That turned out to be where most of the adventure was. In part 2 I'll start with the moment Docker refused to start, and an error message that pointed me in completely the wrong direction.

What's next