Skip to content
Back to skills

272 Deployment D9b4f83e

ASecurity

**Last Updated**: 2025-10-20 Complete guide to deploying Cloudflare Workers with Wrangler, including CI/CD patterns and production best practices. ---

  • 9 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added October 11, 2026
devopstypescriptgobashsqlnodegitapidatabaseci/cdperformance

Works with

  • cli
  • api

Security analysis

A93/100
  • highPerforms destructive filesystem operations

Pro shows the line behind each finding and how to fix it

Scanned October 11, 2026

npx -y skills add tools-only/X-Skills --skill 272-deployment_d9b4f83e --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of 272 Deployment D9b4f83e?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for 272 Deployment D9b4f83e
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/tools-only-272-deployment-d9b4f83e/badge)](https://www.skillsdirectory.com/skills/tools-only-272-deployment-d9b4f83e)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

SKILL.md
# 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** ✅

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.