Deploy a Zola Site to Codeberg Pages with a Custom Domain
Since Codeberg's official Forgejo Action doesn't support custom domains yet, here's a simple bash script that handles the deploy for you.
If you’re hosting a static site on Codeberg Pages, in my case built with Zola, you’ve probably run into the same friction I did.
Codeberg is in the middle of migrating to a new Pages backend (git-pages), and custom domain support for the automated action just isn’t there yet. So if you want deploys to actually work with your own domain, you need to push the built output to the pages branch yourself.
So I wrote a small bash script to handle it. Here’s what it does and why.
The script
#!/bin/bash
set -e
GREEN='\033[1;32m'
BLUE='\033[1;36m'
BLANK='\033[0m'
step() {
printf "\n${BLUE}==> %s${BLANK}\n" "$1"
}
done_msg() {
printf "\n${GREEN}==> %s${BLANK}\n" "$1"
}
step "Building Zola site..."
zola build
step "Stashing build output..."
TEMP_DIR=$(mktemp -d)
cp -r public/. $TEMP_DIR/
step "Switching to pages branch..."
git checkout pages
step "Clearing branch contents..."
git rm -rf . > /dev/null 2>&1 || true
step "Copying new build..."
cp -r $TEMP_DIR/. .
rm -rf $TEMP_DIR
step "Committing and pushing..."
git add .
git commit -m "Deploy $(date '+%Y-%m-%d %H:%M')"
git push origin pages
step "Switching back to main..."
git checkout main
step "Restoring submodules..."
git submodule update --init --recursive
done_msg "Site deployed to Codeberg Pages."
The first thing the script does is set -e, which makes it exit immediately if any command fails. Without it, a failed build could still trigger a deploy of stale files. Always worth having in deploy scripts.
After zola build, the output is in public/. Before switching branches (which would wipe it), we stash it in a temporary directory. Using mktemp instead of a hardcoded path like /tmp/zola-build avoids collisions if you ever run this twice at the same time.
TEMP_DIR=$(mktemp -d)
cp -r public/. $TEMP_DIR/
We switch to pages and nuke everything in it. The || true is there because git rm will complain on an empty branch, harmless but noisy. We suppress it and move on.
git checkout pages
git rm -rf . > /dev/null 2>&1 || true
Copy the build in, clean up the temp dir, commit with a timestamp, and push. Nothing clever here, just the mechanical part of the deploy.
cp -r $TEMP_DIR/. .
rm -rf $TEMP_DIR
git add .
git commit -m "Deploy $(date '+%Y-%m-%d %H:%M')"
git push origin pages
This last bit is easy to forget. Switching branches doesn’t restore submodules automatically, so without it you’d come back to main with a missing or detached theme. The --init --recursive covers nested submodules too.
git checkout main
git submodule update --init --recursive