Deploy to Cloudflare (Workers, R2, D1), Docker, GCP (Cloud Run, GKE), Kubernetes (kubectl, Helm). Use for serverless, containers, CI/CD, GitOps, security audit.
Deploy and manage cloud infrastructure across Cloudflare, Docker, Google Cloud, and Kubernetes.
| Need | Choose |
|---|---|
| Sub-50ms latency globally | Cloudflare Workers |
| Large file storage (zero egress) | Cloudflare R2 |
| SQL database (global reads) | Cloudflare D1 |
| Containerized workloads | Docker + Cloud Run/GKE |
| Enterprise Kubernetes | GKE |
| Managed relational DB | Cloud SQL |
| Static site + API | Cloudflare Pages |
| Container orchestration | Kubernetes |
| Package management for K8s | Helm |
# Cloudflare Worker
wrangler init my-worker && cd my-worker && wrangler deploy
# Docker
docker build -t myapp . && docker run -p 3000:3000 myapp
# GCP Cloud Run
gcloud run deploy my-service --image gcr.io/project/image --region us-central1
# Kubernetes
kubectl apply -f manifests/ && kubectl get pods
cloudflare-platform.md - Edge computing overviewcloudflare-workers-basics.md - Handler types, patternscloudflare-workers-advanced.md - Performance, optimizationcloudflare-workers-apis.md - Runtime APIs, bindingscloudflare-r2-storage.md - Object storage, S3 compatibilitycloudflare-d1-kv.md - D1 SQLite, KV storebrowser-rendering.md - Puppeteer automationdocker-basics.md - Dockerfile, images, containersdocker-compose.md - Multi-container appsgcloud-platform.md - gcloud CLI, authenticationgcloud-services.md - Compute Engine, GKE, Cloud Runkubernetes-basics.md - Core concepts, architecture, workloadskubernetes-kubectl.md - Essential commands, debugging workflowkubernetes-helm.md / kubernetes-helm-advanced.md - Helm charts, templateskubernetes-security.md / kubernetes-security-advanced.md - RBAC, secretskubernetes-workflows.md / kubernetes-workflows-advanced.md - GitOps, CI/CDkubernetes-troubleshooting.md / kubernetes-troubleshooting-advanced.md - Debugscripts/cloudflare-deploy.py - Automate Worker deploymentsscripts/docker-optimize.py - Analyze DockerfilesSecurity: Non-root containers, RBAC, secrets in env vars, image scanning Performance: Multi-stage builds, edge caching, resource limits Cost: R2 for large egress, caching, right-size resources Development: Docker Compose local dev, wrangler dev, version control IaC
The following sections are inlined from the skill's reference files for Cursor compatibility.
Headless browser automation with Puppeteer/Playwright on Cloudflare Workers.
wrangler.toml:
name = "browser-worker"
main = "src/index.ts"
compatibility_date = "2024-01-01"
browser = { binding = "MYBROWSER" }
import puppeteer from '@cloudflare/puppeteer';
export default {
async fetch(request: Request, env: Env): Promise<Response> {
const browser = await puppeteer.launch(env.MYBROWSER);
const page = await browser.newPage();
await page.goto('https://example.com', { waitUntil: 'networkidle2' });
const screenshot = await page.screenshot({ type: 'png' });
await browser.close();
return new Response(screenshot, {
headers: { 'Content-Type': 'image/png' }
});
}
};
// Disconnect instead of close
await browser.disconnect();
// Retrieve and reconnect
const sessions = await puppeteer.sessions(env.MYBROWSER);
const freeSession = sessions.find(s => !s.connectionId);
if (freeSession) {
const browser = await puppeteer.connect(env.MYBROWSER, freeSession.sessionId);
}
const browser = await puppeteer.launch(env.MYBROWSER);
const page = await browser.newPage();
await page.setContent(`
<!DOCTYPE html>
<html>
<head>
<style>
body { font-family: Arial; padding: 50px; }
h1 { color: #2c3e50; }
</style>
</head>
<body>
<h1>Certificate</h1>
<p>Awarded to: <strong>John Doe</strong></p>
</body>
</html>
`);
const pdf = await page.pdf({
format: 'A4',
printBackground: true,
margin: { top: '1cm', right: '1cm', bottom: '1cm', left: '1cm' }
});
await browser.close();
return new Response(pdf, {
headers: { 'Content-Type': 'application/pdf' }
});
export class Browser {
state: DurableObjectState;
browser: any;
lastUsed: number;
constructor(state: DurableObjectState, env: Env) {
this.state = state;
this.lastUsed = Date.now();
}
async fetch(request: Request, env: Env) {
if (!this.browser) {
this.browser = await puppeteer.launch(env.MYBROWSER);
}
this.lastUsed = Date.now();
await this.state.storage.setAlarm(Date.now() + 10000);
const page = await this.browser.newPage();
const url = new URL(request.url).searchParams.get('url');
await page.goto(url);
const screenshot = await page.screenshot();
await page.close();
return new Response(screenshot, {
headers: { 'Content-Type': 'image/png' }
});
}
async alarm() {
if (Date.now() - this.lastUsed > 60000) {
await this.browser?.close();
this.browser = null;
} else {
await this.state.storage.setAlarm(Date.now() + 10000);
}
}
}
import { Ai } from '@cloudflare/ai';
export default {
async fetch(request: Request, env: Env): Promise<Response> {
const browser = await puppeteer.launch(env.MYBROWSER);
const page = await browser.newPage();
await page.goto('https://news.ycombinator.com');
const content = await page.content();
await browser.close();
const ai = new Ai(env.AI);
const response = await ai.run('@cf/meta/llama-3-8b-instruct', {
messages: [
{
role: 'system',
content: 'Extract top 5 article titles and URLs as JSON'
},
{ role: 'user', content: content }
]
});
return Response.json(response);
}
};
export default {
async queue(batch: MessageBatch<any>, env: Env): Promise<void> {
const browser = await puppeteer.launch(env.MYBROWSER);
for (const message of batch.messages) {
const page = await browser.newPage();
await page.goto(message.body.url);
const links = await page.evaluate(() => {
return Array.from(document.querySelectorAll('a')).map(a => a.href);
});
for (const link of links) {
await env.QUEUE.send({ url: link });
}
await page.close();
message.ack();
}
await browser.close();
}
};
await page.goto(url, {
timeout: 60000, // 60 seconds max
waitUntil: 'networkidle2'
});
await page.waitForSelector('.content', { timeout: 45000 });
await page.setViewport({ width: 1920, height: 1080 });
const screenshot = await page.screenshot({
type: 'png', // 'png' | 'jpeg' | 'webp'
quality: 90, // JPEG/WebP only
fullPage: true, // Full scrollable page
clip: { // Crop
x: 0, y: 0,
width: 800,
height: 600
}
});
disconnect() instead of close()disconnect() for reusewaitUntil strategyTimeout Errors:
await page.goto(url, {
timeout: 60000,
waitUntil: 'domcontentloaded' // Faster than networkidle2
});
Memory Issues:
await page.close(); // Close pages
await browser.disconnect(); // Reuse session
Font Rendering: Use supported fonts (Noto Sans, Roboto, etc.) or inject custom:
<link href="https://fonts.googleapis.com/css2?family=Poppins" rel="stylesheet">
puppeteer.launch(binding) - Start browserpuppeteer.connect(binding, sessionId) - Reconnectpuppeteer.sessions(binding) - List sessionsbrowser.newPage() - Create pagebrowser.disconnect() - Disconnect (keep alive)browser.close() - Close (terminate)page.goto(url, options) - Navigatepage.screenshot(options) - Capturepage.pdf(options) - Generate PDFpage.content() - Get HTMLpage.evaluate(fn) - Execute JS# Create database
wrangler d1 create my-database
# Add to wrangler.toml
[[d1_databases]]
binding = "DB"
database_name = "my-database"
database_id = "YOUR_DATABASE_ID"
# Apply schema
wrangler d1 execute my-database --file=./schema.sql
// Query
const result = await env.DB.prepare(
"SELECT * FROM users WHERE id = ?"
).bind(userId).first();
// Insert
await env.DB.prepare(
"INSERT INTO users (name, email) VALUES (?, ?)"
).bind("Alice", "[email protected]").run();
// Batch (atomic)
await env.DB.batch([
env.DB.prepare("UPDATE accounts SET balance = balance - 100 WHERE id = ?").bind(user1),
env.DB.prepare("UPDATE accounts SET balance = balance + 100 WHERE id = ?").bind(user2)
]);
// All results
const { results } = await env.DB.prepare("SELECT * FROM users").all();
# Create namespace
wrangler kv:namespace create MY_KV
# Add to wrangler.toml
[[kv_namespaces]]
binding = "KV"
id = "YOUR_NAMESPACE_ID"
// Put with TTL
await env.KV.put("session:token", JSON.stringify(data), {
expirationTtl: 3600,
metadata: { userId: "123" }
});
// Get
const value = await env.KV.get("session:token");
const json = await env.KV.get("session:token", "json");
const buffer = await env.KV.get("session:token", "arrayBuffer");
const stream = await env.KV.get("session:token", "stream");
// Get with metadata
const { value, metadata } = await env.KV.getWithMetadata("session:token");
// Delete
await env.KV.delete("session:token");
// List
const list = await env.KV.list({ prefix: "user:" });
| Need | Choose |
|---|---|
| SQL queries | D1 |
| Sub-millisecond reads | KV |
| ACID transactions | D1 |
| Large values (>25MB) | R2 |
| Strong consistency | D1 (writes), Durable Objects |
| Automatic expiration | KV |
Cloudflare Developer Platform: comprehensive edge computing ecosystem for full-stack applications on global network across 300+ cities.
Global Network:
V8 Isolates:
Workers - Serverless functions on edge
D1 - SQLite database with global read replication
KV - Distributed key-value store
R2 - Object storage (S3-compatible)
Durable Objects - Stateful compute with WebSockets
Queues - Message queue system
Pages - Static site hosting + serverless functions
Workers AI - Run AI models on edge
Browser Rendering - Headless browser automation
┌─────────────────────────────────────────┐
│ Cloudflare Pages (Frontend) │
│ Next.js / Remix / Astro │
└──────────────────┬──────────────────────┘
│
┌──────────────────▼──────────────────────┐
│ Workers (API Layer) │
│ - Routing │
│ - Authentication │
│ - Business logic │
└─┬──────┬──────┬──────┬──────┬───────────┘
│ │ │ │ │
▼ ▼ ▼ ▼ ▼
┌────┐ ┌────┐ ┌────┐ ┌────┐ ┌────────────┐
│ D1 │ │ KV │ │ R2 │ │ DO │ │ Workers AI │
└────┘ └────┘ └────┘ └────┘ └────────────┘
export default {
async fetch(request: Request, env: Env) {
// KV: Fast cache
const cached = await env.KV.get(key);
if (cached) return new Response(cached);
// D1: Structured data
const user = await env.DB.prepare(
"SELECT * FROM users WHERE id = ?"
).bind(userId).first();
// R2: Media files
const avatar = await env.R2_BUCKET.get(`avatars/${user.id}.jpg`);
// Durable Objects: Real-time
const chat = env.CHAT_ROOM.get(env.CHAT_ROOM.idFromName(roomId));
// Queue: Async processing
await env.EMAIL_QUEUE.send({ to: user.email, template: 'welcome' });
return new Response(JSON.stringify({ user }));
}
};
npm install -g wrangler
wrangler login
wrangler init my-worker
# Development
wrangler dev # Local dev server
wrangler dev --remote # Dev on real edge
# Deployment
wrangler deploy # Deploy to production
wrangler deploy --dry-run # Preview changes
# Logs
wrangler tail # Real-time logs
wrangler tail --format pretty # Formatted logs
# Versions
wrangler deployments list # List deployments
wrangler rollback [version] # Rollback
# Secrets
wrangler secret put SECRET_NAME
wrangler secret list
# D1
wrangler d1 create my-db
wrangler d1 execute my-db --file=schema.sql
# KV
wrangler kv:namespace create MY_KV
wrangler kv:key put --binding=MY_KV "key" "value"
# R2
wrangler r2 bucket create my-bucket
wrangler r2 object put my-bucket/file.txt --file=./file.txt
name = "my-worker"
main = "src/index.ts"
compatibility_date = "2024-01-01"
# Environment variables
[vars]
ENVIRONMENT = "production"
# D1 Database
[[d1_databases]]
binding = "DB"
database_name = "my-database"
database_id = "YOUR_DATABASE_ID"
# KV Namespace
[[kv_namespaces]]
binding = "KV"
id = "YOUR_NAMESPACE_ID"
# R2 Bucket
[[r2_buckets]]
binding = "R2_BUCKET"
bucket_name = "my-bucket"
# Durable Objects
[[durable_objects.bindings]]
name = "COUNTER"
class_name = "Counter"
script_name = "my-worker"
# Queues
[[queues.producers]]
binding = "MY_QUEUE"
queue = "my-queue"
# Workers AI
[ai]
binding = "AI"
# Cron triggers
[triggers]
crons = ["0 0 * * *"]
wrangler secret for API keys| Need | Choose |
|---|---|
| Sub-millisecond reads | KV |
| SQL queries | D1 |
| Large files (>25MB) | R2 |
| Real-time WebSockets | Durable Objects |
| Async background jobs | Queues |
| ACID transactions | D1 |
| Strong consistency | Durable Objects |
| Zero egress costs | R2 |
| AI inference | Workers AI |
| Static site hosting | Pages |
S3-compatible object storage with zero egress fees.
wrangler r2 bucket create my-bucket
wrangler r2 bucket create my-bucket --location=wnam
Locations: wnam, enam, weur, eeur, apac
wrangler r2 object put my-bucket/file.txt --file=./local-file.txt
wrangler.toml:
[[r2_buckets]]
binding = "MY_BUCKET"
bucket_name = "my-bucket"
Worker:
// Put
await env.MY_BUCKET.put('user-uploads/photo.jpg', imageData, {
httpMetadata: {
contentType: 'image/jpeg',
cacheControl: 'public, max-age=31536000'
},
customMetadata: {
uploadedBy: userId,
uploadDate: new Date().toISOString()
}
});
// Get
const object = await env.MY_BUCKET.get('large-file.mp4');
if (!object) {
return new Response('Not found', { status: 404 });
}
return new Response(object.body, {
headers: {
'Content-Type': object.httpMetadata.contentType,
'ETag': object.etag
}
});
// List
const listed = await env.MY_BUCKET.list({
prefix: 'user-uploads/',
limit: 100
});
// Delete
await env.MY_BUCKET.delete('old-file.txt');
// Head (check existence)
const object = await env.MY_BUCKET.head('file.txt');
if (object) {
console.log('Size:', object.size);
}
# Configure
aws configure
# Access Key ID: <your-key-id>
# Secret Access Key: <your-secret>
# Region: auto
# Operations
aws s3api list-buckets --endpoint-url https://<accountid>.r2.cloudflarestorage.com
aws s3 cp file.txt s3://my-bucket/ --endpoint-url https://<accountid>.r2.cloudflarestorage.com
# Presigned URL
aws s3 presign s3://my-bucket/file.txt --endpoint-url https://<accountid>.r2.cloudflarestorage.com --expires-in 3600
import { S3Client, PutObjectCommand } from "@aws-sdk/client-s3";
const s3 = new S3Client({
region: "auto",
endpoint: `https://${accountId}.r2.cloudflarestorage.com`,
credentials: {
accessKeyId: process.env.R2_ACCESS_KEY_ID,
secretAccessKey: process.env.R2_SECRET_ACCESS_KEY
}
});
await s3.send(new PutObjectCommand({
Bucket: "my-bucket",
Key: "file.txt",
Body: fileContents
}));
import boto3
s3 = boto3.client(
service_name='s3',
endpoint_url=f'https://{account_id}.r2.cloudflarestorage.com',
aws_access_key_id=access_key_id,
aws_secret_access_key=secret_access_key,
region_name='auto'
)
s3.upload_fileobj(file_obj, 'my-bucket', 'file.txt')
s3.download_file('my-bucket', 'file.txt', './local-file.txt')
For files >100MB:
const multipart = await env.MY_BUCKET.createMultipartUpload('large-file.mp4');
// Upload parts (5MiB - 5GiB each, max 10,000 parts)
const part1 = await multipart.uploadPart(1, chunk1);
const part2 = await multipart.uploadPart(2, chunk2);
// Complete
const object = await multipart.complete([part1, part2]);
rclone config # Configure Cloudflare R2
# Upload with optimization
rclone copy large-video.mp4 r2:my-bucket/ \
--s3-upload-cutoff=100M \
--s3-chunk-size=100M
r2.dev (rate-limited):