Codeberg Pages
Deploy your Hwaro site to Codeberg Pages, free static hosting backed by Codeberg's Forgejo instance.
How Codeberg Pages Works
Codeberg serves static sites in two modes:
- User / org site — a repository named
pagesunder your account. Codeberg serves the default branch of that repo athttps://USERNAME.codeberg.page/. - Project site — any other repository. Codeberg serves a branch named
pagesathttps://USERNAME.codeberg.page/REPO/.
This distinction matters: a project site pushes to a pages branch, while a user site pushes to the default branch (typically main) of the pages repo. The workflow below defaults to the project-site case and exposes PAGES_BRANCH so the user-site case is a one-line change.
Prerequisites
- Codeberg account
- Repository on Codeberg (either
pagesfor a user site, or any repo for a project site) - A Codeberg access token with
write:repositoryscope (Settings → Applications → Generate new token)
Method 1: Forgejo Actions (Recommended)
Codeberg supports Forgejo Actions, which is GitHub Actions-compatible. Forgejo Actions is opt-in per repository, so enable it under Settings → Actions before the workflow will run.
Forgejo also accepts workflows under
.gitea/workflows/for backwards compatibility, but.forgejo/workflows/is the upstream-blessed path and the one Hwaro generates.
Generate the Workflow
hwaro tool platform codeberg-pages
This writes .forgejo/workflows/deploy.yml:
---
name: Hwaro Deploy
on:
push:
branches: [main]
workflow_dispatch:
jobs:
deploy:
runs-on: docker
container:
image: ghcr.io/hahwul/hwaro:latest
env:
# Project site: "pages" (default). User/org site (repo named
# "pages"): override to your default branch, e.g. "main".
PAGES_BRANCH: pages
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Build site
run: hwaro build
- name: Deploy to Codeberg Pages
env:
CODEBERG_TOKEN: ${{ secrets.CODEBERG_TOKEN }}
run: |
cd public
git init -b "$PAGES_BRANCH"
git config user.name "${{ github.actor }}"
git config user.email "${{ github.actor }}@noreply.codeberg.org"
git add -A
git commit -m "Deploy: $(date -u +'%Y-%m-%dT%H:%M:%SZ')"
git push --force \
"https://${{ github.actor }}:$CODEBERG_TOKEN@codeberg.org/${{ github.repository }}.git" \
"$PAGES_BRANCH"
Branch history is not preserved. Each run starts with a fresh
git initand force-pushes, so the published branch is treated as a publish-only artifact. Keep your source onmain(or wherever you develop); never edit thepagesbranch by hand.
User Site vs Project Site
The default PAGES_BRANCH: pages targets a project site (any repo, served at USERNAME.codeberg.page/REPO/). For a user / org site in a repo literally named pages, change the branch to your default branch:
env:
PAGES_BRANCH: main # default branch of the `pages` repo
Add the Token Secret
- Go to your repository Settings → Actions → Secrets
- Add a new secret named
CODEBERG_TOKEN - Paste your Codeberg access token (with
write:repositoryscope)
Set Base URL
Update config.toml:
# User/org site (repo named "pages")
base_url = "https://USERNAME.codeberg.page"
# Project site (any other repo)
base_url = "https://USERNAME.codeberg.page/REPO"
Push to main and the workflow will build and deploy automatically.
Method 2: Deploy via hwaro deploy
If you'd rather deploy from your local machine, wire it up as a [[deployment.targets]] entry that calls a small shell script.
Create scripts/deploy-codeberg.sh:
#!/bin/bash
set -e
SOURCE_DIR="${1:?Usage: deploy-codeberg.sh <source-dir>}"
REMOTE_URL="${CODEBERG_REMOTE:-$(git remote get-url origin)}"
PAGES_BRANCH="${PAGES_BRANCH:-pages}"
TMPDIR=$(mktemp -d)
cp -r "$SOURCE_DIR"/. "$TMPDIR"
cd "$TMPDIR"
git init -b "$PAGES_BRANCH"
git add -A
git commit -m "Deploy to Codeberg Pages"
git push --force "$REMOTE_URL" "$PAGES_BRANCH"
rm -rf "$TMPDIR"
chmod +x scripts/deploy-codeberg.sh
Add the target to config.toml:
[[deployment.targets]]
name = "codeberg-pages"
command = "scripts/deploy-codeberg.sh {source}"
Then build and deploy:
hwaro build
hwaro deploy codeberg-pages
# Preview without deploying
hwaro deploy codeberg-pages --dry-run
# User site (push to the default branch instead of `pages`)
PAGES_BRANCH=main hwaro deploy codeberg-pages
As with Method 1, the script force-pushes a fresh init, so branch history is not preserved.
Method 3: Manual Branch Deploy
hwaro build
cd public
git init -b pages
git add -A
git commit -m "Deploy"
git push --force https://codeberg.org/USERNAME/REPO.git pages
For a user site (pages repo), substitute the default branch name (e.g. main) for pages in both the init and push commands.
Custom Domain
Codeberg Pages supports custom domains via a .domains file plus a DNS record. See the official Codeberg docs for the authoritative version.
Repo names with dots are unsupported. Use
-or_in the repo name if you plan to attach a custom domain.
1. Add a .domains file
Place a .domains file at the root of the served branch listing the domains, one per line. The first line is the canonical domain; all subsequent domains are 301-redirected to it. Empty lines and # comments are allowed.
www.example.org
example.org
Drop it under static/.domains so Hwaro copies it into public/ on every build:
static/
└── .domains
Hwaro copies hidden dot-paths from static/ verbatim into public/ on every build (cold or --cache/incremental), so static/.domains always reaches the served branch. See Static Files for details.
2. Configure DNS
Pick one of the three options below.
Option A — CNAME (recommended, simplest). Point your domain at one of these names:
| Site type | CNAME target |
|---|---|
| Personal site | USERNAME.codeberg.page |
| Project site | REPO.USERNAME.codeberg.page |
| Custom branch | BRANCH.REPO.USERNAME.codeberg.page |
CNAME delegates the whole hostname, so you cannot run email (MX) on the same name with this option.
Option B — ALIAS + TXT. If your DNS provider supports ALIAS (or ANAME) records:
| Type | Name | Value |
|---|---|---|
| ALIAS | @ | codeberg.page |
| TXT | @ | REPO.USERNAME.codeberg.page (or USERNAME.codeberg.page for a user site) |
Option C — A + AAAA + TXT. Use this if your provider doesn't support ALIAS, or if your zone uses DNSSEC (which is incompatible with the codeberg.page CNAME):
| Type | Name | Value |
|---|---|---|
| A | @ | 217.197.84.141 |
| AAAA | @ | 2a0a:4580:103f:c0de::2 |
| TXT | @ | REPO.USERNAME.codeberg.page (or USERNAME.codeberg.page for a user site) |
Verify the latest IPs on the Codeberg Pages docs before relying on them. Codeberg occasionally rotates them.
If your zone uses CAA records, add an entry that allows Let's Encrypt so Codeberg can issue a TLS certificate:
@ CAA 0 issue "letsencrypt.org"
3. Update base_url
base_url = "https://www.example.org"
Troubleshooting
Workflow Doesn't Run
- Make sure Forgejo Actions is enabled under Settings → Actions for the repository
- Confirm the workflow file is committed at
.forgejo/workflows/deploy.yml(or.gitea/workflows/deploy.yml)
Push Fails with 401 / 403
- Re-check the
CODEBERG_TOKENsecret value and that the token still haswrite:repositoryscope - Tokens are tied to your account, so make sure the actor has push access to the target repo
404 on the Deployed Site
- Codeberg Pages may take a minute or two to publish after the first push
- For a project site, confirm the branch is named exactly
pages - For a user site, confirm the repo is named exactly
pagesandPAGES_BRANCHmatches its default branch - Verify
base_urlmatches the published URL (no trailing slash)
Custom Domain Doesn't Resolve to HTTPS
- Confirm the
.domainsfile is present at the root of the published branch - If you have CAA records, make sure
letsencrypt.orgis allowed - Allow a few minutes for the certificate to be issued after DNS propagates
See Also
- Deploy Configuration — Target setup and matchers
- CLI Reference — All deploy command options
- Codeberg Pages docs — Upstream reference
- Other platforms: GitHub Pages | GitLab CI | Netlify | Cloudflare Pages