Forgejo to GitHub to Cloudflare Pages: A One-Way Publishing Workflow
You can keep a website’s canonical repository in a self-hosted Forgejo instance without replacing Cloudflare Pages’ GitHub integration.
The pattern is:
Developer or authorized agent → Forgejo canonical repository → Forgejo push mirror → GitHub deployment repository → Cloudflare PagesForgejo owns the source. GitHub is a deployment relay. Cloudflare Pages watches the GitHub production branch and builds the site when Forgejo pushes a new commit.
This guide covers the repository and deployment workflow. To build the website itself, use Build a Static Astro Website for Cloudflare Pages.
When this pattern fits
Section titled “When this pattern fits”Use this workflow when:
- Forgejo is your preferred source of truth.
- Cloudflare Pages already builds reliably from GitHub.
- You want humans and automation to commit through Forgejo.
- You do not want to maintain custom deployment code just to remove GitHub from the build trigger.
The operating rule
Section titled “The operating rule”Decide which forge is canonical before configuring anything.
For this guide:
Write in Forgejo. Mirror to GitHub. Build in Cloudflare Pages.What you need
Section titled “What you need”- A working Forgejo instance and permission to manage the source repository.
- A GitHub account that can create or write to the destination repository.
- A Cloudflare account with access to Workers & Pages.
- A website that builds from a Git repository.
- A known production branch, usually
main. - A password manager or secret store for the GitHub credential.
This guide uses HTTPS and a GitHub fine-grained personal access token. Forgejo also supports SSH push mirrors, but token-based HTTPS is easier to scope to one repository and matches the workflow described here.
Step 1: align Forgejo and GitHub before enabling the mirror
Section titled “Step 1: align Forgejo and GitHub before enabling the mirror”A push mirror is not a merge service. The destination must be empty or already contain the history you intend Forgejo to publish.
For a new website
Section titled “For a new website”-
Create the project in Forgejo.
-
Push the initial branch to Forgejo.
-
Create an empty GitHub repository.
-
Do not initialize the GitHub repository with a README, license, or
.gitignoreif those files already exist in Forgejo.
The empty GitHub repository is ready to receive the first mirror push.
For an existing GitHub website
Section titled “For an existing GitHub website”Treat GitHub as canonical during a short migration window:
-
Freeze writes to the GitHub repository.
-
Record the default branch, visibility, branches, tags, and current production commit ID.
-
In Forgejo, open Create… > New Migration.
-
Select GitHub or the generic Git service.
-
Enter the GitHub repository URL and authentication if the repository is private.
-
Do not select This repository will be a mirror. The goal is a one-time import followed by a normal Forgejo repository.
-
Complete the migration.
-
Compare the Forgejo and GitHub production branch commit IDs.
Verify the baseline
Section titled “Verify the baseline”The production branch must resolve to the same commit on both forges before the push mirror is enabled.
git ls-remote https://forgejo.example.com/OWNER/REPO.git refs/heads/maingit ls-remote https://github.com/OWNER/REPO.git refs/heads/mainStep 2: create a repository-scoped GitHub token
Section titled “Step 2: create a repository-scoped GitHub token”GitHub recommends fine-grained personal access tokens when they support the required operation. A fine-grained token can be restricted to one resource owner, selected repositories, and specific permissions.
-
In GitHub, open your profile menu and select Settings.
-
Select Developer settings.
-
Under Personal access tokens, select Fine-grained tokens.
-
Select Generate new token.
-
Give the token a descriptive name, such as
forgejo-push-example-site. -
Set an expiration date that matches your rotation process.
-
Select the GitHub user or organization under Resource owner.
-
Under Repository access, choose Only select repositories.
-
Select only the destination repository.
-
Under Repository permissions, set Contents to Read and write.
-
Generate the token and store it immediately in your password manager or secret store.
If the repository contains GitHub Actions workflow files and Forgejo must update .github/workflows/*, GitHub may require Workflows: Read and write. Add that permission only when the repository actually needs it. Cloudflare Pages’ Git integration does not require a GitHub Actions workflow.
For a small number of repositories, one token per repository keeps failures and compromise scope separate. GitHub recommends considering a GitHub App for long-lived integrations or larger-scale automation. A GitHub App is useful when token count or lifecycle management becomes a real problem, not merely because it exists.
Step 3: add the Forgejo push mirror
Section titled “Step 3: add the Forgejo push mirror”In the Forgejo source repository:
-
Open Settings > Repository.
-
Find Mirror Settings.
-
Enter the GitHub repository URL:
https://github.com/GITHUB_OWNER/GITHUB_REPOSITORY.git -
Set Branch Filter to
mainfor a production-only publishing path. -
Expand Authorization.
-
Enter your GitHub username as Username.
-
Enter the fine-grained token as Password.
-
Enable Sync when new commits are pushed.
-
Select Add Push Mirror.
Choose the branch filter carefully
Section titled “Choose the branch filter carefully”Forgejo’s documentation states that an empty branch filter uses git push --mirror. That can force-update remote refs and overwrite changes made directly in GitHub.
Use the narrowest filter that supports your deployment model:
| Branch filter | Result |
|---|---|
main |
Pushes only the production branch |
main, feature/* |
Pushes production plus matching preview branches |
| Empty | Mirrors all refs with force-mirror behavior |
For a simple Cloudflare Pages production workflow, main is the safer default. Add preview branches only if you use Cloudflare preview deployments and have a naming convention worth preserving.
Step 4: connect GitHub to Cloudflare Pages
Section titled “Step 4: connect GitHub to Cloudflare Pages”If the GitHub repository already deploys through Cloudflare Pages, verify the existing settings and move to the next step.
For a new Pages project:
-
In the Cloudflare dashboard, open Workers & Pages.
-
Create a Pages project and choose the Git integration option.
-
Authorize the Cloudflare Workers & Pages GitHub App.
-
Limit the GitHub App to Only select repositories and select the destination repository.
-
Choose the repository in Cloudflare Pages.
-
Set the production branch to
main. -
Enter the website’s build command and output directory.
-
Save the project and run the first deployment.
Cloudflare Pages can create preview deployments for non-production branches. Configure preview branch rules under the Pages project’s build settings if you mirror preview branches from Forgejo.
The build command and output directory depend on the website. For example, many Astro sites use a build command such as pnpm build and publish the generated dist directory. Keep those details in the website repository so the deployment remains reproducible.
Step 5: validate the complete path
Section titled “Step 5: validate the complete path”-
Confirm the baseline.
Verify that Forgejo and GitHub still have the same
maincommit before creating the test commit. -
Create a harmless Forgejo-originated commit.
Use a documentation-only change or an empty commit:
Terminal window git clone https://forgejo.example.com/OWNER/REPO.gitcd REPOgit commit --allow-empty -m "chore: verify Forgejo publishing path"git push origin mainIf your repository requires pull requests, create and merge the validation change through Forgejo instead of pushing directly to
main. -
Verify the Forgejo mirror.
Open Settings > Repository > Mirror Settings. Check the push mirror status. Use Synchronize Now if the push-on-commit event has not run yet.
Forgejo should report a successful synchronization without authentication or branch-rule errors.
-
Verify GitHub.
Confirm that GitHub
maincontains the exact Forgejo commit ID. The commit should arrive without a manual GitHub push. -
Verify Cloudflare Pages.
Open the Pages project’s deployment history and confirm that:
- a production build started for the mirrored commit,
- the deployment used the expected production branch,
- the build completed successfully,
- the deployed commit ID matches Forgejo and GitHub.
-
Verify the public site.
Load the production URL and perform one focused check that matters for the site: homepage response, expected title, redirect behavior, or another stable contract.
The verified chain should now be:
Forgejo commit ID = GitHub commit ID = Cloudflare Pages deployed commit IDOperating the workflow
Section titled “Operating the workflow”Make changes only in Forgejo
Section titled “Make changes only in Forgejo”Use Forgejo branches, pull requests, reviews, and merge controls. GitHub is the deployment repository, not a second collaboration surface.
Track token expiration
Section titled “Track token expiration”A mirror with an expired token stops publishing while Forgejo remains healthy. Record the token owner, repository, expiration date, and rotation procedure without recording the token value.
Rotate one repository at a time:
-
Create the replacement token.
-
Update the Forgejo push mirror credential.
-
Synchronize and verify it.
-
Revoke the old token after the new path is proven.
Monitor mirror failures
Section titled “Monitor mirror failures”At minimum, check Forgejo’s push mirror status after important merges. For unattended production use, add monitoring or a scheduled parity check that compares the Forgejo and GitHub production commit IDs.
Handle an emergency GitHub commit
Section titled “Handle an emergency GitHub commit”If somebody commits directly to GitHub after cutover:
-
Pause or disable the Forgejo push mirror.
-
Freeze writes on both sides.
-
Import the GitHub commit into Forgejo.
-
Resolve conflicts in Forgejo.
-
Verify the production branch matches.
-
Resume the mirror.
Troubleshooting
Section titled “Troubleshooting”| Symptom | Likely cause | Check |
|---|---|---|
Forgejo reports 401 or 403 |
Wrong token, expired token, wrong resource owner, or missing Contents write permission | Token repository selection, expiration, and permissions |
GitHub rejects the push to main |
Branch protection or repository rules block the token owner | GitHub rulesets and required pull-request policy; do not disable controls blindly |
| Workflow-file updates fail | Token cannot modify .github/workflows/* |
Add Workflows write permission only if needed |
| Forgejo sync succeeds but Cloudflare does not build | Wrong Pages repository, production branch, branch controls, or build watch paths | Cloudflare Git integration and build settings |
| Cloudflare builds the wrong branch | Production branch is not main |
Settings > Builds > Branch control |
| GitHub refs disappear or are overwritten | Mirror ran without a branch filter and used force-mirror behavior | Restore refs from Forgejo or backup, then add an intentional branch filter |
| SSH mirror misses LFS objects | Forgejo does not implement LFS mirroring over SSH | Use an HTTPS mirror or handle LFS separately |
Rollback
Section titled “Rollback”If the mirror causes unexpected behavior:
-
Disable or remove the affected Forgejo push mirror.
-
Stop writes while comparing Forgejo and GitHub refs.
-
Preserve the last known-good branch tips on both forges.
-
Keep the GitHub repository connected to Cloudflare Pages unless the deployment integration itself is the problem.
-
Do not revoke or delete credentials until you understand the failure and have a recovery path.
Disabling the mirror stops new publication. It does not remove previous GitHub commits or Cloudflare deployments.
Security checklist
Section titled “Security checklist”- Forgejo is the documented canonical source.
- GitHub is treated as deployment-only after cutover.
- Existing repositories were aligned before enabling the mirror.
- The GitHub token can access only one repository.
- The token has Contents read/write and no unrelated permissions.
- The token has a documented expiration and owner.
- The token value is stored outside Git and documentation.
- The Forgejo branch filter is intentional.
- Branch protections were not disabled to make mirroring easier.
- A Forgejo-originated commit was verified in GitHub and Cloudflare Pages.
- Rollback and emergency GitHub-import procedures are documented.
Companion guide
Section titled “Companion guide”The website half of this workflow is covered in Build a Static Astro Website for Cloudflare Pages. It explains creating and structuring the Astro project, typed Markdown content, local validation, and the dist/ build consumed by Cloudflare Pages.