Skip to content

Cloudflare Pages + GitHub Setup Guide

Project: arlyn.io company website Date: March 2026


Overview

This document outlines the steps taken to connect a private GitHub repository (hosted under the ArlynLabs organization) to Cloudflare Pages, enabling automatic deployment of the static site on every push to main.


Prerequisites

  • A Cloudflare account with the target domain (arlyn.io) already registered
  • A GitHub personal account that owns the target GitHub organization
  • The GitHub organization created (ArlynLabs)
  • The target repository created (ArlynLabs/arlyn-website, private)

Step 1: Create the GitHub Repository

Create a private repository in the GitHub organization to serve as the source for the site.

  • Organization: ArlynLabs
  • Repository name: arlyn-website
  • Visibility: Private
  • Branch: main

Push an initial commit (e.g. a README.md) to initialize the main branch before connecting Cloudflare.


Step 2: Create a GitHub Personal Access Token (PAT)

A fine-grained PAT is used to allow programmatic access to the repository (for automated commits and pushes).

  1. Log into GitHub as your personal account (org owner)
  2. Go to Settings → Developer settings → Personal access tokens → Fine-grained tokens
  3. Click Generate new token with the following settings:
  4. Token name: e.g. Claude Cowork
  5. Resource owner: Leave as your personal account (d-arlynlabs) — since you own the org, your personal account already has full access to org repos, and no org approval step is required
  6. Repository access: Only select repositories → arlyn-website
  7. Permissions:
    • Contents: Read and write
    • Metadata: Read-only (auto-selected)
  8. Copy the token immediately (shown only once)
  9. Save the token to .secrets/github_pat in the project workspace

Note on resource owner and org approval: If you set the resource owner to the organization (ArlynLabs) instead of your personal account, GitHub will require an org admin to approve the token before it activates. The approval page is at ArlynLabs org settings → Personal access tokens → Pending requests. Setting the personal account as resource owner (as done here) avoids this extra step entirely, since the personal account inherits full access to the org's repos as its owner.


Step 3: Configure Git for Automated Commits

Configure git in the automation environment to use the PAT for authentication.

git config --global user.name "d-arlyn"
git config --global user.email "d@arlyn.dev"
git config --global credential.helper store
echo "https://x-token-auth:<PAT>@github.com" > ~/.git-credentials
chmod 600 ~/.git-credentials

A reusable setup script is stored at .claude/setup.sh in the workspace. Run this at the start of each session to restore git access, since the automation environment resets between sessions.


Step 4: Install the Cloudflare GitHub App on the Organization

How the GitHub–Cloudflare connection works

Cloudflare Pages integrates with GitHub through a GitHub App — a first-party integration that lives on the GitHub side and grants Cloudflare permission to read your repository and post deployment status back to pull requests. Think of it as GitHub saying "I trust Cloudflare to watch this repo."

Once installed, the flow works like this:

You push to GitHub → GitHub notifies Cloudflare (via the App) → Cloudflare pulls the code and deploys it

The GitHub App lives in your GitHub organization settings (under Installed GitHub Apps), not inside Cloudflare. Cloudflare just holds a reference to the installation so it knows which repo to watch.

Installation steps

The installation is initiated from the Cloudflare side, then completed on GitHub:

  1. In the Cloudflare dashboard, go to Workers & Pages → Create application → Pages → Get started → Import an existing Git repository
  2. Click Connect GitHub — Cloudflare redirects you to GitHub to install its app
  3. GitHub asks "Where do you want to install Cloudflare Workers and Pages?" — select ArlynLabs
  4. On the next GitHub screen, choose Only select repositories and pick arlyn-website (rather than granting access to all org repos)
  5. Confirm the permissions GitHub lists:
  6. Read access to metadata
  7. Read and write access to administration, checks, code, deployments, and pull requests
  8. Click Install & Authorize — GitHub installs the app on ArlynLabs and redirects back to Cloudflare

What ends up where

What Where it lives Purpose
Cloudflare GitHub App GitHub → ArlynLabs org → Settings → GitHub Apps Grants Cloudflare access to the repo
Cloudflare Pages project Cloudflare dashboard → Workers & Pages Defines build settings and holds deployment history
The link between them Configured when creating the Pages project (Step 6) Tells Cloudflare which repo and branch to watch

Known issue: browser UI loop

After the app is installed, returning to the Cloudflare "Connect GitHub" flow lands back on GitHub's app selection screen, which now only shows a Configure link for ArlynLabs (since the app is already installed) rather than completing the OAuth handshake back to Cloudflare. This prevents the browser UI from finishing the Pages project setup.

Solution: Create the Pages project directly via the Cloudflare API instead (see Step 6), which links to GitHub using the existing app installation without needing the browser OAuth flow to complete.


Step 5: Create a Cloudflare API Token

