Blog Projects About

tjnome@homelab:~$ cat ~/blogs/lumiere-to-live-site.md

From Lumière to a live site: a homelab journey

3 min read

This website is my personal corner of the internet. My preciousss... We wants it, we needs it! And because it's so precious, it's built with a focus on simplicity and ease of maintenance.

Heh heh heh! Unveiling the mechanics

This site runs on Hugo . I write all my content in simple Markdown files, and Hugo transforms them into lightweight HTML pages. No databases, just choo-choo-choo, blazing-fast content delivery. For a personal site, that’s exactly what I want.

My homelab: a descent into the suffering of self-hosting

Instead of relying on external hosting providers, this site lives on my own homelab. It’s a mini-PC humming away, quietly asserting its dominance over my projects. It’s also a continuous learning failing experience and a way to experiment with self-managed infrastructure.

Deployment: Docker and Dokploy

So how does this digital preciousss escape my lair and reach the masses? Through a deployment process that… well, let’s just say it gets the job done.

The Dockerfile is the recipe for the image, defining how the site gets built and served:

#####################################################################
#                            Build Stage                            #
#####################################################################
# Use the 'exts' image which includes Hugo Extended and Node.js.
FROM hugomods/hugo:exts AS builder

# Set the working directory for the build.
WORKDIR /src

# First, copy only the package files and install dependencies.
# This uses Docker's build cache, so 'npm ci' only runs when package files change.
COPY package*.json ./
RUN npm ci

# Now, copy the rest of the source code.
COPY . .

# Run the custom build command. This executes "hugo && pagefind --source public"
# as defined in package.json.
RUN npm run build

#####################################################################
#                            Final Stage                            #
#####################################################################
# Use the lightweight Nginx-only image for the final container.
FROM hugomods/hugo:nginx

# Copy the generated static files from the 'builder' stage's /src/public directory
# into the final image's webroot, which is '/site'.
COPY --from=builder /src/public /site

It’s a multi-stage build. The build stage starts with the hugomods/hugo:exts image, which includes Hugo Extended and Node.js. It copies package.json and package-lock.json first, so npm ci only reruns when dependencies change. Then it copies the rest of the source and runs npm run build, which executes hugo and pagefind to generate the static site.

The final stage switches to the much lighter hugomods/hugo:nginx image and copies only the built files from the builder’s public directory into the Nginx webroot (/site). The final image ends up small and boring, containing nothing but what’s needed to serve the site. Boring is good.

Then Dokploy takes over. It’s an open-source, self-hosted deployment platform that picks up changes from Git, builds the Docker image, deploys it to the server, and handles reverse proxying and SSL.

In short, I write, Hugo builds, Docker packages, and Dokploy deploys, all on my own hardware. Now, if you’ll excuse me, my gaming backlog isn’t going to clear itself, and I have a Monolith to conquer, a tainted path leading directly to the Paintress!