Hosting a Blog
Set out to host this blog to document and share things. This would be one of the first times to really put all the pieces together of this stack. Gitea was choosen for git source code hosting and it does have a container registry so will go ahead and use it to push artifacts to.
Source Repository Structure
hugo/
docker/
helm/
terraform/
Hugo
Initialized the hugo projects structure with
hugo new site hugo
After creating an empty project its required to add a theme, else you get an empty site. Not the first whose run into this, that Step 3 of the quick start is a doozy.
And from here on have to remember to do a git clone --recurse-submodules
or git submodule update --init
or the site will not generate correctly.
Docker Container
To get it hosted was going to need to serve the static html content in some sort of web server. After finding this reddit post and this articledecided that nginx was the way to go.
Prefering the minimal alpine images and to keep versions well pinned.
https://medium.com/@lorique/howto-multi-stage-docker-builds-with-hugo-78a53565d567
FROM docker.io/library/alpine:3.21 as hugo-builder
RUN apk add --update hugo=0.139.0-r5
WORKDIR /opt/blog
COPY ../hugo .
RUN hugo build
FROM docker.io/library/nginx:1.28.0-alpine3.21-otel
WORKDIR /usr/share/nginx/html
COPY --from=hugo-builder /opt/blog/public .
Helm
To deploy into kubernetes was going to need a helm chart. Created an empty project using the cli template generation.
cd helm/charts
helm create blog
Needed to update was the repository
location in values.yaml
and the appVersion
in Chart.yaml
.
Since I’ll be hosting this via the cloudflare tunnel will want to include an optional TunnelBinding
cr.
Terraform
Helm does most of the heavy lifting, but terraform will be here to keep track of the versions and configurations used when running helm. It will also create the target namespace since helm doesnt allow for further managing the namespaces post initial creation.
resource "kubernetes_namespace" "this" {
metadata {
name = "blog"
annotations = merge(
(var.linkerd_enabled
? {
"linkerd.io/inject" = "enabled"
}
: {}
),
var.namespace_annotations
)
}
}
resource "helm_release" "this" {
chart = "blog"
name = "blog"
repository = "oci://<gitea host>/helm-charts"
version = "1.0.0"
skip_crds = true
namespace = kubernetes_namespace.this.metadata[0].name
values = concat(
[
yamlencode({
})
],
(var.linkerd_enabled
? [
yamlencode({
podAnnotations = {
"linkerd.io/inject" = "enabled"
"config.linkerd.io/proxy-cpu-limit" = "10m"
"config.linkerd.io/proxy-cpu-request" = "10m"
"config.linkerd.io/proxy-memory-limit" = "32Mi"
"config.linkerd.io/proxy-memory-request" = "32Mi"
}
})
]
: []
),
(!var.resource_requirements_enabled
? [
yamlencode({
resources = {
requests = {
cpu = "0m"
memory = "0Mi"
}
}
})
]
: []
),
(!var.resource_requirements_enabled && var.linkerd_enabled
? [
yamlencode({
podAnnotations = {
"config.linkerd.io/proxy-cpu-request" = "0m"
"config.linkerd.io/proxy-memory-request" = "0Mi"
}
})
]
: []
),
var.values
)
}
At current I’m not hosting a terraform module registry and so far terraform doesnt support oci module repo sources, yet. So for now will settle for git::http://..
references. Comes with some disadvantages. Terraform doesnt cache bust when git is updated. With terraform module hosted with the project the whole repo gets cloned on terraform init making it a bit more heavy weight than I’d like.
Building
With 2 main artifacts there are 2 separate build steps.
Docker Image
podman login <gitea host>
podman build . -f docker/Dockerfile -t <gitea host>/blog:v1.0.0
podman push <gitea host>/blog:v1.0.0
Helm Chart
helm registry login <gitea host> -u <gitea user>
helm package helm/charts/blog
helm push blog-1.0.0.tgz oci://<gitea host>/helm-charts
Deployment
With all the pieces published place its time to deploy.
module "blog" {
source = "git::https://<gitea host>/blog.git//terraform/modules/blog"
values = [
yamlencode({
tunnelbinding = {
enabled = true
fqdn = "evolutionchamber.org"
tunnelRef = {
name = "cluster-tunnel"
kind = "ClusterTunnel"
}
}
imagePullSecrets = [
{
name = "registry-secret"
}
]
})
]
}
This wont be able to pull the repo right away. After the namespace is created will need create Secret
containing the registry login creds. Going to have to figure out a better way to manage auth here.
kubectl create secret docker-registry registry-secret -n blog --docker-server=<gitea host> --docker-username=<gitea user> --docker-password=<gitea token>