This site is built with Next.js, a React framework that’s great for server-side rendering, which boosts load times by generating content at build time and improves SEO. Next.js also provides automatic image resizing, which is really useful for handling different screen sizes.
I’m using the output: standalone
option in my build process, which bundles everything into a single package, including a server.js
file that handles image resizing and serves all assets. I package all of this into a Docker image and run it on my Kubernetes cluster.
Initially, I hosted the site on Vercel, but I wanted more control, so I moved everything to my own K3s-based Kubernetes cluster. (If you’re curious about how I set up the cluster, I wrote a blog post about it here.)
Build Next.js
Build Next.js app in a builder step
Move Build
Move the resulting Next.js build to the actual image
Upload Image
Upload the image to home Docker registry tagged with commit SHA
Apply Helm
Apply Helm release using Helmfile with commit SHA as image tag
A major concern when building a Docker image is size. If you go with a standard non-alpine based Node.js base image and just install all dependencies, you’ll end up with an image that’s over 1.6GB. That’s way too big for production.
Thankfully, Next.js offers a template to slim down the image. I use the node:20-alpine
image, which is significantly smaller (116MB) compared to the default Debian image (around 1.1GB). Then, I follow a multi-stage build process:
This approach results in a much smaller image—about 214MB, which is reasonable for production.
Here’s the Dockerfile I use:
For deployments, I use a combination of:
Here’s a simplified version of the process:
This is the helmfile I use:
One of the things I liked about Vercel was the automatic preview deployments for pull requests (PRs). When you open a PR on Github, Vercel automatically deploys the changes and gives you a preview link. I wanted to bring that feature to my setup too!
So, I set up GitHub Actions to do the following:
Here’s how it looks in the GitHub Actions workflow:
This setup mimics the PR-based preview feature of Vercel, but with full control over my infrastructure. When the PR is merged or closed, the deployment is cleaned up automatically.