Migrating from Cloudflare Pages to Codeberg Pages wasn't that difficult, actually

Background

For those not aware, on March 22, 2025, I registered this domain. Within a couple of days I had written a rather basic static site using Eleventy, which has got me covered ever since.

Of course, writing a website is only half the job: one also needs a place to host it. I hosted the site's source code over at GitHub and used Cloudflare Pages to host the website itself. Cloudflare has some really nice integration both with GitHub and GitLab (although I can't have an opinion on the latter, since I haven't used it), and updating my site was as simple as editing markdown files for my blog posts and then pushing them over to GitHub.

As time passed by, I became more and more interested in the whole concept of tech sovereignty, especially after realizing the degree to which Europe is dependent on American infrastructure. Obviously, in today's world, it isn't possible to be 100% independent of each other, especially in the tech field. Cloudflare, for example, might be an American company, but many of the features they offer, like their data centers in ~350 cities or their unmatched DDoS protection make them practically have a monopoly on their market. That being said, I am not a company that needs unmatched website protections. I am simply an individual messing around and I at the very least don't want my website to go down with the entire Internet. My stance towards GitHub is a bit worse, since their new WebUI makes the website tedious to use (my Firefox desktop browser currently reports it takes anywhere from 3.8s to 7.7s to fully load a page after clicking on any of the navigation buttons. Codeberg takes anywhere from 1s to 1.5s and the navigation feels smoother).

I had already created a Codeberg account a while back to open an issue at the Conversations mobile XMPP client, so all I had to do was actually migrate or create new projects there.

Getting the first website live

Before making the move, I wanted to see how easily I could deploy a website. Thankfully for me, Codeberg has its own static site hosting service, Codeberg Pages. I eventually decided to make a USAID death counter, both because I was moved by a study that said that, in the best-case scenario, the deaths caused by USAID's defunding are expected to be around 8.475.000 (if you have no idea what I am talking about, check out this), and because this is, if anything, the most characteristic example of the consequences of being overwhelmingly dependent on the US, albeit, in this case, it isn't tech sovereignty that is lost, but human lives.

DNS shenanigans

I purchased my domain from an Estonian registrar, Zone, but since until lately my website was hosted in Cloudflare, I had transferred DNS control to it. Now that I don't really need to use it for anything else, I might as well transfer it back (as of writing this, Cloudflare is in charge of this domain's DNS).

