From 852f718af7555b94266ad180bd7a319980488fe7 Mon Sep 17 00:00:00 2001 From: Peter Kurfer Date: Tue, 30 Apr 2024 15:21:30 +0200 Subject: [PATCH] docs: Hugo on Cloudflare pages --- ...2-02-libvirt-podman-netavark-follow-up.md} | 2 +- ...=> 2022-02-libvirt-podman-network-mesh.md} | 0 content/post/2024-04-forgejo-deploy-hugo.md | 155 ++++++++++++++++++ 3 files changed, 156 insertions(+), 1 deletion(-) rename content/post/{libvirt-podman-netavark-follow-up.md => 2022-02-libvirt-podman-netavark-follow-up.md} (98%) rename content/post/{libvirt-podman-network-mesh.md => 2022-02-libvirt-podman-network-mesh.md} (100%) create mode 100644 content/post/2024-04-forgejo-deploy-hugo.md diff --git a/content/post/libvirt-podman-netavark-follow-up.md b/content/post/2022-02-libvirt-podman-netavark-follow-up.md similarity index 98% rename from content/post/libvirt-podman-netavark-follow-up.md rename to content/post/2022-02-libvirt-podman-netavark-follow-up.md index 57d7519..567520b 100644 --- a/content/post/libvirt-podman-netavark-follow-up.md +++ b/content/post/2022-02-libvirt-podman-netavark-follow-up.md @@ -10,7 +10,7 @@ tags = [ ] +++ -This is a follow up post to ["Joining libvirt {{}}s and Podman container in a common network"]({{}}). +This is a follow up post to ["Joining libvirt {{}}s and Podman container in a common network"]({{}}). Therefore I won't cover all the basics again and how to configure libvirt because nothing's changed on that side. ## Podman 4.0 diff --git a/content/post/libvirt-podman-network-mesh.md b/content/post/2022-02-libvirt-podman-network-mesh.md similarity index 100% rename from content/post/libvirt-podman-network-mesh.md rename to content/post/2022-02-libvirt-podman-network-mesh.md diff --git a/content/post/2024-04-forgejo-deploy-hugo.md b/content/post/2024-04-forgejo-deploy-hugo.md new file mode 100644 index 0000000..d3c4ecd --- /dev/null +++ b/content/post/2024-04-forgejo-deploy-hugo.md @@ -0,0 +1,155 @@ ++++ +author = "Peter Kurfer" +title = "Build & deploy a Hugo site with Gitea/Forgejo actions" +description = "How to host a Hugo site with Cloudflare pages and deploy it automatically with Forgejo actions" +date = "2024-04-30" +tags = [ + "hugo", + "cloudflare", + "CD/CD", + "actions" +] ++++ + +I admit it. I like self-hosting. +I like the idea of being able to control every aspect of my infrastructure. +It was only consequent to also self-host my blog. +This article describes my odyssey and why I ended up letting [Cloudflare](https://www.cloudflare.com/) do the hosting. + +In the beginning - there was a repository. +As we all know, the repository is the truth. +When the time came for deploying the blog, I already had a Kubernetes (K8s) cluster at hand so the obvious choice was to containerize the web page and host it there. +I wrote a simple Dockerfile with a multi-stage build, just like this: + +```Dockerfile +FROM docker.io/golang:1-alpine as builder + +WORKDIR /tmp + +RUN apk add -U --no-cache hugo git + +WORKDIR /src + +COPY . /src/ + +RUN hugo --minify --environment production --config config.toml + +FROM caddy as runtime + +COPY --from=builder /src/public /usr/share/caddy +``` + +prepared my deployment manifests and setup a CI pipeline (back then with DroneCI) to deploy everything. + +So far so good, the only complicacy was that I now had two 'truths'. +One was the repository and the second one was the container registry - let alone that I also had to 💸 the storage for both. +Of course, various container registries have cleanup options but being a software engineer, why using something existing when you can build the 11th solution to solve the same problem, right? + +Yes...actually, no! + +In the beginning I just accepted the fact and went on. +Every now and then, when the amount of images became costlier, I manually deleted a few until I reached a reasonable count - say...five, I mean in the end there was no reason to keep any old version at all, but you know, I was lazy. +At some point I had a similar problem at work with our SPAs and I couldn't help but wonder: is this really the best way? +Not only because I'm duplicating the content every time, but also the web server needs patching, every now and then a breaking change in the configuration system happens and so on and so forth. +I came across the possibility to serve a S3 bucket (or similar) directly from a [K8s ingress](https://kubernetes.io/docs/concepts/services-networking/ingress/#resource-backend). +That sounded awesome! +No need to build a container image, no need to waste compute resources, simple copy to S3 bucket and be done with it! + +So I came back to my blog and tried to migrate to this approach. +I wasted a few hours of my spare time, only realizing that - apparently - Cloudflare R2 or some CLI or something else is ignoring the content type of my files, leaving me with `application/octet-stream` which is absolutely useless for web pages. +It might be different when I would use [MinIO](https://min.io/) or AWS S3 but I didn't want to waste even more resources (and 💵) on hosting a MinIO instance in my cluster. +Also, I am already using Hetzner Cloud and didn't feel like distributing my costs around multiple cloud providers, so I started looking for alternative solutions. + +I then stumbled upon [Cloudflare Pages](https://pages.cloudflare.com/). +After a 'quick' prototype I was happy and decided to migrate - actually not so quick, I spent a few evenings on migrating my whole DNS setup to [external-dns](https://github.com/kubernetes-sigs/external-dns) and experimented with Cloudflare DNS for DoS protection but that's a topic for another day. + +The only other problem I had was: I also got rid of DroneCI in favor of [Forgejo Actions](https://forgejo.org/docs/latest/user/actions/). +I know, if I would use GitHub, there would be perfect integration from Cloudflare to build my Hugo page and deploy it, but we don't want to make things too easy, right? + +But using Forgejo Actions also seemed pretty straight forward: + +```yaml +name: Deploy pages +on: + push: + branches: + - main + +jobs: + deploy: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Setup Hugo + uses: peaceiris/actions-hugo@v3 + - name: Build + run: hugo --minify --environment production + - name: Deploy + uses: cloudflare/wrangler-action@v3 + with: + apiToken: ${{ secrets.CF_PAGES_TOKEN }} + accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} + command: pages deploy public --project-name=blog +``` + +Well, not so fast kiddo! + +At first I noticed, when using [Hugo modules](https://gohugo.io/hugo-modules/) you need to fetch those modules before being able to build anything, alright: + +```yaml + // ... + - name: Build + run: | + hugo mod get + hugo --minify --environment production + // ... +``` + +then, obviously, I realized, for being able to fetch those modules, you need a Go SDK, there you go (pun intended): + +```yaml + // ... + - name: Setup Go + uses: actions/setup-go@v5 + with: + go-version: "1.22.x" + - name: Setup Hugo + uses: peaceiris/actions-hugo@v3 + // ... +``` + +and now we're getting - finally - to the point when things got really annoying... +I'm using the [github.com/LordMathis/hugo-theme-nightfall](https://github.com/LordMathis/hugo-theme-nightfall) theme. +Although being a very minimalistic theme, it requires [dart-sass](https://gohugo.io/functions/resources/tocss/#dart-sass). +Even though this also seem straight forward, especially because there's only [documentation for Github Actions](https://gohugo.io/functions/resources/tocss/#github-pages), with Forgejo Actions it isn't. +The key difference between Github Actions and Forgejo Actions is, that Forgejo Actions are running in containers. +The officially recommended way to install `dart-sass` in Github Actions is via `snap`, but snap doesn't really work in containers, so I had find another way. +When doing some research, you might come across the official [`dart-sass` repository](https://github.com/sass/dart-sass) that mentions another installation method: + +```bash +npm install -g sass +``` + +but: + +> The `--embedded` command-line flag is not available when you install Dart Sass as an npm package. + +(*see [here](https://github.com/sass/dart-sass?tab=readme-ov-file#embedded-dart-sass)*) + +unfortunately, Hugo requires the `--embedded` flag, so also not an option. +Eventually I came around this abomination: + +```yaml + - name: Install sass + run: | + export SASS_VERSION=$(curl https://api.github.com/repos/sass/dart-sass/releases | jq -r '. | first |.tag_name | capture("(?[[:digit:]]+\\.[[:digit:]]+\\.[[:digit:]]+)") | .version') + curl -L "https://github.com/sass/dart-sass/releases/download/${SASS_VERSION}/dart-sass-${SASS_VERSION}-linux-arm64.tar.gz" | tar xvz -C /opt/ + ln -s /opt/dart-sass/sass /usr/local/bin/ +``` + +*Don't get confused by the huge capture in the `jq` expression, I'm using this snippet whenever I have to use the version of a package in the filename and this way I don't have to think about, is there a `v` prefix or not, looking at you 'goreleaser' 👀* + +That downloads the latest release of `dart-sass` and makes it available in the `$PATH`. +So far I'm not considering the CPU architecture because whenever possible I'm running my CI jobs on ARM machines anyway, but if I find the time, I might try to implement a custom action similar to `peaceiris/actions-hugo@v3` but with `dart-sass` support. + +You can imagine how happy I was realizing the `cloudflare/wrangler-action@v3` step 'just worked' ™. \ No newline at end of file