/deploy,A skill for Claude Code that deploys static sites to Cloudflare Pages with custom domains. Git repo, DNS, SSL — all automated.
Download these files and place them in ~/.claude/skills/website-deploy/
---
name: website-deploy
description: Deploy a static website to Cloudflare Pages with GitHub repo creation, subdomain configuration, and production deployment
disable-model-invocation: false
allowed-tools: Bash, Read, Write, Edit, Task, AskUserQuestion
---
# Website Deployment Skill
Deploy a static website to Cloudflare Pages with a custom subdomain.
## Configuration
All sensitive values are read from environment variables or a local config file.
### Required Environment Variables
| Variable | Description |
|---|---|
| `GITHUB_TOKEN` | GitHub personal access token (or `gh auth` session) |
| `CLOUDFLARE_API_TOKEN` | Cloudflare API token with Zone:DNS:Edit, Zone:Zone:Read, Account:Pages:Edit |
| `CLOUDFLARE_ACCOUNT_ID` | Cloudflare account ID |
### Required Config: `~/.claude/skills/website-deploy/config.env`
```bash
DEPLOY_GITHUB_ORG=your-github-org
DEPLOY_DOMAIN=your-domain.com
DEPLOY_CF_ZONE_ID=your-cloudflare-zone-id
```
Load config at the start of execution:
```bash
source ~/.claude/skills/website-deploy/config.env
```
## Pre-flight Checks
```bash
gh auth status
source ~/.zshrc && printenv CLOUDFLARE_API_TOKEN | wc -c # should be 41
source ~/.claude/skills/website-deploy/config.env && echo "Org: $DEPLOY_GITHUB_ORG | Domain: $DEPLOY_DOMAIN"
```
All must succeed before proceeding.
## Execution Steps
### Step 1: Confirm Deployment Details
Use `AskUserQuestion` to gather:
1. **Project name**: Used for GitHub repo name, Cloudflare Pages project name, and subdomain
2. **Cloudflare account**: Only ask if `wrangler whoami` shows multiple accounts
Derive from project name:
- `$REPO_NAME` = project name
- `$PAGES_PROJECT_NAME` = project name (no dots allowed, use hyphens)
- `$SUBDOMAIN` = `$PROJECT_NAME.$DEPLOY_DOMAIN`
### Step 2: Initialize Git + Create GitHub Repo
```bash
cd $PROJECT_DIRECTORY
git init
gh repo create $DEPLOY_GITHUB_ORG/$REPO_NAME --private \
--description "$DESCRIPTION" \
--source=. --remote=origin
```
### Step 3: Verify Static Site Structure
Ensure these files exist in the project:
- `index.html` - Main entry point
- `styles.css` or `style.css` - Stylesheet (optional)
- `robots.txt` - SEO configuration (optional)
- `_headers` (optional) - Cloudflare Pages security headers
- `_redirects` (optional) - Cloudflare Pages URL redirects
### Step 4: Initial Commit and Push
```bash
git add .
git commit -m "feat: initial website deployment"
git push -u origin main
```
### Step 5: Deploy to Cloudflare Pages
```bash
npx wrangler pages project create $PAGES_PROJECT_NAME --production-branch=main
npx wrangler pages deploy . --project-name=$PAGES_PROJECT_NAME --branch=main
```
### Step 6: Configure Subdomain
Add a CNAME record via Cloudflare API:
```bash
python3 -c "
import urllib.request, json, os
token = os.environ['CLOUDFLARE_API_TOKEN']
zone_id = os.environ.get('DEPLOY_CF_ZONE_ID', '')
data = json.dumps({
'type': 'CNAME',
'name': '$PROJECT_NAME',
'content': '$PAGES_PROJECT_NAME.pages.dev',
'proxied': True, 'ttl': 1
}).encode()
req = urllib.request.Request(
f'https://api.cloudflare.com/client/v4/zones/{zone_id}/dns_records',
data=data, method='POST')
req.add_header('Authorization', f'Bearer {token}')
req.add_header('Content-Type', 'application/json')
resp = urllib.request.urlopen(req)
print(json.dumps(json.loads(resp.read()), indent=2))
"
```
Bind custom domain via Pages Domains API:
```bash
python3 -c "
import urllib.request, json, os
token = os.environ['CLOUDFLARE_API_TOKEN']
account_id = os.environ['CLOUDFLARE_ACCOUNT_ID']
data = json.dumps({'name': '$PROJECT_NAME.$DEPLOY_DOMAIN'}).encode()
req = urllib.request.Request(
f'https://api.cloudflare.com/client/v4/accounts/{account_id}/pages/projects/$PAGES_PROJECT_NAME/domains',
data=data, method='POST')
req.add_header('Authorization', f'Bearer {token}')
req.add_header('Content-Type', 'application/json')
try:
resp = urllib.request.urlopen(req)
print(json.dumps(json.loads(resp.read()), indent=2))
except urllib.error.HTTPError as e:
print(f'Error {e.code}: {e.read().decode()}')
"
```
SSL is provisioned automatically by Cloudflare (Let's Encrypt).
### Step 7: Verification
```bash
dig $PROJECT_NAME.$DEPLOY_DOMAIN
curl -I https://$PAGES_PROJECT_NAME.pages.dev
curl -I https://$PROJECT_NAME.$DEPLOY_DOMAIN
```
### Step 8: Document Deployment
Create README.md with deployment info and update commands.DEPLOY_GITHUB_ORG=your-github-org DEPLOY_DOMAIN=your-domain.com DEPLOY_CF_ZONE_ID=your-cloudflare-zone-id
Add to ~/.zshrc or ~/.bashrc:
# GitHub (or just run: gh auth login)
export GITHUB_TOKEN="ghp_your_token"
# Cloudflare
export CLOUDFLARE_API_TOKEN="your_cf_api_token"
export CLOUDFLARE_ACCOUNT_ID="your_cf_account_id"
Cloudflare token permissions: Zone:DNS:Edit / Zone:Zone:Read / Account:Pages:Edit
Place the files you downloaded above into:
~/.claude/skills/website-deploy/
├── SKILL.md # Skill logic (download above)
└── config.env # Fill in your values (download above)
Fill in your actual values:
DEPLOY_GITHUB_ORG=my-org # your GitHub org or username
DEPLOY_DOMAIN=example.com # your domain (managed by Cloudflare)
DEPLOY_CF_ZONE_ID=abc123... # Cloudflare zone ID for the domain
Find your Zone ID in Cloudflare Dashboard → your domain → Overview → right sidebar.
# In Claude Code, type:
/deploy
Claude will ask for the project name, then handle everything: GitHub repo, Cloudflare Pages deploy, DNS, SSL.
Creates a private repo, pushes all files.
Deploys to global CDN via Wrangler.
CNAME DNS + auto SSL via Let's Encrypt.
Checks DNS, HTTPS, writes README.
gh)
Authenticated via gh auth login