For the first actual content to be posted to this blog is nothing other than exactly how I deployed and update it!

Architecture and Infrastructure

First, let’s start off by listing our current Architecture/Infrastructure. I am a huge fan of self-hosting, and previously have had a bare-metal server which ran my home-lab k8s cluster but I have since migrated to utilizing an amazing open-source project called Ampernetacle, which takes advantage of Oracle Cloud’s very generous free tier, allowing us to deploy a 4 ARM based node (1 vCPU/4GB) cluster on a forever free basis.

For our k8s Ingress, I went with the battle-hardened, easy to use nginx-ingress controller, though in order to fit within our OCI free-tier, some deployment changes must be made in order to limit the Cloud Load Balancer which it requires, might make a separate post explaining how I deployed it in this configuration.

Now onto the blog itself, since I am a very lazy writer I wanted to keep things as simple as possible for the publishing process, given that I had previously experimented with Hugo, that is the static site generator I chose to use, along with [Forestry.io ]()for a Headless CMS with a very sleek and friendly UI for writing and publishing. Since Forestry actually uses Git as it’s backend for content, I decided to use Github Actions in order to automate the building of the Docker image to be deployed, which is executed on every commit to the main branch, so we kinda have the following workflow:

Setting up both Hugo and Forestry.io is pretty straightforward and very well documented on both of the tools websites. So let’s focus on actually setting up the Docker image and Github Actions workflow.

Containerizing Our Blog

For containerizing our blog, Hugo actually builds all of the project into a single static folder, containing html, JS and CSS assets, so we’ll use a multi-stage build containing of two stages:

  1. Build static assets with Hugo
  2. Utilize a basic nginx web-server image to serve our static content

Here’s how the Dockerfile looks:

# Utilize alpine as our builder image
FROM alpine as builder

# Add Hugo to our image in order for us to build our static content
RUN apk add --update \
    hugo

# Copy over the blog files
COPY . /site

#Change into the directory
WORKDIR /site

#Compile into static assets
RUN hugo -D

FROM  nginx as web
# Copying static assets from builder stage
COPY --from=builder /site/public /usr/share/nginx/html

# Only for documentation of the standard NGINX port, we can ommit the CMD instruction since it will use the default one from the image we are building up.
EXPOSE 80

And here I ran into my first problem while trying to make a first manual deployment of this image to my home-lab cluster. Upon creating the deployment the pods would enter a CrashLoopBackOff status immediatly, checking the pod logs we would see the following error:

exec /docker-entrypoint.sh: exec format error

Since I was building this image locally on my Ubuntu desktop running an AMD CPU, and trying to deploy this image to a k8s cluster based on ARM, we would get an exec format error upon trying to execute it, since it was built for the wrong architecture. In order to overcome that we would have to utilize a new feature of the Docker CLI: [Docker Buildx]().

With it, we are able to build [Multi-platform images]() and push them to our Container Registry of choice, upon pulling the image, the Docker Daemon will automatically select the appropriate image for the OS and Architecture which it intends to run on.

So, for a first manual build and push of this image we run the following command

docker buildx build --push --platform linux/arm64/v8,linux/amd64 --tag ghcr.io/<git-username>/blog:latest .

And now we have a healthy deployment of our blog on our kubernetes cluster

❯ k get pod -n personal   
NAME                               READY   STATUS    RESTARTS   AGE
blog-deployment-65749794c9-gl4fd   1/1     Running   0          50m

b