.TH codeberg-pages 7 .SH NAME codeberg-pages \- Codeberg Pages with SSL and Custom Domain .SH AUTHOR Artyom Bologov .SH SYNOPSIS Moving from Gitlab Pages to Codeberg Pages. There are some difficulties, but nothing impossible. Here’s what I did. .SH TEXT .P Yesterday night, Gitlab sent me an email saying I’m running out of “compute minutes.” Whatever that means. Most likely, I was running out of CI/CD resources when building this website on Gitlab Pages. It’s been a thing going for some time: .P * I’m trying some new absurd tech stack. .P * And Gitlab keeps sending me warnings regarding depleted resources. .P I guess I went overboard with \fBthis-post-is-ed (7)\fP using ed(1) as my new site generator. Nonetheless, that’s not nice of Gitlab Pages offering “infinite and free” website builds. And then saying I have no resources anymore. .P So I decided to do the most Saturday evening thing programmers do: migrate my website to an absolutely different hosting setup. .UR https://codeberg.page To Codeberg Pages .UE . .P Codeberg doesn’t offer some of the features Gitlab provides. In particular, no CI/CD/website builds, and no automatic SSL certificates. Thus this post: to document what I went through to reproduce my current setup on Codeberg: .TP .B Domain aartaka.me and subdomains, managed by Namecheap. .TP .B SSL Certificates With DNS verification, issued by Let’s Encrypt \fBdigital-bum (7)\fP (because I’m poor and anti-capitalist.) .TP .B Email Custom Domain Email by Disroot. .TP .B Build process Makefiles and ed(1). .P So here’s how I went about it. .SH Getting Started with Codeberg Pages .UR https://docs.codeberg.org/codeberg-pages The documentation by Codeberg is good enough .UE : Create a \fBpages\fP repository, add HTML files to the root, and \fBgit push\fP them there. .P I diverged from these instructions once: I named the default branch “pages”, instead of “main”. This way, my website is deployed from “pages”, while “main” is reserved for my experiments and drafts. .P I also had to move all my files to repository root. On Gitlab, I had them in \fBpublic/\fP subdirectory for deployment. But that’s too minor to care. .SS Webhooks .P Update March 2026: since recently, Codeberg started to move towards \fBgit-pages\fP server support. Which is wonderful news! But that also means you have to set up webhooks to make it refresh on every commit. .UR https://docs.codeberg.org/codeberg-pages/#user%2Forganization-websites See Codeberg docs for the full procedure .UE . .SH DNS & Email .P .UR https://docs.codeberg.org/codeberg-pages/using-custom-domain Again, documentation by Codeberg works .UE : Create a \fB.domains\fP file with your domains: .P .in +4n .EX aartaka.me aartaka.codeberg.page pages.aartaka.codeberg.page pages.pages.aartaka.codeberg.page .EE .in .P — My .domains contents .P and add DNS records: .TP .B CNAME The simplest case for when you only care about the website. Doesn’t work with e.g. my custom domain email setup. .TP .B ALIAS A simple enough case that works with my email. That’s the one I picked. .TP .B A/AAAA Didn’t try it, sorry. .P So here’s my final setup: .P .in +4n .EX ALIAS @ codeberg.page. TXT @ pages.aartaka.codeberg.page .EE .in .P — DNS records for Codeberg Pages (as displayed by Namecheap, @ is aartaka.me) .P The email is managed by MX and SPF records, and ALIAS/TXT don’t interfere with these. Should be fine, given that I’m still receiving spam. .SS Addendum: Subdomain Records .UR https://r7rs.aartaka.me So I made an HTML5 rendering of Scheme R7RS standard .UE . It required a new subdomain connected to a new Pages-enabled repository. So I added a new subdomain in Namecheap: .P .in +4n .EX CNAME r7rs r7rs-html5.aartaka.codeberg.page. .EE .in .P — The only additional record necessary to enable a subdomain .P This and .domains file in the original repository having the \fBr7rs-html5.aartaka.codeberg.page\fP domain. Easy. .SH Setting Up SSL with Let’s Encrypt .P This is likely covered elsewhere, but I want to reproduce this anyway. Especially given that Codeberg docs don’t cover this except for one note in Custom Domain page. So I need to explain future myself how in the world all of this works. .P So... Let’s Encrypt. They are doing miracles, but they require domain ownership check first. And this check is done by putting a file into a \fB.well-known\fP subdirectory on the server. .P The problem is: Codeberg blocks requests to \fB.well-known\fP. So one has to use alternative verification strategies. Like DNS verification. .UR "https://www.digitalocean.com/community/tutorials/how-to-acquire-a-let-s-encrypt-certificate-using-dns-validation-with-acme-dns-certbot-on-ubuntu-18-04" Here’s the DigitalOcean guide I followed .UE . (Update 2026: Certbot’s own DNS challenge type is fine by itself, even without this script.) But the procedure is relatively simple: .P * Install certbot, .P * Download the certbot extension (acme-dns-auth.py) they’re linking to, .P * Make it executable with \fBchmod +x acme-dns-auth.py\fP, .P * Add it to the path where certbot and find it: /etc/letsencrypt/acme-dns-auth.py .P * And run a magic incantation: .P .in +4n .EX sudo certbot certonly --manual --manual-auth-hook /etc/letsencrypt/acme-dns-auth.py --preferred-challenges dns --debug-challenges -d *.aartaka.me -d aartaka.me .EE .in .P — Certbot call with a DNS auth extension .P It provides all the instructions, and the guide above augments it. Here are the records I had to add: .P .in +4n .EX // Notice the single letsencrypt.org here: // Namecheap didn’t allow me to include any value beyond that. CAA 0 issue "letsencrypt.org" CNAME _acme-challenge xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx.auth.acme-dns.io. .EE .in .P — CAA record to allow certificates from Let’s Encrypt and the actual challenge value (redacted) .P After DNS records update (I waited for two minutes, but better wait five, just in case,) you can press \fBEnter\fP to continue setup. Certbot will register the domain and generate your certificates. In case you have multiple subdomains, repeat the procedure and records for these, or use wildcard domain, like me. .P When the certificates expire, I’ll have to run this scary one-liner again. But that’s going to happen in 90 days, so who cares? .SH Website Generation .P Now the hardest part: Codeberg doesn’t offer any CI/CD beyond static resource publishing. I might’ve tried to hack them or use some Turing-complete CI quirk. But I don’t want trouble. .P So I’m generating my pages locally and then pushing them to Codeberg. That’s where the “pages” branch comes into play. I’m keeping my “main” clean, and then switch to “pages” and generate all the posts in all the formats: .P .in +4n .EX git checkout pages git merge master make all -j 4 git commit --all -m "..." git push .EE .in .P — Every post publication procedure (I usually do that in Magit, though) .P I can’t imagine the trouble I’d need to go through if I was using some fancy blog engine. Endless npm vulnerabilities and gigabytes of \fBnode_modules\fP, likely. But, luckily, I’m using ed(1) as my SSG and I don’t have to mess with any of that. And it’s taking a really short time to generate all 5 (!) formats for all the ~50 pages I have. .P So yes, you might be dissatisfied with Codeberg Pages if you want fancy blog engine builds somewhere in the cloud. But I don’t do that, so I’m perfectly fine with Codeberg. Still, do give it a try! .SH Changes Due to git-pages Move .P Codeberg recently moved to a new Pages back-end: .UR https://codeberg.org/git-pages/git-pages git-pages .UE . This changed some behaviors I previously took for granted. .P One of the behaviors that changed was automatic redirection of path to path.html. It used to work in previous server, but now it’s intentionally unsupported. The fix would be to add redirects to _redirects file: .P .in +4n .EX /codeberg-pages /codeberg-pages.html 301 .EE .in .P — Post redirects .P Another change that I’m not sure even worked in the old Pages server: splats cannot be generated with extensions. This rule does not work: .P .in +4n .EX /*.h /:splat.html 301 .EE .in .P — The rule that doesn’t work because splats + extensions are not supported .P Given that splats don’t work this way, one cannot just write a rule redirecting arbitrary pages to their .html versions: .P .in +4n .EX /* /:splat.html .EE .in .P — This page -> page.html rule DOES NOT WORK .P It doesn’t work because splats + extensions are unsupported \fIand\fP because it results in infinite recursive redirect. So a better way to handle this would be generating redirects for every page separately. This is how I do it in my site Makefile (huge thanks to whitequark for the initial implementation!) .P .in +4n .EX .PHONY: _redirects _redirects: _redirects.in $(html_objects) $(html_only_objects) # _redirects.in is non-auto-generated redirects, e.g. for old posts and splats cp _redirects.in _redirects $(foreach object,$(objects), echo "/$(object) /$(object).html 301" >> _redirects ;) $(foreach object,$(_html_only_objects), echo "/$(object) /$(object).html 301" >> _redirects ;) .EE .in .P — Makefile rule for _redirects .P But these are individual problems. While solutions to these are useful (and I’d have benefited from them before annoying the devs.) It’s better to debug these oneself. Two ways to get errors and other auxiliary info for the git-pages-deployed site: .P .in +4n .EX curl https://aartaka.me/.git-pages/manifest.json git-pages-cli --debug-manifest https://aartaka.me .EE .in .P — Two options for manifest debugging .P This results in huge JSONs with a lot of useful info on site deployment. So yeah, you’re more self-sufficient now! .P Good luck with your Codeberg Pages! .SH COPYRIGHT .UR https://creativecommons.org/licenses/by/4.0 CC-BY 4.0 .UE 2022-2026 by Artyom Bologov (aartaka,) .UR https://codeberg.org/aartaka/pages/commit/a91befa with one commit remixing Claude-generated code .UE . Any and all opinions listed here are my own and not representative of my employers; future, past and present.