Cloudflare Pages
Deploy static sites (React, Vue, Astro, etc.) to Cloudflare Pages as production or preview deployments. Pilum installs dependencies, runs your build and uploads the output with wrangler pages deploy.
Prerequisites
- Node.js and npm (or pnpm) installed
- Wrangler, run through
npx(no global install needed) - A Cloudflare account with a Pages project already created
- A Cloudflare API token with Pages edit permission
Create the Pages Project
Create the Cloudflare Pages project before the first deploy. The project name must exactly match the name field in your pilum.yaml.
Option A: Cloudflare Dashboard
- Go to Workers & Pages in the sidebar
- Click Create → Pages
- Choose Direct Upload (not “Connect to Git”; Pilum handles the deploy)
- Name the project to match your
pilum.yamlnamefield - Click Create. You don’t need to upload anything.
Option B: Wrangler CLI
npx wrangler pages project create my-site --production-branch mainAPI Token
# Create an API token:# Cloudflare Dashboard → My Profile → API Tokens → Create Token# Use the "Edit Cloudflare Workers" template, or a custom token with# Account → Cloudflare Pages → Edit
export CLOUDFLARE_API_TOKEN="your-token-here"Service Configuration
name: my-sitetype: cloudflare-pages
cloudflare: account_id: "abc123def456" token_env: "CLOUDFLARE_API_TOKEN" production_branch: "main" package_manager: "npm"
build: cmd: "npm run build" output_dir: "dist"Use type: cloudflare-pages (or provider: cloudflare).
Required Fields
| Field | Description |
|---|---|
name | Project name; must match your Cloudflare Pages project name |
cloudflare.account_id | Cloudflare account ID |
Optional Fields
Cloudflare Configuration
| Field | Type | Default | Description |
|---|---|---|---|
cloudflare.token_env | string | CLOUDFLARE_API_TOKEN | Name of the environment variable holding the API token |
cloudflare.account_id_env | string | - | Name of an environment variable holding the account ID. When set, it is used instead of account_id. |
cloudflare.production_branch | string | main | Tag value that deploys to production |
cloudflare.package_manager | string | npm | npm or pnpm. Any other value uses npm. |
account_id_env keeps the account ID out of a public repository. It is not in the JSON schema yet, and pilum check still asks for account_id, so set account_id to a placeholder if you want check to pass.
Build Configuration
| Field | Type | Default | Description |
|---|---|---|---|
build.cmd | string | - | Build command. If empty, the build step is skipped. |
build.output_dir | string | dist | Directory to upload, relative to the service directory |
There is no default build command at deploy time. If you leave out build.cmd, nothing is built and wrangler uploads whatever is already in output_dir.
Where Each Step Runs
| Step | Runs in |
|---|---|
| install dependencies | The directory you run pilum in (normally the repository root) |
| build site | The directory you run pilum in |
| deploy to pages | The service directory (where pilum.yaml is) |
So npm ci/pnpm install and build.cmd run from the root, while output_dir is resolved from the service directory. In a single-site repo with pilum.yaml at the root these are the same place.
Deployment Steps
The Cloudflare Pages recipe runs:
- install dependencies [build] —
npm ci, orpnpm install --frozen-lockfile - build site [build] —
build.cmdthrough/bin/sh -c. Skipped whenbuild.cmdis empty. - deploy to pages [deploy] —
npx wrangler pages deploy <output_dir> --project-name=<name> --branch=<branch>
Production vs Preview Deploys
The --tag value picks the deployment type:
- Production: the tag equals
production_branch(defaultmain), or islatest. Pilum passes--branch=<production_branch>. - Preview: any other tag. Pilum passes
--branch=<tag>.
# Production deploypilum deploy my-site --tag=main
# Preview deploy (e.g., from a feature branch)pilum deploy my-site --tag=feature-loginPreview deployments get a URL like https://<branch>.my-site.pages.dev.
Package Manager
npm (default)
cloudflare: package_manager: "npm"Installs with npm ci.
pnpm
cloudflare: package_manager: "pnpm"Installs with pnpm install --frozen-lockfile.
Yarn and bun are not supported for Cloudflare Pages; the schema’s yarn option falls back to npm.
pnpm Monorepo
For a site that is one package in a pnpm workspace, put pilum.yaml in the site’s package directory, run Pilum from the workspace root, and filter the build:
name: my-sitetype: cloudflare-pages
cloudflare: account_id: "abc123def456" package_manager: "pnpm"
build: cmd: "pnpm --filter @scope/my-site... build" output_dir: "dist" # apps/my-site/dist, relative to this directoryThe ... suffix builds the package and all its workspace dependencies in order. Install and build run from the workspace root; the deploy runs in apps/my-site, so output_dir is the package’s own dist.
Build Step
build: cmd: "npm run build" output_dir: "dist"Common output directories by framework:
| Framework | Output Dir |
|---|---|
| Vite / React / Vue | dist |
| Next.js (static) | out |
| Astro | dist |
| Create React App | build |
Generated Deploy Script
The deploy step runs:
if [ -z "$CLOUDFLARE_API_TOKEN" ]; then echo "Error: CLOUDFLARE_API_TOKEN environment variable is not set" exit 1fiexport CLOUDFLARE_API_TOKEN="$CLOUDFLARE_API_TOKEN"export CLOUDFLARE_ACCOUNT_ID='abc123def456'npx wrangler pages deploy 'dist' --project-name='my-site' --branch='main'With account_id_env: CF_ACCOUNT_ID, the account line becomes a check that $CF_ACCOUNT_ID is set, followed by export CLOUDFLARE_ACCOUNT_ID="$CF_ACCOUNT_ID".
Example Commands
# Full pipeline (install, build, deploy) to productionpilum deploy my-site --tag=main
# Install and build onlypilum build my-site
# Preview commandspilum dry-run my-site --tag=main
# Deploy only (output_dir must already be built)pilum deploy my-site --only-tags=deploy --tag=main
# Preview deployment from a feature branchpilum deploy my-site --tag=feature-authStatus and Logs
pilum logs my-site (and pilum status my-site) run npx wrangler pages deployment list --project-name my-site. pilum logs prints that deployment list. pilum status currently shows the state as Unknown for Pages projects, or Error if wrangler fails, so use pilum logs or the dashboard to see deployments.
There is no pilum rollback for Cloudflare Pages; use the dashboard’s rollback.
CI/CD Integration
GitHub Actions (pilum-action)
name: Deployon: push: branches: [main]
jobs: deploy: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: '20'
- name: Deploy with Pilum uses: SID-Technologies/pilum-action@v1 with: command: deploy services: my-site tag: ${{ github.ref_name }} env: CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}GitHub Actions (manual install)
Release archives are named pilum_<version>_<os>_<arch>.tar.gz, so download by pattern:
name: Deployon: push: branches: [main]
jobs: deploy: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: '20'
- name: Install Pilum env: GH_TOKEN: ${{ github.token }} run: | gh release download --repo SID-Technologies/Pilum --pattern '*_linux_amd64.tar.gz' --output pilum.tar.gz tar xzf pilum.tar.gz pilum sudo mv pilum /usr/local/bin/
- name: Deploy env: CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} run: pilum deploy my-site --tag=${{ github.ref_name }}github.ref_name is the branch name, so pushes to main deploy to production and the same workflow on other branches creates preview deployments.
Troubleshooting
Authentication failed
Error: CLOUDFLARE_API_TOKEN environment variable is not setExport the variable named in cloudflare.token_env:
export CLOUDFLARE_API_TOKEN="your-token-here"Project not found
Error: A request to the Cloudflare API failed. Could not find projectThe name in your pilum.yaml must exactly match your Cloudflare Pages project name. Create the project first in the dashboard or with:
npx wrangler pages project create my-site --production-branch mainDeploy went to preview instead of production
The tag did not match production_branch. Check the --branch= value in pilum dry-run; an empty --branch='' means --tag was not passed.
Wrong output directory
No files found in dist/build.output_dir is relative to the service directory, not the directory you run Pilum from. Check where your framework writes its output:
build: cmd: "npm run build" output_dir: "build" # e.g., for Create React AppWrangler not found
npx: command not found: wranglerEnsure Node.js and npm are installed. Wrangler is run through npx, so it doesn’t need a global install; adding it as a dev dependency pins the version.
Next Steps
- Service Configuration — Full
pilum.yamlreference - Environments and CI — Running Pilum in CI
- CLI Commands — All commands and flags
- Troubleshooting — Common issues