Deploying from Forgejo to Cloudflare Pages
Cloudflare Pages' built-in Git integration only connects to GitHub and GitLab — not a self-hosted Forgejo/Gitea instance. To deploy from Forgejo, a Forgejo Actions workflow builds the site and pushes it to Pages with Wrangler (Direct Upload).
The workflow lives at .forgejo/workflows/deploy.yml
and runs on every push to main.
graph LR
A[git push main] --> B[Forgejo Actions runner]
B --> C[zensical build --clean]
C --> D[wrangler pages deploy site]
D --> E[Cloudflare Pages]
No secrets in the repo
The API token and account ID are stored as Forgejo Actions secrets, never committed. The project name is a plain variable.
1. Register a Forgejo Actions runner
Forgejo Actions needs a runner (act_runner)
to execute jobs — the instance has none by default. On a host with Docker:
# Get a registration token from:
# Repo → Settings → Actions → Runners → Create new runner
# (or a site-wide token under Site Administration → Actions → Runners)
docker run -d --restart=always --name forgejo-runner \
-v /var/run/docker.sock:/var/run/docker.sock \
-v $PWD/runner-data:/data \
-e FORGEJO_INSTANCE_URL=https://gitea.vanth.internal \
-e FORGEJO_RUNNER_REGISTRATION_TOKEN=<registration-token> \
code.forgejo.org/forgejo/runner:latest
The runner registers with labels (e.g. ubuntu-latest) mapped to Docker images.
ubuntu-latest maps to catthehacker/ubuntu:act-latest, which already includes
Python, Node, and npm — everything the build needs.
Then enable Actions on the repo: Settings → Actions → Enable.
2. Create the Cloudflare Pages project
Create a Direct Upload project (not Git-connected — Wrangler pushes to it):
npx wrangler pages project create docs-zensical --production-branch main
…or in the dashboard: Workers & Pages → Create → Pages → Direct Upload.
Don't mix Git integration with Direct Upload
A Pages project connected to a Git repo rejects wrangler uploads. If this
site is already deploying from the GitHub → Pages
integration, either point this pipeline at a separate Pages project, or
disconnect the Git integration and let Forgejo be the single source of deploys.
3. Create a scoped API token
Cloudflare dashboard → My Profile → API Tokens → Create Token → Custom token:
| Permission | Scope |
|---|---|
| Account → Cloudflare Pages → Edit | Your account |
Copy the token, and grab your Account ID from the Cloudflare dashboard sidebar
(or wrangler whoami).
4. Add the secrets and variable in Forgejo
Repo → Settings → Actions:
| Type | Name | Value |
|---|---|---|
| Secret | CLOUDFLARE_API_TOKEN |
the token from step 3 |
| Secret | CLOUDFLARE_ACCOUNT_ID |
your Cloudflare account ID |
| Variable | CF_PAGES_PROJECT |
the Pages project name (e.g. docs-zensical) |
5. Push
git push forgejo main
The runner picks up the push, builds with Zensical, and uploads site/ to Pages.
Watch it under Repo → Actions. Once the first deploy succeeds, add your custom
domain on the Pages project (Custom domains → Set up a custom domain).
Notes
- Mirroring GitHub: this repo also has a
.github/workflows/docs.ymlthat deploys to GitHub Pages. The two CI systems ignore each other's workflow directories, so keeping both remotes in sync is fine. - Action resolution: Forgejo resolves
uses:actions from the runner's default actions URL. Ifactions/checkoutfails to resolve, set the runner'sDEFAULT_ACTIONS_URLtohttps://github.com, or pin the full URL in the workflow.