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 — 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 — 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
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 — 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