I initially only changed the nameservers of the usaid. subdomain of this website to Codeberg Pages, and it took me a while to both realize that I had to set all DNS records in Cloudflare to "DNS only" (before that I couldn't even connect to the site due to SSL errors).

Choosing a framework for the website

I used Svelte for it, mainly because I hadn't used it in a while. Even with Svelte, the built website is less than 100kB, which for me is marginally acceptable for something like that. I also chose to use pnpm as a package manager for it, which I prefer over npm, since it doesn't bloat up my node_modules folder, allows me to delay dependency updates and thus prevent supply-chain attacks (to a degree). Furthermore, since v10, pnpm doesn't run any postinstall scripts by default, which is how most supply-chains attacks in the npm registry run malicious payloads.

CI in Codeberg

There are four ways to set up CI (continuous integration) in Codeberg:

  1. Use Codeberg's Woodpecker CI instance
  2. Use a self-hosted Woodpecker CI instance
  3. Use Codeberg's ForgeJo Actions runners
  4. Use a self-hosted ForgeJo Actions runner

For reasons I explain below, I am not currently in a position to self-host anything, and while Codeberg offers ForgeJo action runners, they state that they are providing them "in limited fashion" and recommend to use Woodpecker CI if one wants to run their CI in Codeberg (source here). Furthermore, while anyone can run ForgeJo Actions, due to the fact that they don't have an approval workflow like Woodpecker CI, the resource limits there are tigher. That shouldn't have mattered for me, since pipelines for such small websites usually take less than a couple of minutes. Nevertheless, since they recommended that people use Woodpecker CI instead of ForgeJo Actions, that's what I went with. I applied a request to get access to me and got it rather quickly.

Learning the basics of Woodpecker CI

Woodpecker CI follows a container-centered approach, whereas ForgeJo Actions are more similar to GitHub's CI. Each step is run within its own container.

In our case, we have 3 workflows steps. The first one is the clone step, where we clone the repository on which we want to perform an action. In the next one, we use the official pnpm container image to run the necessary commands and scripts to build the website.

The third step, deploying the actual website, was the more challenging bit. I couldn't find any clear way to deploy a website using Woodpecker CI, since all the documentation I stumbled upon said that either I had to use ForgeJo actions or push my changes to a pages branch and use a webhook to deploy it from there. I initially used plugin-codeberg-pages-deploy for deploying the build output, but since its README stated that it uses a deprecated approach, I found another plugin, git-pages-woodpecker-plugin, which allows me to directly upload the output of my build step to my website by using a non-deprecated approach.

Doing it for real

The USAID death counter website was merely an exercise to learn my way around Codeberg Pages and Woodpecker CI.

Upon migrating my repository to Codeberg using its built-in migration tool, I had to migrate my deployment pipelines from GitHub Actions to Woodpecker CI. Thankfully, I had been using the same three-step as described above for my personal website, so, after fighting a bit with Woodpecker CI because each time something just didn't want to work, I was able to get my website up-and-running.

The other day I realized I had forgotten to set a couple of environment variables that contained, for example, my personal email and my contact PGP key. It isn't like these are top-secret information, but I just wanted to make the scraper's job just a bit harder. After doing that & updating all applicable github.com's to codeberg.org, I had effectively moved my personal website from GitHub & Cloudflare Pages to Codeberg & Codeberg Pages.

While writing this article, I got really bothered of manually having to amend my commits to set the article date to the Git commit date, so I set up a small git hook under .githooks/pre-commit to take care of that for me and replace any occurrences of "GIT_COMMIT_DATETIME" with that.

CSS hell

Wanting to express my happiness that my website was now hosted on European infrastructure, I decided I might as well put a message on my site's footer (the one you are seeing right now! If, for whatever reason, it has changed, it used to be "Proudly hosted with Codeberg Pages! 🇩🇪 🇪🇺")

Except... the flags' colors looked a bit off, like these ones: 🇩🇪 🇪🇺

The issue came from a dark-theme CSS stylesheet I had found in the wild and that I quickly added to my site because I couldn't be bothered to write a dark-theme CSS stylesheet back in the day. Overall, it did what I wanted to do, but by then I had already realized that something with wrong with it, due to my avatar in the about me me looking undersaturated. It was only after it seriously started bothering me due to the aforementioned flag emojis that I decided to check what's the deal.

The aforementioned CSS stylesheet uses the hue-rotate CSS filter for all the heavy lifting. Normally, that shouldn't cause an issue, however, to not cause a color invertion effect on images and videos, that filter is then applied again to them, in an attempt to try and cancel the effect. This ends up backfiring, since hue-rotate does not preserve saturation or lightness, leading to the effect seen below.

I removed that CSS filter from the HTML body element and instead used the background-color & color CSS properties, while retaining the hue-rotate filter for elements unlikely to cause similar issues, such as audio & code elements, alongside anchor (<a>) elements without any images in them.

100% transparency

I also decided that I might as well add a link to the pipeline that deployed that specific version of the website, just for the sake of transparency. The process for that wasn't difficult at all, since Woodpecker CI injects environment variables, such as CI_PIPELINE_URL and CI_COMMIT_SHA, to all runner images, so all I had to do was modify my Eleventy JavaScript configuration file to pass the environment variables from process.env as a global env variable, available to all my templates and modify the default template's footer accordingly.

Apart from all that, I also decided to make the footer responsive, because it looked like it went off the screen on mobile devices, so I made the aforementioned flag emojis and pipeline deployment link disappear.

Now what?

My website might not be perfect, but until now I have enjoyed every single step of building it, including writing this blog post. Some parts might have been more painful than others (I am looking at you, DNS), but nevertheless, through this process I at the very least got familiar with Woodpecker CI, and I have to say that its approach is, at the very least, interesting.

Now that I don't really need to use Cloudflare for anything else, I might as well move DNS control back to Zone. I believe that Cloudflare is excellent for commercial purposes, but for sites like this one, it's just overkill.

I also mentioned using environment variables for my personal email & PGP contact key, but since Codeberg Pages doesn't seem to at least obfuscate email addresses (Cloudflare did that, but that was a simple ROT cipher that would be deciphered on the client side as long as JavaScript was enabled), I will probably just embed them in the site's source code.

I previously mentioned that I can't really self-host anything right now. That's both because I am preparing for university and, in case I don't get accepted into my university's dorm, that will leave me with less money. Furthermore, the only hardware that I own and could run software like Woodpecker CI is a Raspberry Pi 4, and I am considering using that for other purposes. I would like to donate some money to Codeberg as an exchange for them hosting this website and any future Git repositories I will create, but I don't make any money of my own yet. Once I do, I will start exploring the fantastic world of self-hosting & make sure to send Codeberg a recurring donation. It's the least I can do.

P.S. If you too value European tech sovereignty, consider donating to Codeberg or similar projects.