CI/CD setup
This guide walks through setting up the GitHub Actions CI/CD pipeline for automated Cloudflare Workers deployment.
Prerequisites
Section titled “Prerequisites”- Cloudflare account with Workers enabled
- GitHub repository with Actions enabled
- SOPS installed locally (
nix profile install nixpkgs#sops) - Age key pair for encryption
Step 1: Create and Encrypt Secrets
Section titled “Step 1: Create and Encrypt Secrets”1.1 Create Cloudflare API Token
Section titled “1.1 Create Cloudflare API Token”- Visit https://dash.cloudflare.com/profile/api-tokens
- Click “Create Token”
- Use “Edit Cloudflare Workers” template or create custom token with:
- Account.Workers Scripts (Edit)
- Account.Workers Routes (Edit)
- Copy the generated token
1.2 Get Cloudflare Account ID
Section titled “1.2 Get Cloudflare Account ID”- Visit https://dash.cloudflare.com/
- Select your account
- Go to Workers & Pages
- Find Account ID in the right sidebar
1.3 Get Other Service Tokens
Section titled “1.3 Get Other Service Tokens”Optional but recommended for full CI functionality:
- CACHIX_AUTH_TOKEN: Get from https://app.cachix.org/cache/YOUR_CACHE/settings
- CACHIX_CACHE_NAME: Your Cachix cache name
1.4 Create Unencrypted Secrets File
Section titled “1.4 Create Unencrypted Secrets File”Create vars/shared.yaml with your secrets:
CLOUDFLARE_ACCOUNT_ID: your-actual-account-idCLOUDFLARE_API_TOKEN: your-actual-api-tokenCACHIX_AUTH_TOKEN: your-actual-cachix-tokenCACHIX_CACHE_NAME: your-cache-nameCI_AGE_KEY: age-secret-key-1... # CI age private key from .sops.yamlThe CI_AGE_KEY should be the private key corresponding to the public key:
age1m9m8h5vqr7dqlmvnzcwshmm4uk8umcllazum6eaulkdp3qc88ugs22j3p8
1.5 Encrypt the Secrets File
Section titled “1.5 Encrypt the Secrets File”# Verify you have the correct age keys configuredcat .sops.yaml
# Encrypt the file in placesops --encrypt --in-place vars/shared.yaml
# Verify encryption succeededhead vars/shared.yaml# Should show encrypted content starting with ENC[...]1.6 Commit Encrypted Secrets
Section titled “1.6 Commit Encrypted Secrets”git add vars/shared.yamlgit commit -m "build: add encrypted secrets for CI/CD"git pushStep 2: Configure GitHub Repository
Section titled “Step 2: Configure GitHub Repository”2.1 Upload SOPS Age Key to GitHub Secrets
Section titled “2.1 Upload SOPS Age Key to GitHub Secrets”The CI needs the private age key to decrypt vars/shared.yaml:
# Extract the CI_AGE_KEY from the encrypted filesops --decrypt --extract '["CI_AGE_KEY"]' vars/shared.yaml | gh secret set SOPS_AGE_KEYOr manually:
- Decrypt the file:
sops vars/shared.yaml - Copy the
CI_AGE_KEYvalue - Go to https://github.com/YOUR_USERNAME/YOUR_REPO/settings/secrets/actions
- Click “New repository secret”
- Name:
SOPS_AGE_KEY - Value: Paste the age private key
- Click “Add secret”
2.2 Set GitHub Variables (Optional)
Section titled “2.2 Set GitHub Variables (Optional)”If using Cachix, set these as repository variables (not secrets):
- Go to https://github.com/YOUR_USERNAME/YOUR_REPO/settings/variables/actions
- Add variable
CACHIX_CACHE_NAMEwith your cache name
Alternatively, the workflow will read from the encrypted vars/shared.yaml.
2.3 Configure Production Environment
Section titled “2.3 Configure Production Environment”- Go to https://github.com/YOUR_USERNAME/YOUR_REPO/settings/environments
- Click “New environment”
- Name:
production - Add protection rules as desired (e.g., required reviewers)
- Save
Step 3: Test the Workflow
Section titled “Step 3: Test the Workflow”3.1 Manual Test with workflow_dispatch
Section titled “3.1 Manual Test with workflow_dispatch”Test the workflow manually before enabling automatic deployment:
# Trigger workflow with deployment disabled (safe test)gh workflow run ci.yaml
# Or with deployment enabledgh workflow run ci.yaml -f deploy_enabled=true3.2 Monitor Workflow Execution
Section titled “3.2 Monitor Workflow Execution”# Watch the workflow rungh run watch
# Or view in browsergh run view --web3.3 Verify Each Job
Section titled “3.3 Verify Each Job”The workflow should complete these jobs in order:
- ✅ secrets-scan: Gitleaks secret scanning
- ✅ set-variables: Configure workflow variables
- ✅ bootstrap-verification: Validate bootstrap workflow
- ✅ config-validation: Test config.nix user definitions
- ✅ autowiring-validation: Verify nixos-unified autowiring
- ✅ secrets-workflow: Test sops-nix mechanics
- ✅ justfile-activation: Validate justfile recipes
- ✅ cache-overlay-packages: Pre-cache overlay packages
- ✅ nix: Build all flake outputs
- ✅ docs-test: Test documentation site
- ✅ docs-deploy: Deploy to Cloudflare Workers (only if enabled)
3.4 Check Deployment
Section titled “3.4 Check Deployment”If deployment succeeded, verify at:
- Cloudflare Dashboard: https://dash.cloudflare.com/
- Your Workers URL (shown in workflow deployment step)
Step 4: Enable Automatic Deployment
Section titled “Step 4: Enable Automatic Deployment”Once manual testing succeeds, automatic deployment on push to main is already configured.
Push to main branch:
git checkout maingit pull# Make changes...git add .git commit -m "your changes"git pushThe workflow will automatically:
- Run all CI checks
- Build the site
- Deploy to Cloudflare Workers
Workflow Triggers
Section titled “Workflow Triggers”The CI/CD workflow runs on:
-
Manual dispatch (
workflow_dispatch)debug_enabled: Enable tmate debugging sessiondeploy_enabled: Force deployment even on non-main branch
-
Pull requests (
pull_request)- Runs CI checks only (no deployment)
- Skip with label:
skip-ci - Enable debug with label:
actions-debug
-
Push to main (
pushtomainbranch)- Runs full CI
- Automatically deploys to production
Troubleshooting
Section titled “Troubleshooting”Workflow fails at “Decrypt secrets”
Section titled “Workflow fails at “Decrypt secrets””Check:
SOPS_AGE_KEYis set correctly in GitHub secretsvars/shared.yamlexists and is encrypted- Age key has permissions to decrypt the file
# Test decryption locallyexport SOPS_AGE_KEY_FILE=/path/to/your/age/keysops --decrypt vars/shared.yamlDeployment fails with “Invalid API token”
Section titled “Deployment fails with “Invalid API token””Check:
- Token has correct permissions (Workers Scripts Edit, Workers Routes Edit)
- Token hasn’t expired
- Account ID matches your Cloudflare account
Build fails with “Module not found”
Section titled “Build fails with “Module not found””Check:
bun installsucceeded- All dependencies in
package.jsonare correct - Nix flake is up to date
Run locally:
nix developbun installbun run buildSOPS decryption shows wrong age key
Section titled “SOPS decryption shows wrong age key”Ensure the CI_AGE_KEY in vars/shared.yaml matches the public key in .sops.yaml:
keys: - &ci age1m9m8h5vqr7dqlmvnzcwshmm4uk8umcllazum6eaulkdp3qc88ugs22j3p8Generate the public key from private key:
echo "YOUR_PRIVATE_KEY" | age-keygen -ySecurity Notes
Section titled “Security Notes”- Never commit unencrypted secrets to the repository
- Rotate API tokens regularly
- Use minimal required permissions for tokens
- Enable branch protection on main branch
- Review workflow logs for exposed secrets
- Use environment protection rules for production
Next Steps
Section titled “Next Steps”After successful setup:
- Configure custom domain in Cloudflare
- Set up monitoring and alerts
- Add status badges to README
- Configure additional environments (staging, preview)
- Add deployment notifications (Slack, Discord, etc.)
Useful Commands
Section titled “Useful Commands”# List workflowsgh workflow list
# View workflow runsgh run list --workflow=ci.yaml
# Trigger manual deploymentgh workflow run ci.yaml -f deploy_enabled=true
# View latest rungh run view
# Download workflow artifactsgh run download
# Re-run failed workflowgh run rerun <run-id>References
Section titled “References”- Cloudflare Workers: https://developers.cloudflare.com/workers/
- Wrangler CLI: https://developers.cloudflare.com/workers/wrangler/
- SOPS: https://github.com/getsops/sops
- GitHub Actions: https://docs.github.com/actions