A Cloudflare API token is required to create and manage the Pages project programmatically.

  1. In the Cloudflare dashboard, go to My Profile → API Tokens → Create Token
  2. Choose Create Custom Token
  3. Configure with the following settings:
  4. Token name: Cloudflare Pages Deploy
  5. Permissions: Account → Cloudflare Pages → Edit
  6. Account Resources: All accounts
  7. Click Continue to summary, then Create Token
  8. Copy the token immediately (shown only once)
  9. Save the token to .secrets/cloudflare_api_token in the project workspace

Step 6: Create the Cloudflare Pages Project via API

The Pages project was created using the Cloudflare REST API, linking it directly to the GitHub repository.

curl -X POST "https://api.cloudflare.com/client/v4/accounts/<ACCOUNT_ID>/pages/projects" \
  -H "Authorization: Bearer <CF_API_TOKEN>" \
  -H "Content-Type: application/json" \
  --data '{
    "name": "arlyn-website",
    "production_branch": "main",
    "source": {
      "type": "github",
      "config": {
        "owner": "ArlynLabs",
        "repo_name": "arlyn-website",
        "production_branch": "main",
        "pr_comments_enabled": true,
        "deployments_enabled": true
      }
    },
    "build_config": {
      "build_command": "",
      "destination_dir": "/",
      "root_dir": ""
    }
  }'

Result: - Project name: arlyn-website - Pages URL: arlyn-website.pages.dev - Account ID: e3874665bc48a0819bc5be1dfbdf454d - No build command is needed — the site is plain static HTML/CSS/JS


Step 7: Trigger the Initial Deployment

After creating the project, trigger the first deployment manually via API:

curl -X POST "https://api.cloudflare.com/client/v4/accounts/<ACCOUNT_ID>/pages/projects/arlyn-website/deployments" \
  -H "Authorization: Bearer <CF_API_TOKEN>" \
  -H "Content-Type: application/json" \
  --data '{"branch": "main"}'

From this point forward, every push to main on ArlynLabs/arlyn-website will automatically trigger a new deployment.


Deployment Workflow (Ongoing)

cd /path/to/arlyn-website
# make changes to site files
git add .
git commit -m "describe changes"
git push
# Cloudflare Pages auto-deploys within ~1 minute

Preview deployments are also created automatically for non-main branches.


Step 8: Connect the Custom Domain (arlyn.io)

Add the custom domain via the Cloudflare API:

curl -X POST "https://api.cloudflare.com/client/v4/accounts/<ACCOUNT_ID>/pages/projects/arlyn-website/domains" \
  -H "Authorization: Bearer <CF_API_TOKEN>" \
  -H "Content-Type: application/json" \
  --data '{"name": "arlyn.io"}'

Then add the DNS record in the Cloudflare dashboard (DNS → Records for arlyn.io):

Type Name Target Proxy
CNAME @ (root) arlyn-website.pages.dev Proxied (orange cloud)

Note: Using @ as the CNAME name works because Cloudflare flattens CNAME records at the apex domain — a feature not available on standard DNS providers.

Once the record is saved, return to Pages → Custom Domains and click Check DNS records. The domain status should update to Active with SSL enabled within about 30 seconds.

Result: arlyn.io resolves to the live Cloudflare Pages deployment with SSL provided by Google CA.


Step 9: Redirect pages.dev to the Custom Domain

Cloudflare Pages permanently serves the site at both arlyn-website.pages.dev and arlyn.io. The .pages.dev URL is not a preview — it always reflects the live production deployment. Left unaddressed, search engines could index it as a duplicate of the canonical domain.

To prevent this, a _worker.js file is added to the repo root. Cloudflare Pages automatically picks this up and runs it as an edge worker on every request, allowing hostname-based logic before assets are served.

// _worker.js
export default {
  async fetch(request, env) {
    const url = new URL(request.url);

    // Redirect pages.dev URL to the canonical domain
    if (url.hostname.endsWith('.pages.dev')) {
      return Response.redirect(
        `https://arlyn.io${url.pathname}${url.search}`,
        301
      );
    }

    // Serve static assets normally for all other requests
    return env.ASSETS.fetch(request);
  }
};

Any request to arlyn-website.pages.dev receives a 301 redirect to the equivalent path on arlyn.io. Requests to arlyn.io are unaffected — env.ASSETS.fetch(request) serves the static files as normal.

Cost: This worker runs only on .pages.dev traffic, which is negligible in practice. Cloudflare's free tier includes 100,000 Worker requests per day — far more than needed here.


Key References

Item Value
GitHub org ArlynLabs
Repository ArlynLabs/arlyn-website
Cloudflare Pages URL arlyn-website.pages.dev
Cloudflare Account ID e3874665bc48a0819bc5be1dfbdf454d
PAT location .secrets/github_pat
CF API token location .secrets/cloudflare_api_token
Session setup script .claude/setup.sh