Files
2025-11-30 08:24:36 +08:00

857 lines
14 KiB
Markdown

# Deployment Guide
**Last Updated**: 2025-10-20
Complete guide to deploying Cloudflare Workers with Wrangler, including CI/CD patterns and production best practices.
---
## Table of Contents
1. [Prerequisites](#prerequisites)
2. [Wrangler Commands](#wrangler-commands)
3. [Environment Configuration](#environment-configuration)
4. [CI/CD Pipelines](#cicd-pipelines)
5. [Production Best Practices](#production-best-practices)
6. [Monitoring and Logs](#monitoring-and-logs)
---
## Prerequisites
### 1. Cloudflare Account
Sign up at https://dash.cloudflare.com/sign-up
### 2. Get Account ID
```bash
# Option 1: From dashboard
# Go to: Workers & Pages → Overview → Account ID (right sidebar)
# Option 2: Via Wrangler
wrangler whoami
```
Add to `wrangler.jsonc`:
```jsonc
{
"account_id": "YOUR_ACCOUNT_ID_HERE"
}
```
### 3. Authenticate Wrangler
```bash
# Login via browser
wrangler login
# Or use API token (for CI/CD)
export CLOUDFLARE_API_TOKEN="your-token"
```
**Create API token**:
1. Go to: https://dash.cloudflare.com/profile/api-tokens
2. Click "Create Token"
3. Use template: "Edit Cloudflare Workers"
4. Copy token (only shown once!)
---
## Wrangler Commands
### Development
```bash
# Start local dev server (http://localhost:8787)
wrangler dev
# Local mode (no network requests to Cloudflare)
wrangler dev --local
# Custom port
wrangler dev --port 3000
# Specific environment
wrangler dev --env staging
```
### Deployment
```bash
# Deploy to production
wrangler deploy
# Deploy to specific environment
wrangler deploy --env staging
wrangler deploy --env production
# Dry run (validate without deploying)
wrangler deploy --dry-run
# Deploy with compatibility date
wrangler deploy --compatibility-date 2025-10-11
```
### Type Generation
```bash
# Generate TypeScript types for bindings
wrangler types
# Output to custom file
wrangler types --output-file=types/worker.d.ts
```
### Logs
```bash
# Tail live logs
wrangler tail
# Filter by status code
wrangler tail --status error
# Filter by HTTP method
wrangler tail --method POST
# Filter by IP
wrangler tail --ip-address 1.2.3.4
# Format as JSON
wrangler tail --format json
```
### Deployments
```bash
# List recent deployments
wrangler deployments list
# View specific deployment
wrangler deployments view DEPLOYMENT_ID
# Rollback to previous deployment
wrangler rollback --deployment-id DEPLOYMENT_ID
```
### Secrets
```bash
# Set secret (interactive)
wrangler secret put MY_SECRET
# Set secret from file
echo "secret-value" | wrangler secret put MY_SECRET
# List secrets
wrangler secret list
# Delete secret
wrangler secret delete MY_SECRET
```
### KV Operations
```bash
# Create KV namespace
wrangler kv namespace create MY_KV
# List namespaces
wrangler kv namespace list
# Put key-value
wrangler kv key put --namespace-id=YOUR_ID "key" "value"
# Get value
wrangler kv key get --namespace-id=YOUR_ID "key"
# List keys
wrangler kv key list --namespace-id=YOUR_ID
```
### D1 Operations
```bash
# Create D1 database
wrangler d1 create my-database
# Execute SQL
wrangler d1 execute my-database --command "SELECT * FROM users"
# Run SQL file
wrangler d1 execute my-database --file schema.sql
# List databases
wrangler d1 list
```
### R2 Operations
```bash
# Create R2 bucket
wrangler r2 bucket create my-bucket
# List buckets
wrangler r2 bucket list
# Upload file
wrangler r2 object put my-bucket/file.txt --file local-file.txt
# Download file
wrangler r2 object get my-bucket/file.txt --file local-file.txt
```
---
## Environment Configuration
### Single Environment (Default)
```jsonc
{
"name": "my-worker",
"account_id": "YOUR_ACCOUNT_ID",
"main": "src/index.ts",
"compatibility_date": "2025-10-11",
"vars": {
"ENV": "production"
},
"kv_namespaces": [
{ "binding": "MY_KV", "id": "production-kv-id" }
]
}
```
### Multiple Environments
```jsonc
{
"name": "my-worker",
"account_id": "YOUR_ACCOUNT_ID",
"main": "src/index.ts",
"compatibility_date": "2025-10-11",
// Shared configuration
"observability": {
"enabled": true
},
// Environment-specific configuration
"env": {
"staging": {
"name": "my-worker-staging",
"vars": {
"ENV": "staging",
"API_URL": "https://api-staging.example.com"
},
"kv_namespaces": [
{ "binding": "MY_KV", "id": "staging-kv-id" }
],
"d1_databases": [
{ "binding": "DB", "database_name": "my-db-staging", "database_id": "staging-db-id" }
]
},
"production": {
"name": "my-worker-production",
"vars": {
"ENV": "production",
"API_URL": "https://api.example.com"
},
"kv_namespaces": [
{ "binding": "MY_KV", "id": "production-kv-id" }
],
"d1_databases": [
{ "binding": "DB", "database_name": "my-db", "database_id": "production-db-id" }
],
"routes": [
{ "pattern": "example.com/*", "zone_name": "example.com" }
]
}
}
}
```
Deploy:
```bash
wrangler deploy --env staging
wrangler deploy --env production
```
### Environment Detection in Code
```typescript
app.get('/api/info', (c) => {
const env = c.env.ENV || 'development'
const apiUrl = c.env.API_URL || 'http://localhost:3000'
return c.json({ env, apiUrl })
})
```
---
## CI/CD Pipelines
### GitHub Actions
Create `.github/workflows/deploy.yml`:
```yaml
name: Deploy to Cloudflare Workers
on:
push:
branches:
- main
jobs:
deploy:
runs-on: ubuntu-latest
name: Deploy
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run tests
run: npm test
- name: Deploy to Cloudflare Workers
uses: cloudflare/wrangler-action@v3
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
```
**With environment-specific deployment**:
```yaml
name: Deploy
on:
push:
branches:
- main
- staging
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Install dependencies
run: npm ci
- name: Deploy to staging
if: github.ref == 'refs/heads/staging'
uses: cloudflare/wrangler-action@v3
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
command: deploy --env staging
- name: Deploy to production
if: github.ref == 'refs/heads/main'
uses: cloudflare/wrangler-action@v3
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
command: deploy --env production
```
**Add secrets to GitHub**:
1. Go to: Repository → Settings → Secrets → Actions
2. Add `CLOUDFLARE_API_TOKEN`
3. Add `CLOUDFLARE_ACCOUNT_ID`
### GitLab CI/CD
Create `.gitlab-ci.yml`:
```yaml
image: node:20
stages:
- test
- deploy
test:
stage: test
script:
- npm ci
- npm test
deploy_staging:
stage: deploy
script:
- npm ci
- npx wrangler deploy --env staging
only:
- staging
environment:
name: staging
deploy_production:
stage: deploy
script:
- npm ci
- npx wrangler deploy --env production
only:
- main
environment:
name: production
```
**Add variables to GitLab**:
1. Go to: Settings → CI/CD → Variables
2. Add `CLOUDFLARE_API_TOKEN` (masked)
3. Add `CLOUDFLARE_ACCOUNT_ID`
### Manual Deployment Script
Create `scripts/deploy.sh`:
```bash
#!/bin/bash
set -e
ENV=${1:-production}
echo "🚀 Deploying to $ENV..."
# Run tests
echo "Running tests..."
npm test
# Type check
echo "Type checking..."
npm run type-check
# Build
echo "Building..."
npm run build
# Deploy
echo "Deploying to Cloudflare..."
if [ "$ENV" = "production" ]; then
wrangler deploy --env production
else
wrangler deploy --env staging
fi
echo "✅ Deployment complete!"
echo "🔗 Check logs: wrangler tail --env $ENV"
```
Usage:
```bash
chmod +x scripts/deploy.sh
./scripts/deploy.sh staging
./scripts/deploy.sh production
```
---
## Production Best Practices
### 1. Use Compatibility Dates
Always set a recent `compatibility_date`:
```jsonc
{
"compatibility_date": "2025-10-11"
}
```
**Why**: Ensures consistent behavior and access to new features.
**Update regularly**: Check https://developers.cloudflare.com/workers/configuration/compatibility-dates/
### 2. Enable Observability
```jsonc
{
"observability": {
"enabled": true
}
}
```
**Provides**:
- Real-time metrics
- Error tracking
- Performance monitoring
### 3. Set Resource Limits
```jsonc
{
"limits": {
"cpu_ms": 50 // Maximum CPU time per request (paid plan)
}
}
```
### 4. Configure Custom Domains
```jsonc
{
"routes": [
{
"pattern": "api.example.com/*",
"zone_name": "example.com"
}
]
}
```
**Or via dashboard**:
1. Workers & Pages → Your Worker → Triggers
2. Add Custom Domain
### 5. Use Secrets for Sensitive Data
```bash
# Never commit secrets to git
wrangler secret put API_KEY
wrangler secret put DATABASE_URL
```
```typescript
// Access in code
const apiKey = c.env.API_KEY
```
### 6. Implement Rate Limiting
```typescript
import { Hono } from 'hono'
const app = new Hono()
app.use('/api/*', async (c, next) => {
const ip = c.req.header('cf-connecting-ip')
const key = `rate-limit:${ip}`
const count = await c.env.MY_KV.get(key)
if (count && parseInt(count) > 100) {
return c.json({ error: 'Rate limit exceeded' }, 429)
}
await c.env.MY_KV.put(key, (parseInt(count || '0') + 1).toString(), {
expirationTtl: 60 // 1 minute
})
await next()
})
```
### 7. Add Health Check Endpoint
```typescript
app.get('/health', (c) => {
return c.json({
status: 'ok',
version: '1.0.0',
timestamp: new Date().toISOString()
})
})
```
### 8. Implement Error Tracking
```typescript
app.onError((err, c) => {
console.error('Error:', err)
// Send to error tracking service
// await sendToSentry(err)
return c.json({
error: 'Internal Server Error',
requestId: c.req.header('cf-ray')
}, 500)
})
```
### 9. Use Structured Logging
```typescript
import { logger } from 'hono/logger'
app.use('*', logger())
app.get('/api/users', (c) => {
console.log(JSON.stringify({
level: 'info',
message: 'Fetching users',
userId: c.req.header('x-user-id'),
timestamp: new Date().toISOString()
}))
return c.json({ users: [] })
})
```
### 10. Test Before Deploying
```bash
# Run tests
npm test
# Type check
npm run type-check
# Lint
npm run lint
# Test locally
wrangler dev --local
# Test remotely (without deploying)
wrangler dev
```
---
## Monitoring and Logs
### Real-Time Logs
```bash
# Tail all requests
wrangler tail
# Filter by status
wrangler tail --status error
wrangler tail --status ok
# Filter by method
wrangler tail --method POST
# Filter by search term
wrangler tail --search "error"
# Output as JSON
wrangler tail --format json
```
### Analytics Dashboard
View in Cloudflare Dashboard:
1. Workers & Pages → Your Worker → Metrics
2. See:
- Requests per second
- Errors
- CPU time
- Response time
### Custom Metrics
```typescript
app.use('*', async (c, next) => {
const start = Date.now()
await next()
const duration = Date.now() - start
console.log(JSON.stringify({
type: 'metric',
name: 'request_duration',
value: duration,
path: c.req.path,
method: c.req.method,
status: c.res.status
}))
})
```
### External Monitoring
**Use Workers Analytics Engine**:
```typescript
app.use('*', async (c, next) => {
await next()
// Write to Analytics Engine
c.env.ANALYTICS.writeDataPoint({
indexes: [c.req.path],
blobs: [c.req.method, c.req.header('user-agent')],
doubles: [Date.now(), c.res.status]
})
})
```
**Or send to external services**:
```typescript
// Send to Datadog, New Relic, etc.
await fetch('https://api.datadoghq.com/api/v1/logs', {
method: 'POST',
headers: {
'DD-API-KEY': c.env.DATADOG_API_KEY,
'Content-Type': 'application/json'
},
body: JSON.stringify({
message: 'Request processed',
status: c.res.status,
path: c.req.path
})
})
```
---
## Rollback Strategy
### Immediate Rollback
```bash
# List recent deployments
wrangler deployments list
# Rollback to specific deployment
wrangler rollback --deployment-id DEPLOYMENT_ID
```
### Gradual Rollout
```jsonc
{
"name": "my-worker-canary",
"routes": [
{
"pattern": "example.com/*",
"zone_name": "example.com",
"script": "my-worker"
}
]
}
```
1. Deploy new version to `-canary` worker
2. Route 10% of traffic to canary
3. Monitor metrics
4. Gradually increase to 100%
5. Promote canary to main
---
## Performance Optimization
### 1. Minimize Bundle Size
```bash
# Check bundle size
wrangler deploy --dry-run --outdir=dist
# Analyze
ls -lh dist/
```
**Tips**:
- Avoid large dependencies
- Use dynamic imports for heavy modules
- Tree-shake unused code
### 2. Use Edge Caching
```typescript
app.get('/api/data', async (c) => {
const cache = caches.default
const cacheKey = new Request(c.req.url, c.req.raw)
let response = await cache.match(cacheKey)
if (!response) {
// Fetch data
const data = await fetchData()
response = c.json(data)
// Cache for 5 minutes
response.headers.set('Cache-Control', 'max-age=300')
c.executionCtx.waitUntil(cache.put(cacheKey, response.clone()))
}
return response
})
```
### 3. Optimize Database Queries
```typescript
// ❌ Bad: N+1 queries
for (const user of users) {
const posts = await c.env.DB.prepare('SELECT * FROM posts WHERE user_id = ?').bind(user.id).all()
}
// ✅ Good: Single query
const posts = await c.env.DB.prepare('SELECT * FROM posts WHERE user_id IN (?)').bind(userIds).all()
```
---
## Troubleshooting Deployments
### Deployment Fails
```bash
# Check configuration
wrangler deploy --dry-run
# Verbose output
WRANGLER_LOG=debug wrangler deploy
# Check account access
wrangler whoami
```
### Build Errors
```bash
# Clear cache
rm -rf node_modules/.wrangler
rm -rf .wrangler
# Reinstall dependencies
npm ci
# Try again
npm run deploy
```
### Routes Not Working
```bash
# List routes
wrangler routes list
# Check zone assignment
# Dashboard → Workers & Pages → Your Worker → Triggers
```
---
**Production-tested deployment patterns**
**CI/CD examples validated**
**Monitoring strategies proven**