
Deploying Applications
- 48 installs
- 426 repo stars
- Updated December 11, 2025
- ancoleman/ai-design-components
Deploying-applications is a Claude Code skill that provides deployment patterns from Kubernetes to serverless and edge functions with IaC and GitOps guidance.
About
Deploying-applications is a Claude Code skill providing deployment patterns from Kubernetes to serverless and edge functions. A developer uses it when deploying applications, setting up CI/CD, or managing infrastructure. It gives a strategy decision tree (Kubernetes, serverless, containers, edge), Infrastructure-as-Code guidance with Pulumi/OpenTofu/SST, GitOps patterns with ArgoCD or Flux, and serverless database and edge-function options.
- Deployment strategy decision tree: Kubernetes, serverless, containers, edge
- IaC with Pulumi, OpenTofu, and SST; GitOps with ArgoCD or Flux
- Serverless databases and edge functions (Cloudflare Workers, Deno)
Deploying Applications by the numbers
- 48 all-time installs (skills.sh)
- Ranked #743 of 1,435 DevOps & CI/CD skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
deploying-applications capabilities & compatibility
- Capabilities
- deploying applications · deploying on aws · deploying on azure · building ci pipelines
- Works with
- kubernetes · vercel · aws · cloudflare · terraform · docker
- Use cases
- devops · ci cd
What deploying-applications says it does
Deployment patterns from Kubernetes to serverless and edge functions.
Covers Kubernetes (Helm, ArgoCD), serverless (Vercel, Lambda), edge (Cloudflare Workers, Deno), IaC (Pulumi, OpenTofu, SST), and GitOps patterns.
npx skills add https://github.com/ancoleman/ai-design-components --skill deploying-applicationsAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 48 |
|---|---|
| repo stars | ★ 426 |
| Last updated | December 11, 2025 |
| Repository | ancoleman/ai-design-components ↗ |
What it does
Pick and implement a deployment strategy across Kubernetes, serverless, containers, or edge functions.
Who is it for?
Choosing and implementing a production deployment strategy with IaC and GitOps.
Skip if: Application feature code or single cloud-provider service selection depth.
When should I use this skill?
Deploying to production, setting up GitOps, or choosing between Kubernetes, serverless, and edge.
What you get
A deployment strategy matched to the workload with IaC and GitOps automation.
- Deployment strategy selection
- IaC scaffolding (Pulumi/OpenTofu/SST)
- GitOps pipeline (ArgoCD/Flux)
By the numbers
- Linkerd 5-10% overhead vs Istio 25-35% overhead
- Cloudflare Workers <5ms cold start
Files
Deploying Applications
Production deployment patterns from Kubernetes to serverless and edge functions. Bridges the gap from application assembly to production infrastructure.
Purpose
This skill provides clear guidance for:
- Selecting the right deployment strategy (Kubernetes, serverless, containers, edge)
- Implementing Infrastructure as Code with Pulumi or OpenTofu
- Setting up GitOps automation with ArgoCD or Flux
- Choosing serverless databases (Neon, Turso, PlanetScale)
- Deploying edge functions (Cloudflare Workers, Deno Deploy)
When to Use This Skill
Use this skill when:
- Deploying applications to production infrastructure
- Setting up CI/CD pipelines and GitOps workflows
- Choosing between Kubernetes, serverless, or edge deployment
- Implementing Infrastructure as Code (Pulumi, OpenTofu, SST)
- Migrating from manual deployment to automated infrastructure
- Integrating with
assembling-componentsfor complete deployment flow
Deployment Strategy Decision Tree
WORKLOAD TYPE?
├── COMPLEX MICROSERVICES (10+ services)
│ └─ Kubernetes + ArgoCD/Flux (GitOps)
│ ├─ Helm 4.0 for packaging
│ ├─ Service mesh: Linkerd (5-10% overhead) or Istio (25-35%)
│ └─ See references/kubernetes-patterns.md
├── VARIABLE TRAFFIC / COST-SENSITIVE
│ └─ Serverless
│ ├─ Database: Neon/Turso (scale-to-zero)
│ ├─ Compute: Vercel, AWS Lambda, Cloud Functions
│ ├─ Edge: Cloudflare Workers (<5ms cold start)
│ └─ See references/serverless-dbs.md and references/edge-functions.md
├── CONSISTENT LOAD / PREDICTABLE TRAFFIC
│ └─ Containers (ECS, Cloud Run, Fly.io)
│ ├─ ECS Fargate: AWS-native, serverless containers
│ ├─ Cloud Run: GCP, scale-to-zero containers
│ └─ Fly.io: Global edge, multi-region
├── GLOBAL LOW-LATENCY (<50ms)
│ └─ Edge Functions + Edge Database
│ ├─ Cloudflare Workers + D1 (SQLite)
│ ├─ Deno Deploy + Turso (libSQL)
│ └─ See references/edge-functions.md
└── RAPID PROTOTYPING / STARTUP MVP
└─ Managed Platform as a Service
├─ Vercel (Next.js, zero-config)
├─ Railway (any framework)
└─ Render (auto-deploy from Git)
IaC CHOICE?
├─ TypeScript-first → Pulumi (Apache 2.0, multi-cloud)
├─ HCL-based → OpenTofu (CNCF, Terraform-compatible)
└─ Serverless TypeScript → SST v3 (built on Pulumi)Core Concepts
Infrastructure as Code (IaC)
Define infrastructure using code instead of manual configuration.
Primary: Pulumi (TypeScript)
- Context7 ID:
/pulumi/docs(Trust: 94.6/100, 9,525 snippets) - TypeScript-first (same language as React/Next.js)
- Multi-cloud support (AWS, GCP, Azure, Cloudflare)
- See references/pulumi-guide.md for patterns and examples
Alternative: OpenTofu (HCL)
- CNCF project, Terraform-compatible
- MPL-2.0 license (open governance)
- Drop-in Terraform replacement
- See references/opentofu-guide.md for migration
Serverless: SST v3 (TypeScript)
- Built on Pulumi
- Optimized for AWS Lambda, API Gateway
- Live Lambda development
GitOps Deployment
Declarative infrastructure with Git as source of truth.
ArgoCD (Recommended for platform teams):
- Rich web UI
- Built-in RBAC and multi-tenancy
- Self-healing deployments
- See references/gitops-argocd.md
Flux (Recommended for DevOps automation):
- Kubernetes-native
- CLI-focused
- Simpler architecture
- See references/gitops-argocd.md
Service Mesh
Optional layer for microservices communication, security, and observability.
When to Use Service Mesh:
- Multi-team microservices (security boundaries)
- Zero-trust networking (mTLS required)
- Advanced traffic management (canary, blue-green)
When NOT to Use:
- Simple monolith or 2-3 services (overhead not justified)
- Serverless architectures (incompatible)
Linkerd (Performance-focused):
- 5-10% overhead
- Rust-based
- Simple, opinionated
Istio (Feature-rich):
- 25-35% overhead
- C++ (Envoy)
- Advanced routing, observability
See references/kubernetes-patterns.md for service mesh patterns.
Quick Start Workflows
Workflow 1: Deploy Next.js to Vercel (Zero-Config)
# Install Vercel CLI
npm i -g vercel
# Link project
vercel link
# Deploy to production
vercel --prodSee examples/nextjs-vercel/ for complete example.
Workflow 2: Deploy to Kubernetes with ArgoCD
1. Create Helm chart 2. Push chart to Git repository 3. Create ArgoCD Application 4. ArgoCD syncs automatically
See examples/k8s-argocd/ for complete GitOps setup.
Workflow 3: Deploy Serverless with Pulumi
import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";
// Create Lambda function
const lambda = new aws.lambda.Function("api", {
runtime: "nodejs20.x",
handler: "index.handler",
role: role.arn,
code: new pulumi.asset.FileArchive("./dist"),
});
export const apiUrl = lambda.invokeArn;See examples/pulumi-aws/ and references/pulumi-guide.md for patterns.
Workflow 4: Deploy Edge Function to Cloudflare Workers
import { Hono } from 'hono'
const app = new Hono()
app.get('/api/hello', (c) => {
return c.json({ message: 'Hello from edge!' })
})
export default appDeploy with Wrangler:
wrangler deploySee examples/cloudflare-workers-hono/ and references/edge-functions.md.
Integration with assembling-components
After building an application with assembling-components, this skill provides deployment patterns:
Frontend (Next.js/Vite) → Deployment: 1. Review deployment decision tree 2. Choose platform: Vercel (Next.js), Cloudflare Pages (static), or custom (Pulumi) 3. Set up environment variables 4. Deploy using chosen method
Backend (FastAPI/Axum) → Deployment: 1. Containerize application (Dockerfile) 2. Choose platform: ECS Fargate, Cloud Run, or Kubernetes 3. Set up IaC (Pulumi or OpenTofu) 4. Deploy with GitOps (ArgoCD/Flux) or CI/CD
See references/pulumi-guide.md for integration examples.
Reference Files
Kubernetes Deployment
- references/kubernetes-patterns.md - Helm 4.0, service mesh, autoscaling
- references/gitops-argocd.md - ArgoCD/Flux GitOps workflows
Serverless & Edge
- references/serverless-dbs.md - Neon, Turso, PlanetScale (scale-to-zero)
- references/edge-functions.md - Cloudflare Workers, Deno Deploy (<5ms cold starts)
Infrastructure as Code
- references/pulumi-guide.md - Pulumi TypeScript patterns, component model
- references/opentofu-guide.md - OpenTofu/Terraform migration
Utility Scripts
Scripts in scripts/ are executed without loading into context (token-free).
Generate Kubernetes Manifests:
python scripts/generate_k8s_manifests.py --app-name my-app --replicas 3Validate Deployment Configuration:
python scripts/validate_deployment.py --config deployment.yamlSee script files for full usage documentation.
Examples
Complete, runnable examples in examples/:
- pulumi-aws/ - ECS Fargate deployment with Pulumi
- k8s-argocd/ - Kubernetes + ArgoCD GitOps
- sst-serverless/ - SST v3 serverless TypeScript
Each example includes:
- README.md with setup instructions
- Complete source code
- Environment variable configuration
- Deployment commands
Library Recommendations
Infrastructure as Code (2025)
Primary: Pulumi
- Context7:
/pulumi/docs(Trust: 94.6, 9,525 snippets) - TypeScript-first, multi-cloud
- Apache 2.0 license
Alternative: OpenTofu
- CNCF project, MPL-2.0
- Terraform-compatible
- HCL syntax
Serverless: SST v3
- Built on Pulumi
- AWS Lambda optimized
- TypeScript-native
Serverless Databases
Neon PostgreSQL:
- Database branching (like Git)
- Scale-to-zero compute
- Full PostgreSQL compatibility
Turso SQLite:
- Edge deployment (200+ locations)
- Sub-millisecond reads
- libSQL (SQLite fork)
PlanetScale MySQL:
- Non-blocking schema changes
- Vitess-powered
- Per-row pricing
See references/serverless-dbs.md for comparison and integration.
Edge Functions
Cloudflare Workers:
- <5ms cold starts (V8 isolates)
- 200+ edge locations
- 128MB memory per request
Deno Deploy:
- TypeScript-native
- Web Standard APIs
- Global edge (<50ms)
Hono Framework:
- Runs on all edge runtimes
- 14KB bundle size
- TypeScript-first
See references/edge-functions.md for patterns.
Best Practices
Security
- Use secrets management (AWS Secrets Manager, Vault)
- Enable mTLS for service-to-service communication
- Implement least-privilege IAM roles
- Scan container images for vulnerabilities
Cost Optimization
- Use serverless databases for variable traffic (scale-to-zero)
- Enable horizontal pod autoscaling (HPA) in Kubernetes
- Right-size compute resources (CPU/memory)
- Use spot instances for non-critical workloads
Performance
- Deploy close to users (edge functions for global apps)
- Use CDN for static assets (CloudFront, Cloudflare)
- Implement caching strategies (Redis, CloudFront)
- Monitor cold start times for serverless
Reliability
- Implement health checks (Kubernetes liveness/readiness probes)
- Set up auto-scaling (HPA, Lambda concurrency)
- Use multi-region deployments for critical services
- Implement circuit breakers and retries
Troubleshooting
Deployment Failures
Kubernetes pod fails to start: 1. Check pod logs: kubectl logs <pod-name> 2. Describe pod: kubectl describe pod <pod-name> 3. Verify resource limits and requests 4. Check image pull errors (imagePullSecrets)
Serverless cold starts too slow: 1. Reduce bundle size (tree-shaking, code splitting) 2. Use provisioned concurrency (AWS Lambda) 3. Consider edge functions (Cloudflare Workers) 4. Optimize initialization code
GitOps sync errors (ArgoCD/Flux): 1. Verify Git repository access 2. Check manifest validity (kubectl apply --dry-run) 3. Review sync policies (prune, selfHeal) 4. Check ArgoCD/Flux logs
Performance Issues
High service mesh overhead: 1. Consider switching to Linkerd (5-10% vs Istio 25-35%) 2. Disable unnecessary features 3. Evaluate if service mesh is needed
Database connection pool exhaustion: 1. Increase connection pool size 2. Use serverless databases (Neon scale-to-zero) 3. Implement connection pooling (PgBouncer)
See references/ files for detailed troubleshooting guides.
Migration Patterns
From Manual to IaC
1. Inventory existing infrastructure 2. Start with non-critical environments (dev, staging) 3. Use Pulumi/OpenTofu to codify infrastructure 4. Test in staging before production 5. Gradual migration (one service at a time)
From Terraform to OpenTofu
# Install OpenTofu
brew install opentofu
# Migrate state
terraform state pull > terraform.tfstate.backup
tofu init -migrate-state
tofu plan
tofu applySee references/opentofu-guide.md for complete migration.
From EC2 to Containers
1. Containerize application (create Dockerfile) 2. Test locally (Docker Compose) 3. Deploy to staging (ECS/Cloud Run/Kubernetes) 4. Monitor performance and costs 5. Cutover production traffic (blue-green deployment)
From Containers to Serverless
1. Identify stateless services 2. Refactor to serverless-friendly patterns 3. Use serverless databases (Neon/Turso) 4. Deploy to Lambda/Cloud Functions 5. Monitor cold starts and costs
Next Steps
After deploying applications:
- Set up observability (metrics, logs, traces)
- Implement CI/CD pipelines (GitHub Actions, GitLab CI)
- Configure auto-scaling and resource limits
- Set up disaster recovery and backups
- Document runbooks for incident response
Additional Resources
- Pulumi documentation: https://www.pulumi.com/docs/
- OpenTofu documentation: https://opentofu.org/docs/
- ArgoCD documentation: https://argo-cd.readthedocs.io/
- Cloudflare Workers docs: https://developers.cloudflare.com/workers/
- Neon documentation: https://neon.tech/docs/
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: my-app
namespace: argocd
spec:
project: default
source:
repoURL: https://github.com/your-org/your-repo # UPDATE THIS
targetRevision: main
path: k8s-argocd/overlays/production
destination:
server: https://kubernetes.default.svc
namespace: production
syncPolicy:
automated:
prune: true # Delete resources not in Git
selfHeal: true # Force sync if cluster state differs
allowEmpty: false
syncOptions:
- CreateNamespace=true
retry:
limit: 5
backoff:
duration: 5s
factor: 2
maxDuration: 3m
# Ignore differences in replicas (managed by HPA)
ignoreDifferences:
- group: apps
kind: Deployment
jsonPointers:
- /spec/replicas
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app
labels:
app: my-app
spec:
replicas: 2
selector:
matchLabels:
app: my-app
template:
metadata:
labels:
app: my-app
spec:
containers:
- name: app
image: my-app:latest
ports:
- containerPort: 3000
name: http
env:
- name: PORT
value: "3000"
- name: NODE_ENV
valueFrom:
configMapKeyRef:
name: app-config
key: environment
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 500m
memory: 512Mi
livenessProbe:
httpGet:
path: /health
port: http
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
httpGet:
path: /ready
port: http
initialDelaySeconds: 5
periodSeconds: 5
Kubernetes + ArgoCD GitOps Example
Complete GitOps deployment with Kubernetes and ArgoCD.
Prerequisites
- Kubernetes cluster (minikube, kind, or cloud provider)
- kubectl configured
- ArgoCD installed on cluster
Project Structure
k8s-argocd/
├── base/
│ ├── deployment.yaml
│ ├── service.yaml
│ ├── configmap.yaml
│ └── kustomization.yaml
├── overlays/
│ ├── development/
│ │ └── kustomization.yaml
│ ├── staging/
│ │ └── kustomization.yaml
│ └── production/
│ └── kustomization.yaml
├── argocd/
│ └── application.yaml
└── README.mdSetup ArgoCD
# Install ArgoCD
kubectl create namespace argocd
kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml
# Wait for ArgoCD to be ready
kubectl wait --for=condition=available --timeout=600s deployment/argocd-server -n argocd
# Access ArgoCD UI
kubectl port-forward svc/argocd-server -n argocd 8080:443
# Get initial admin password
kubectl -n argocd get secret argocd-initial-admin-secret -o jsonpath="{.data.password}" | base64 -dDeploy Application with ArgoCD
1. Update Git repository URL in argocd/application.yaml
2. Apply ArgoCD application:
kubectl apply -f argocd/application.yaml3. Sync application:
# Via CLI
argocd app sync my-app
# Or via UI
# Navigate to http://localhost:8080
# Click on application → SYNCManual Deployment (Without ArgoCD)
# Development
kubectl apply -k overlays/development
# Staging
kubectl apply -k overlays/staging
# Production
kubectl apply -k overlays/productionVerify Deployment
# Check pods
kubectl get pods -n production
# Check service
kubectl get svc -n production
# View logs
kubectl logs -l app=my-app -n production --tail=100 -f
# Port forward to access locally
kubectl port-forward svc/my-app 3000:80 -n productionGitOps Workflow
1. Make changes to Kubernetes manifests in Git 2. Commit and push to Git repository 3. ArgoCD detects changes automatically (every 3 minutes) 4. ArgoCD syncs cluster state to match Git 5. Self-healing: If manual changes made to cluster, ArgoCD reverts them
Rollback
# Via ArgoCD UI
# Click application → HISTORY → Select previous version → ROLLBACK
# Via CLI
argocd app rollback my-app 1Troubleshooting
Application out of sync:
argocd app get my-app
argocd app diff my-appSync errors:
argocd app sync my-app --dry-runPod fails to start:
kubectl describe pod <pod-name> -n production
kubectl logs <pod-name> -n productionNext Steps
- Add Helm charts for complex applications
- Configure health checks
- Set up notifications (Slack, email)
- Implement progressive delivery (Argo Rollouts)
- Add secrets management (Sealed Secrets)
import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";
import * as awsx from "@pulumi/awsx";
const config = new pulumi.Config();
const imageTag = config.require("imageTag");
const dbPassword = config.requireSecret("dbPassword");
// VPC with public/private subnets across 2 AZs
const vpc = new awsx.ec2.Vpc("app-vpc", {
numberOfAvailabilityZones: 2,
natGateways: { strategy: "Single" }, // Cost optimization: 1 NAT gateway
tags: {
Name: "app-vpc",
Environment: pulumi.getStack(),
ManagedBy: "Pulumi",
},
});
// ECS Cluster
const cluster = new aws.ecs.Cluster("app-cluster", {
tags: {
Environment: pulumi.getStack(),
ManagedBy: "Pulumi",
},
});
// CloudWatch Log Group
const logGroup = new aws.cloudwatch.LogGroup("app-logs", {
retentionInDays: 7,
});
// Application Load Balancer
const alb = new awsx.lb.ApplicationLoadBalancer("app-alb", {
subnetIds: vpc.publicSubnetIds,
defaultTargetGroup: {
port: 3000,
protocol: "HTTP",
healthCheck: {
path: "/health",
interval: 30,
timeout: 5,
healthyThreshold: 2,
unhealthyThreshold: 3,
},
},
});
// Fargate Task Execution Role
const executionRole = new aws.iam.Role("task-execution-role", {
assumeRolePolicy: JSON.stringify({
Version: "2012-10-17",
Statement: [{
Action: "sts:AssumeRole",
Effect: "Allow",
Principal: {
Service: "ecs-tasks.amazonaws.com",
},
}],
}),
});
new aws.iam.RolePolicyAttachment("task-execution-policy", {
role: executionRole.name,
policyArn: "arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy",
});
// Fargate Task Role (for application permissions)
const taskRole = new aws.iam.Role("task-role", {
assumeRolePolicy: JSON.stringify({
Version: "2012-10-17",
Statement: [{
Action: "sts:AssumeRole",
Effect: "Allow",
Principal: {
Service: "ecs-tasks.amazonaws.com",
},
}],
}),
});
// Fargate Service
const service = new awsx.ecs.FargateService("app-service", {
cluster: cluster.arn,
assignPublicIp: false, // Use private subnets
desiredCount: 2,
taskDefinitionArgs: {
executionRole: {
roleArn: executionRole.arn,
},
taskRole: {
roleArn: taskRole.arn,
},
container: {
name: "app",
image: `my-repo:${imageTag}`, // Replace with your ECR repository
cpu: 512,
memory: 1024,
essential: true,
portMappings: [{
containerPort: 3000,
targetGroup: alb.defaultTargetGroup,
}],
environment: [
{
name: "NODE_ENV",
value: pulumi.getStack() === "production" ? "production" : "development",
},
{
name: "PORT",
value: "3000",
},
],
secrets: [
{
name: "DATABASE_PASSWORD",
valueFrom: dbPassword,
},
],
logConfiguration: {
logDriver: "awslogs",
options: {
"awslogs-group": logGroup.name,
"awslogs-region": aws.config.region!,
"awslogs-stream-prefix": "app",
},
},
healthCheck: {
command: ["CMD-SHELL", "curl -f http://localhost:3000/health || exit 1"],
interval: 30,
timeout: 5,
retries: 3,
startPeriod: 60,
},
},
},
});
// Auto Scaling
const scalingTarget = new aws.appautoscaling.Target("app-scaling-target", {
maxCapacity: 10,
minCapacity: 2,
resourceId: pulumi.interpolate`service/${cluster.name}/${service.service.name}`,
scalableDimension: "ecs:service:DesiredCount",
serviceNamespace: "ecs",
});
const cpuScalingPolicy = new aws.appautoscaling.Policy("cpu-scaling-policy", {
policyType: "TargetTrackingScaling",
resourceId: scalingTarget.resourceId,
scalableDimension: scalingTarget.scalableDimension,
serviceNamespace: scalingTarget.serviceNamespace,
targetTrackingScalingPolicyConfiguration: {
targetValue: 70,
predefinedMetricSpecification: {
predefinedMetricType: "ECSServiceAverageCPUUtilization",
},
scaleInCooldown: 300,
scaleOutCooldown: 60,
},
});
// Exports
export const vpcId = vpc.vpcId;
export const clusterName = cluster.name;
export const url = pulumi.interpolate`http://${alb.loadBalancer.dnsName}`;
export const logGroupName = logGroup.name;
export const serviceName = service.service.name;
{
"name": "pulumi-aws-example",
"version": "1.0.0",
"description": "Pulumi AWS ECS Fargate deployment example",
"main": "index.ts",
"scripts": {
"preview": "pulumi preview",
"deploy": "pulumi up",
"destroy": "pulumi destroy"
},
"dependencies": {
"@pulumi/pulumi": "^3.100.0",
"@pulumi/aws": "^6.15.0",
"@pulumi/awsx": "^2.4.0"
},
"devDependencies": {
"@types/node": "^20.10.0",
"typescript": "^5.3.0"
}
}
name: pulumi-aws-example
runtime: nodejs
description: AWS ECS Fargate deployment with Pulumi
template:
config:
aws:region:
description: AWS region
default: us-east-1
imageTag:
description: Docker image tag to deploy
default: latest
dbPassword:
description: Database password (encrypted)
secret: true
Pulumi AWS ECS Fargate Example
Complete example deploying a containerized application to AWS ECS Fargate with Pulumi.
Prerequisites
- AWS account with credentials configured
- Pulumi CLI installed (
brew install pulumi) - Node.js 18+ installed
- Docker (for building container images)
Project Structure
pulumi-aws/
├── index.ts # Main Pulumi program
├── package.json # Dependencies
├── tsconfig.json # TypeScript config
├── Pulumi.yaml # Project metadata
├── Pulumi.dev.yaml # Dev stack config
└── Pulumi.prod.yaml # Production stack configSetup
1. Install dependencies:
npm install2. Configure AWS credentials:
aws configure3. Initialize Pulumi stack:
# Development
pulumi stack init dev
pulumi config set aws:region us-east-1
pulumi config set imageTag v1.0.0
pulumi config set --secret dbPassword mySecurePassword
# Production
pulumi stack init production
pulumi config set aws:region us-east-1
pulumi config set imageTag v1.0.0
pulumi config set --secret dbPassword productionPasswordDeployment
1. Preview changes:
pulumi preview2. Deploy infrastructure:
pulumi up3. Get outputs:
pulumi stack output url
# Output: http://app-alb-1234567.us-east-1.elb.amazonaws.comWhat Gets Deployed
- VPC: 2 availability zones, public/private subnets, NAT gateway
- ECS Cluster: Fargate-based container orchestration
- Application Load Balancer: HTTP traffic distribution
- Fargate Service: 2 container replicas, auto-scaling enabled
- CloudWatch Logs: Container logging
- IAM Roles: Task execution and task roles
Customization
Edit index.ts to customize:
- Container image repository
- CPU/memory allocation (default: 512 CPU, 1024 memory)
- Desired task count (default: 2)
- Environment variables
- Secrets (from AWS Secrets Manager)
Cost Estimate
Development (~$20-30/month):
- NAT Gateway: ~$32/month
- Fargate tasks (2 x t3.micro equivalent): ~$15/month
- ALB: ~$20/month
- Total: ~$67/month
Production (~$100-150/month):
- NAT Gateway: ~$32/month
- Fargate tasks (3 x t3.small equivalent): ~$50/month
- ALB: ~$20/month
- Total: ~$102/month
Cleanup
pulumi destroyTroubleshooting
Task fails to start:
# Check task logs
aws ecs describe-tasks --cluster <cluster-name> --tasks <task-id>
# View CloudWatch logs
aws logs tail /ecs/app-service --followALB returns 502/503:
- Verify container health check endpoint
- Check security group rules
- Ensure container port matches ALB target group
Next Steps
- Add CloudFront for CDN
- Configure custom domain with Route 53
- Set up auto-scaling policies
- Add RDS database
- Implement blue-green deployment
{
"compilerOptions": {
"target": "ES2020",
"module": "commonjs",
"moduleResolution": "node",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"outDir": "bin",
"rootDir": "."
},
"include": [
"**/*.ts"
],
"exclude": [
"node_modules"
]
}
skill: "deploying-applications"
version: "1.0"
domain: "backend"
base_outputs:
- path: ".dockerignore"
must_contain: ["node_modules", ".git", ".env"]
- path: "Dockerfile"
must_contain: ["FROM", "WORKDIR", "COPY", "CMD"]
- path: "docker-compose.yml"
must_contain: ["version:", "services:"]
- path: "deploy/README.md"
must_contain: ["Deployment", "Prerequisites", "Setup"]
conditional_outputs:
maturity:
starter:
- path: "deploy/vercel.json"
must_contain: ["builds", "routes"]
- path: "deploy/railway.json"
must_contain: ["build", "deploy"]
- path: ".github/workflows/deploy.yml"
must_contain: ["name:", "on:", "jobs:"]
intermediate:
- path: "infrastructure/pulumi/index.ts"
must_contain: ["import * as pulumi", "export"]
- path: "infrastructure/pulumi/Pulumi.yaml"
must_contain: ["name:", "runtime: nodejs"]
- path: "deploy/ecs/task-definition.json"
must_contain: ["family", "containerDefinitions", "cpu", "memory"]
- path: "deploy/cloud-run/service.yaml"
must_contain: ["apiVersion: serving.knative.dev", "kind: Service"]
- path: ".github/workflows/deploy-staging.yml"
must_contain: ["environment: staging"]
- path: ".github/workflows/deploy-production.yml"
must_contain: ["environment: production"]
advanced:
- path: "infrastructure/pulumi/index.ts"
must_contain: ["import * as pulumi", "import * as aws", "export"]
- path: "infrastructure/opentofu/main.tf"
must_contain: ["terraform {", "provider", "resource"]
- path: "infrastructure/opentofu/variables.tf"
must_contain: ["variable"]
- path: "infrastructure/opentofu/outputs.tf"
must_contain: ["output"]
- path: "k8s/base/deployment.yaml"
must_contain: ["apiVersion: apps/v1", "kind: Deployment", "replicas"]
- path: "k8s/base/service.yaml"
must_contain: ["apiVersion: v1", "kind: Service", "selector"]
- path: "k8s/base/ingress.yaml"
must_contain: ["apiVersion: networking.k8s.io", "kind: Ingress"]
- path: "argocd/application.yaml"
must_contain: ["apiVersion: argoproj.io", "kind: Application"]
- path: "helm/Chart.yaml"
must_contain: ["apiVersion: v2", "name:", "version:"]
- path: "helm/values.yaml"
must_contain: ["replicaCount:", "image:"]
infrastructure:
kubernetes:
- path: "k8s/base/deployment.yaml"
must_contain: ["apiVersion: apps/v1", "kind: Deployment", "spec:", "containers:"]
- path: "k8s/base/service.yaml"
must_contain: ["apiVersion: v1", "kind: Service", "spec:", "ports:"]
- path: "k8s/base/configmap.yaml"
must_contain: ["apiVersion: v1", "kind: ConfigMap", "data:"]
- path: "k8s/base/hpa.yaml"
must_contain: ["apiVersion: autoscaling/v2", "kind: HorizontalPodAutoscaler"]
- path: "k8s/overlays/production/kustomization.yaml"
must_contain: ["resources:", "../../base"]
- path: "helm/Chart.yaml"
must_contain: ["apiVersion: v2", "name:", "type: application"]
- path: "helm/values.yaml"
must_contain: ["replicaCount:", "image:", "resources:"]
- path: "helm/templates/deployment.yaml"
must_contain: ["{{", "Values", "Release.Name"]
- path: "argocd/application.yaml"
must_contain: ["apiVersion: argoproj.io/v1alpha1", "kind: Application", "source:", "destination:"]
docker_compose:
- path: "docker-compose.yml"
must_contain: ["version:", "services:", "networks:", "volumes:"]
- path: "docker-compose.prod.yml"
must_contain: ["version:", "services:"]
- path: ".env.example"
must_contain: ["DATABASE_URL", "NODE_ENV"]
- path: "Dockerfile"
must_contain: ["FROM", "WORKDIR", "RUN", "EXPOSE", "CMD"]
- path: "deploy/fly.toml"
must_contain: ["app =", "[build]", "[env]"]
- path: "deploy/render.yaml"
must_contain: ["services:", "name:", "env:"]
serverless:
- path: "infrastructure/sst/sst.config.ts"
must_contain: ["import { SSTConfig }", "export default"]
- path: "infrastructure/pulumi/lambda.ts"
must_contain: ["aws.lambda.Function", "runtime:", "handler:"]
- path: "infrastructure/pulumi/api-gateway.ts"
must_contain: ["aws.apigateway", "RestApi"]
- path: "serverless.yml"
must_contain: ["service:", "provider:", "functions:"]
- path: "wrangler.toml"
must_contain: ["name =", "main =", "compatibility_date"]
- path: "deno.json"
must_contain: ["tasks", "imports"]
cloud_provider:
aws:
- path: "infrastructure/pulumi/ecs.ts"
must_contain: ["aws.ecs.Cluster", "aws.ecs.Service", "aws.ecs.TaskDefinition"]
- path: "infrastructure/pulumi/lambda.ts"
must_contain: ["aws.lambda.Function", "aws.lambda.Permission"]
- path: "infrastructure/pulumi/vpc.ts"
must_contain: ["aws.ec2.Vpc", "aws.ec2.Subnet"]
- path: "deploy/ecs/task-definition.json"
must_contain: ["family", "containerDefinitions", "requiresCompatibilities"]
- path: ".github/workflows/deploy-ecs.yml"
must_contain: ["aws-actions/configure-aws-credentials", "aws ecs"]
gcp:
- path: "infrastructure/pulumi/cloud-run.ts"
must_contain: ["gcp.cloudrun.Service", "location:"]
- path: "deploy/cloud-run/service.yaml"
must_contain: ["apiVersion: serving.knative.dev/v1", "kind: Service"]
- path: ".github/workflows/deploy-cloud-run.yml"
must_contain: ["google-github-actions/auth", "gcloud run deploy"]
azure:
- path: "infrastructure/pulumi/app-service.ts"
must_contain: ["azure.appservice", "ResourceGroup"]
- path: ".github/workflows/deploy-azure.yml"
must_contain: ["azure/login", "azure/webapps-deploy"]
cloudflare:
- path: "wrangler.toml"
must_contain: ["name =", "main =", "compatibility_date ="]
- path: "src/index.ts"
must_contain: ["export default", "fetch", "Request", "Response"]
- path: ".github/workflows/deploy-workers.yml"
must_contain: ["cloudflare/wrangler-action"]
database:
neon:
- path: "infrastructure/pulumi/database.ts"
must_contain: ["@pulumi/neon", "Project", "Branch"]
- path: ".env.example"
must_contain: ["DATABASE_URL", "NEON_"]
turso:
- path: "infrastructure/pulumi/database.ts"
must_contain: ["turso", "database"]
- path: ".env.example"
must_contain: ["TURSO_DATABASE_URL", "TURSO_AUTH_TOKEN"]
planetscale:
- path: ".env.example"
must_contain: ["DATABASE_URL", "mysql://"]
- path: "deploy/planetscale/schema.sql"
must_contain: ["CREATE TABLE"]
gitops:
argocd:
- path: "argocd/application.yaml"
must_contain: ["apiVersion: argoproj.io/v1alpha1", "kind: Application", "spec:", "source:", "destination:"]
- path: "argocd/project.yaml"
must_contain: ["apiVersion: argoproj.io/v1alpha1", "kind: AppProject"]
- path: "k8s/base/kustomization.yaml"
must_contain: ["resources:"]
flux:
- path: "flux/clusters/production/infrastructure.yaml"
must_contain: ["apiVersion: kustomize.toolkit.fluxcd.io", "kind: Kustomization"]
- path: "flux/clusters/production/apps.yaml"
must_contain: ["apiVersion: kustomize.toolkit.fluxcd.io", "kind: Kustomization"]
scaffolding:
starter:
- template: "docker-compose-basic"
generates:
- "docker-compose.yml"
- "Dockerfile"
- ".dockerignore"
description: "Basic Docker Compose setup for local development"
- template: "vercel-deployment"
generates:
- "vercel.json"
- ".vercelignore"
- "deploy/README.md"
description: "Zero-config Vercel deployment for Next.js/Vite"
- template: "railway-deployment"
generates:
- "railway.json"
- "Procfile"
- "nixpacks.toml"
description: "Railway deployment configuration"
intermediate:
- template: "pulumi-ecs-fargate"
generates:
- "infrastructure/pulumi/index.ts"
- "infrastructure/pulumi/ecs.ts"
- "infrastructure/pulumi/vpc.ts"
- "infrastructure/pulumi/Pulumi.yaml"
- "infrastructure/pulumi/Pulumi.dev.yaml"
description: "AWS ECS Fargate deployment with Pulumi"
- template: "cloud-run-deployment"
generates:
- "deploy/cloud-run/service.yaml"
- ".github/workflows/deploy-cloud-run.yml"
- "Dockerfile"
description: "GCP Cloud Run serverless container deployment"
- template: "github-actions-cicd"
generates:
- ".github/workflows/deploy-staging.yml"
- ".github/workflows/deploy-production.yml"
- ".github/workflows/rollback.yml"
description: "Multi-environment CI/CD with GitHub Actions"
advanced:
- template: "kubernetes-helm-argocd"
generates:
- "k8s/base/deployment.yaml"
- "k8s/base/service.yaml"
- "k8s/base/ingress.yaml"
- "k8s/base/hpa.yaml"
- "k8s/overlays/production/kustomization.yaml"
- "helm/Chart.yaml"
- "helm/values.yaml"
- "helm/templates/deployment.yaml"
- "argocd/application.yaml"
- "argocd/project.yaml"
description: "Full GitOps deployment with Kubernetes, Helm 4.0, and ArgoCD"
- template: "pulumi-multi-cloud"
generates:
- "infrastructure/pulumi/index.ts"
- "infrastructure/pulumi/aws/ecs.ts"
- "infrastructure/pulumi/gcp/cloud-run.ts"
- "infrastructure/pulumi/cloudflare/workers.ts"
- "infrastructure/pulumi/shared/networking.ts"
- "infrastructure/pulumi/Pulumi.yaml"
description: "Multi-cloud deployment with Pulumi (AWS + GCP + Cloudflare)"
- template: "opentofu-terraform"
generates:
- "infrastructure/opentofu/main.tf"
- "infrastructure/opentofu/variables.tf"
- "infrastructure/opentofu/outputs.tf"
- "infrastructure/opentofu/backend.tf"
- "infrastructure/opentofu/versions.tf"
description: "Infrastructure as Code with OpenTofu (Terraform-compatible)"
- template: "sst-serverless-fullstack"
generates:
- "infrastructure/sst/sst.config.ts"
- "infrastructure/sst/stacks/ApiStack.ts"
- "infrastructure/sst/stacks/DatabaseStack.ts"
- "infrastructure/sst/stacks/FrontendStack.ts"
description: "Full-stack serverless deployment with SST v3"
- template: "edge-functions-cloudflare"
generates:
- "wrangler.toml"
- "src/index.ts"
- "src/router.ts"
- ".github/workflows/deploy-workers.yml"
description: "Edge function deployment with Cloudflare Workers + Hono"
metadata:
primary_blueprints: ["ci-cd"]
contributes_to:
- "Application deployment"
- "Infrastructure automation"
- "GitOps workflows"
- "Serverless architectures"
- "Container orchestration"
deployment_targets:
- "Kubernetes (EKS, GKE, AKS)"
- "AWS ECS Fargate"
- "GCP Cloud Run"
- "Serverless (Lambda, Cloud Functions)"
- "Edge Functions (Cloudflare Workers, Deno Deploy)"
- "Platform-as-a-Service (Vercel, Railway, Render, Fly.io)"
iac_tools:
- "Pulumi (TypeScript-first, multi-cloud)"
- "OpenTofu (Terraform-compatible, CNCF)"
- "SST v3 (Serverless TypeScript)"
- "Terraform (legacy support)"
gitops_tools:
- "ArgoCD (platform teams, rich UI)"
- "Flux (DevOps automation, CLI-focused)"
databases:
- "Neon PostgreSQL (scale-to-zero)"
- "Turso SQLite (edge deployment)"
- "PlanetScale MySQL (non-blocking schema changes)"
integration_points:
- "assembling-components (application → deployment flow)"
- "managing-containers (Docker, container optimization)"
- "implementing-cicd (CI/CD pipelines)"
- "observing-services (monitoring post-deployment)"
- "securing-infrastructure (security hardening)"
Deployment Strategies
Comprehensive guide to deployment strategies including blue-green, canary, rolling, recreate, and A/B testing patterns.
Table of Contents
- Strategy Overview
- Decision Matrix
- Rolling Deployment
- Blue-Green Deployment
- Canary Deployment
- Recreate Deployment
- A/B Testing
- Implementation Patterns
- Best Practices
- Troubleshooting
Strategy Overview
Deployment strategies determine how application updates roll out to production.
Quick Comparison
| Strategy | Downtime | Risk | Rollback Speed | Resource Cost | Complexity |
|---|---|---|---|---|---|
| Rolling | None | Medium | Fast | Low (1x) | Low |
| Blue-Green | None | Low | Instant | High (2x) | Medium |
| Canary | None | Very Low | Fast | Medium (1.1-1.5x) | High |
| Recreate | Yes | High | Slow | Low (1x) | Very Low |
| A/B Testing | None | Low | Medium | Medium (1.5x) | High |
Strategy Characteristics
Rolling Deployment:
- Gradual replacement of old version with new version
- No downtime, incremental updates
- Default strategy for most platforms
Blue-Green Deployment:
- Two identical environments (blue = current, green = new)
- Instant cutover via traffic switch
- Easy rollback, requires 2x resources
Canary Deployment:
- Small percentage of traffic to new version
- Gradual increase based on metrics
- Lowest risk, complex monitoring
Recreate Deployment:
- Stop all old instances, then start new ones
- Brief downtime, simplest approach
- Useful for stateful apps or dev environments
A/B Testing:
- Split traffic between versions based on criteria
- Long-running (days/weeks)
- Feature validation, not just deployment
Decision Matrix
Select deployment strategy based on application characteristics and requirements.
Decision Tree
DOWNTIME ACCEPTABLE?
├─ YES → Recreate
│ └─ Simplest, lowest resource cost
│
└─ NO → Continue...
ROLLBACK SPEED CRITICAL?
├─ YES (instant) → Blue-Green
│ └─ Requires 2x resources
│
└─ NO → Continue...
RISK TOLERANCE?
├─ VERY LOW (new feature/major change) → Canary
│ ├─ Complex monitoring required
│ └─ Gradual rollout (5% → 25% → 50% → 100%)
│
├─ LOW (validated change) → Blue-Green
│ └─ Instant rollback capability
│
├─ MEDIUM (incremental update) → Rolling
│ └─ Default for most deployments
│
└─ HIGH (dev/staging) → Recreate
└─ Acceptable downtime
FEATURE VALIDATION NEEDED?
└─ YES → A/B Testing
├─ Split by user segment
└─ Measure conversion metricsSelection Guidelines
Use Rolling When:
- Standard application updates
- Well-tested changes
- Cost optimization important
- Kubernetes default deployments
Use Blue-Green When:
- Zero-downtime mandatory
- Instant rollback required
- Database migrations (with backward compatibility)
- High-confidence releases
Use Canary When:
- Major version upgrades
- New features with unknown impact
- Complex distributed systems
- High-traffic production systems
Use Recreate When:
- Development/staging environments
- Stateful applications (single-instance databases)
- Downtime windows available
- Simple deployment pipeline
Use A/B Testing When:
- Feature flag validation
- UI/UX experiments
- Conversion optimization
- Long-running comparative analysis
Rolling Deployment
Gradually replace old version pods with new version pods.
How Rolling Works
Initial State: [v1] [v1] [v1] [v1]
Step 1: [v1] [v1] [v1] [v2] (25% new)
Step 2: [v1] [v1] [v2] [v2] (50% new)
Step 3: [v1] [v2] [v2] [v2] (75% new)
Step 4: [v2] [v2] [v2] [v2] (100% new)
Result: Zero downtime, gradual transitionKubernetes Implementation
Deployment with Rolling Update:
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app
spec:
replicas: 4
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1 # Max additional pods during update
maxUnavailable: 1 # Max pods unavailable during update
template:
metadata:
labels:
app: my-app
version: v2
spec:
containers:
- name: my-app
image: my-app:v2
readinessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 5
periodSeconds: 5Update Command:
# Update deployment image
kubectl set image deployment/my-app my-app=my-app:v2
# Monitor rollout status
kubectl rollout status deployment/my-app
# Pause rollout (if issues detected)
kubectl rollout pause deployment/my-app
# Resume rollout
kubectl rollout resume deployment/my-app
# Rollback to previous version
kubectl rollout undo deployment/my-appAWS ECS Rolling Deployment
{
"deploymentConfiguration": {
"deploymentCircuitBreaker": {
"enable": true,
"rollback": true
},
"maximumPercent": 200,
"minimumHealthyPercent": 100
}
}Explanation:
maximumPercent: 200allows 2x tasks during deploymentminimumHealthyPercent: 100ensures no capacity reduction- Circuit breaker automatically rolls back on failure
Configuration Parameters
maxSurge:
- Number of pods that can be created above desired count
- Value: number (e.g.,
1) or percentage (e.g.,25%) - Higher = faster rollout, more resources
maxUnavailable:
- Number of pods that can be unavailable during update
- Value: number (e.g.,
1) or percentage (e.g.,25%) 0= zero capacity reduction (safest)
Common Configurations:
# Fast rollout (more resources)
maxSurge: 2
maxUnavailable: 1
# Conservative (minimal extra resources)
maxSurge: 1
maxUnavailable: 0
# Aggressive (faster, riskier)
maxSurge: 50%
maxUnavailable: 25%Blue-Green Deployment
Two identical environments with instant traffic cutover.
How Blue-Green Works
Initial State:
BLUE (v1) ← 100% traffic
GREEN (idle)
Deploy New Version:
BLUE (v1) ← 100% traffic
GREEN (v2) ← 0% traffic (warming up)
Cutover:
BLUE (v1) ← 0% traffic
GREEN (v2) ← 100% traffic
Rollback (if needed):
BLUE (v1) ← 100% traffic (instant switch back)
GREEN (v2) ← 0% trafficKubernetes Implementation
Using Services and Labels:
# Service (routes traffic based on label)
apiVersion: v1
kind: Service
metadata:
name: my-app
spec:
selector:
app: my-app
version: blue # Switch to 'green' for cutover
ports:
- port: 80
targetPort: 8080
---
# Blue Deployment (current production)
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app-blue
spec:
replicas: 3
selector:
matchLabels:
app: my-app
version: blue
template:
metadata:
labels:
app: my-app
version: blue
spec:
containers:
- name: my-app
image: my-app:v1
---
# Green Deployment (new version)
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app-green
spec:
replicas: 3
selector:
matchLabels:
app: my-app
version: green
template:
metadata:
labels:
app: my-app
version: green
spec:
containers:
- name: my-app
image: my-app:v2Cutover Process:
# 1. Deploy green environment
kubectl apply -f my-app-green.yaml
# 2. Wait for green to be ready
kubectl wait --for=condition=available --timeout=300s deployment/my-app-green
# 3. Test green environment (port-forward for validation)
kubectl port-forward deployment/my-app-green 8080:8080
# 4. Switch traffic to green
kubectl patch service my-app -p '{"spec":{"selector":{"version":"green"}}}'
# 5. Monitor metrics (if issues, rollback)
# 6. If stable, delete blue deployment
kubectl delete deployment my-app-blueAWS CodeDeploy Blue-Green
# appspec.yml
version: 0.0
Resources:
- TargetService:
Type: AWS::ECS::Service
Properties:
TaskDefinition: "arn:aws:ecs:region:account:task-definition/my-app:2"
LoadBalancerInfo:
ContainerName: "my-app"
ContainerPort: 8080
PlatformVersion: "LATEST"
Hooks:
- BeforeInstall: "LambdaFunctionToValidateBeforeInstall"
- AfterInstall: "LambdaFunctionToValidateAfterInstall"
- BeforeAllowTraffic: "LambdaFunctionToValidateBeforeTrafficShift"
- AfterAllowTraffic: "LambdaFunctionToValidateAfterTrafficShift"Pulumi Blue-Green (AWS)
import * as aws from "@pulumi/aws";
import * as pulumi from "@pulumi/pulumi";
// Target group for blue environment
const blueTargetGroup = new aws.lb.TargetGroup("blue", {
port: 8080,
protocol: "HTTP",
vpcId: vpcId,
healthCheck: {
path: "/health",
interval: 30,
},
});
// Target group for green environment
const greenTargetGroup = new aws.lb.TargetGroup("green", {
port: 8080,
protocol: "HTTP",
vpcId: vpcId,
healthCheck: {
path: "/health",
interval: 30,
},
});
// Load balancer listener (switch target group for cutover)
const listener = new aws.lb.Listener("listener", {
loadBalancerArn: loadBalancerArn,
port: 80,
defaultActions: [{
type: "forward",
targetGroupArn: blueTargetGroup.arn, // Change to greenTargetGroup.arn
}],
});
// ECS Service (blue)
const blueService = new aws.ecs.Service("blue", {
cluster: clusterArn,
taskDefinition: blueTaskDefinition.arn,
desiredCount: 3,
loadBalancers: [{
targetGroupArn: blueTargetGroup.arn,
containerName: "my-app",
containerPort: 8080,
}],
});
// ECS Service (green)
const greenService = new aws.ecs.Service("green", {
cluster: clusterArn,
taskDefinition: greenTaskDefinition.arn,
desiredCount: 3,
loadBalancers: [{
targetGroupArn: greenTargetGroup.arn,
containerName: "my-app",
containerPort: 8080,
}],
});Canary Deployment
Gradual rollout to small percentage of traffic with monitoring.
How Canary Works
Step 1: [v1] [v1] [v1] [v1] [v2] 5% canary
↓ ↓ ↓ ↓ ↓
95% 5%
Step 2: [v1] [v1] [v1] [v2] [v2] 25% canary
↓ ↓ ↓ ↓ ↓
75% 25%
Step 3: [v1] [v1] [v2] [v2] [v2] 50% canary
↓ ↓ ↓ ↓ ↓
50% 50%
Step 4: [v2] [v2] [v2] [v2] [v2] 100% canary
Each step includes metrics monitoring and automatic rollback on failure.Kubernetes with Argo Rollouts
Installation:
kubectl create namespace argo-rollouts
kubectl apply -n argo-rollouts -f https://github.com/argoproj/argo-rollouts/releases/latest/download/install.yamlRollout Resource:
apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
name: my-app
spec:
replicas: 10
revisionHistoryLimit: 2
selector:
matchLabels:
app: my-app
template:
metadata:
labels:
app: my-app
spec:
containers:
- name: my-app
image: my-app:v2
ports:
- containerPort: 8080
strategy:
canary:
steps:
- setWeight: 5 # 5% traffic to canary
- pause: {duration: 2m}
- setWeight: 25 # 25% traffic
- pause: {duration: 5m}
- setWeight: 50 # 50% traffic
- pause: {duration: 5m}
- setWeight: 75 # 75% traffic
- pause: {duration: 5m}
# Automatic promotion to 100% after final pause
# Analysis for automatic rollback
analysis:
templates:
- templateName: success-rate
startingStep: 1
args:
- name: service-name
value: my-appAnalysis Template (Prometheus):
apiVersion: argoproj.io/v1alpha1
kind: AnalysisTemplate
metadata:
name: success-rate
spec:
args:
- name: service-name
metrics:
- name: success-rate
interval: 30s
successCondition: result >= 0.95
failureLimit: 3
provider:
prometheus:
address: http://prometheus.monitoring:9090
query: |
sum(rate(http_requests_total{
service="{{args.service-name}}",
status=~"2.."
}[2m]))
/
sum(rate(http_requests_total{
service="{{args.service-name}}"
}[2m]))Rollout Commands:
# Install kubectl plugin
brew install argoproj/tap/kubectl-argo-rollouts
# Watch rollout progress
kubectl argo rollouts get rollout my-app --watch
# Promote canary to next step
kubectl argo rollouts promote my-app
# Abort rollout (automatic rollback)
kubectl argo rollouts abort my-app
# List rollouts
kubectl argo rollouts list rolloutsIstio Canary Deployment
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
name: my-app
spec:
hosts:
- my-app
http:
- match:
- headers:
x-canary:
exact: "true"
route:
- destination:
host: my-app
subset: v2
- route:
- destination:
host: my-app
subset: v1
weight: 95
- destination:
host: my-app
subset: v2
weight: 5
---
apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
metadata:
name: my-app
spec:
host: my-app
subsets:
- name: v1
labels:
version: v1
- name: v2
labels:
version: v2AWS Lambda Canary
import * as aws from "@pulumi/aws";
const lambdaAlias = new aws.lambda.Alias("prod", {
functionName: lambdaFunction.name,
functionVersion: lambdaVersion.version,
routingConfig: {
additionalVersionWeights: {
[newVersion.version]: 0.05, // 5% canary traffic
},
},
});
// Gradually increase weight in subsequent updates:
// 0.05 → 0.25 → 0.50 → 1.00Recreate Deployment
Stop all old instances before starting new ones.
How Recreate Works
Step 1: [v1] [v1] [v1] [v1] (running)
Step 2: [ ] [ ] [ ] [ ] (downtime)
Step 3: [v2] [v2] [v2] [v2] (new version)
Downtime window: Time to stop old + start newKubernetes Implementation
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app
spec:
replicas: 3
strategy:
type: Recreate # All pods deleted before new ones created
template:
metadata:
labels:
app: my-app
spec:
containers:
- name: my-app
image: my-app:v2Update Process:
# Update deployment
kubectl apply -f deployment.yaml
# All v1 pods deleted immediately
# Then v2 pods created
# No gradual transition, brief downtimeUse Cases for Recreate
Stateful Single-Instance Applications:
- Development databases
- Legacy monoliths
- Applications with file locks
Scheduled Maintenance Windows:
- Off-hours deployments
- Pre-announced downtime
- Coordinated with users
Incompatible Version Transitions:
- Breaking database schema changes
- Non-backward compatible APIs
- Major architecture changes
A/B Testing
Long-running deployment with traffic split based on user attributes.
How A/B Testing Works
Users arrive → Route by attribute → Version A or B
↓
Measure metrics
↓
Compare results
↓
Choose winnerA/B Testing vs Canary
| Aspect | Canary | A/B Testing |
|---|---|---|
| Duration | Hours/days | Days/weeks |
| Goal | Safe rollout | Feature validation |
| Traffic Split | Gradual increase | Fixed percentage |
| Metrics | Error rates, latency | Conversion, engagement |
| Rollback | Automatic | Manual decision |
| Routing | Random | User attribute (ID, region, etc.) |
Kubernetes with Flagger
Installation:
kubectl apply -k github.com/fluxcd/flagger//kustomize/linkerdCanary Resource (A/B Mode):
apiVersion: flagger.app/v1beta1
kind: Canary
metadata:
name: my-app
spec:
targetRef:
apiVersion: apps/v1
kind: Deployment
name: my-app
service:
port: 8080
analysis:
interval: 1h
threshold: 10
iterations: 10
match:
- headers:
x-user-type:
exact: "beta" # Route beta users to canary
metrics:
- name: request-success-rate
thresholdRange:
min: 99
interval: 1m
- name: conversion-rate
thresholdRange:
min: 5.0
interval: 1mIstio A/B Testing
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
name: my-app
spec:
hosts:
- my-app.example.com
http:
# Route by user ID (hash-based split)
- match:
- headers:
user-id:
regex: "^[0-4].*" # User IDs starting 0-4 (50%)
route:
- destination:
host: my-app
subset: v2
# Remaining traffic to v1
- route:
- destination:
host: my-app
subset: v1Feature Flag Integration
Combine deployment with feature flags for fine-grained control.
// LaunchDarkly integration
import * as LaunchDarkly from 'launchdarkly-node-server-sdk';
const ldClient = LaunchDarkly.init(process.env.LAUNCHDARKLY_SDK_KEY);
app.get('/feature', async (req, res) => {
const user = {
key: req.user.id,
custom: {
region: req.user.region,
plan: req.user.plan,
},
};
const showNewFeature = await ldClient.variation(
'new-feature-flag',
user,
false
);
if (showNewFeature) {
// Serve new version
res.json({ version: 'v2' });
} else {
// Serve old version
res.json({ version: 'v1' });
}
});Implementation Patterns
Multi-Cloud Patterns
Cloud Provider Services:
| Provider | Rolling | Blue-Green | Canary |
|---|---|---|---|
| AWS | ECS/EKS | CodeDeploy, ALB | Lambda Aliases, App Mesh |
| GCP | GKE | Cloud Run Revisions | Traffic Splitting |
| Azure | AKS | Deployment Slots | Traffic Manager |
| Cloudflare | Workers Gradual Rollout | Workers Versions | Workers Routes |
Serverless Deployments
AWS Lambda with Canary:
# SAM template
Resources:
MyFunction:
Type: AWS::Serverless::Function
Properties:
Handler: index.handler
Runtime: nodejs20.x
AutoPublishAlias: live
DeploymentPreference:
Type: Canary10Percent5Minutes
Alarms:
- !Ref ErrorAlarm
Hooks:
PreTraffic: !Ref PreTrafficHook
PostTraffic: !Ref PostTrafficHookAvailable Deployment Types:
Canary10Percent5Minutes- 10% for 5 min, then 100%Canary10Percent10Minutes- 10% for 10 min, then 100%Linear10PercentEvery1Minute- +10% every minuteAllAtOnce- Immediate 100%
Edge Deployments
Cloudflare Workers Gradual Rollout:
# Deploy new version (0% traffic)
wrangler publish --new-version
# Route 10% traffic to new version
wrangler route --version=new --percentage=10
# Increase gradually
wrangler route --version=new --percentage=25
wrangler route --version=new --percentage=50
wrangler route --version=new --percentage=100Best Practices
Pre-Deployment Validation
Smoke Tests:
# Test new version before cutover
curl -H "X-Canary: true" https://api.example.com/healthDatabase Migrations:
- Use backward-compatible migrations
- Deploy schema changes before code changes
- Test rollback procedures
Dependency Checks:
- Verify external service compatibility
- Check API version requirements
- Validate configuration changes
Monitoring and Metrics
Key Metrics to Monitor:
Availability:
- Request success rate (target: >99.9%)
- Error rate by status code
- Uptime percentage
Performance:
- Response time (p50, p95, p99)
- Throughput (requests/second)
- Database query latency
Resources:
- CPU utilization
- Memory usage
- Network throughputPrometheus Queries:
# Request success rate
sum(rate(http_requests_total{status=~"2.."}[5m]))
/
sum(rate(http_requests_total[5m]))
# Error rate
sum(rate(http_requests_total{status=~"5.."}[5m]))
# p95 latency
histogram_quantile(0.95,
sum(rate(http_request_duration_seconds_bucket[5m])) by (le)
)Automatic Rollback
Conditions for Automatic Rollback:
- Error rate exceeds threshold (e.g., 5%)
- Latency increases significantly (e.g., >2x baseline)
- Health check failures
- Memory/CPU saturation
Kubernetes Example with Argo Rollouts:
strategy:
canary:
steps:
- setWeight: 10
- pause: {duration: 5m}
analysis:
templates:
- templateName: error-rate
args:
- name: error-threshold
value: "0.05" # 5% error rate triggers rollbackCommunication and Documentation
Deployment Checklist: 1. Review changes and test coverage 2. Check database migration compatibility 3. Verify rollback procedure 4. Schedule deployment window 5. Notify stakeholders 6. Prepare incident response plan 7. Monitor metrics during deployment 8. Document any issues encountered
Runbook Template:
# Deployment Runbook: [Feature Name]
## Pre-Deployment
- [ ] Schema migrations tested
- [ ] Rollback procedure verified
- [ ] Monitoring dashboards prepared
## Deployment Steps
1. Deploy green environment
2. Run smoke tests
3. Switch 5% traffic
4. Monitor for 10 minutes
5. Proceed to 25% if stable
## Rollback Procedure
1. Switch traffic back to blue
2. Investigate logs
3. Fix issues
4. Redeploy
## Stakeholders
- Team: @platform-team
- On-call: @sre-oncallTroubleshooting
Common Issues
Deployment Stuck in Progress:
# Check pod status
kubectl get pods -l app=my-app
# Check events
kubectl get events --sort-by='.lastTimestamp'
# Check rollout status
kubectl rollout status deployment/my-app
# Describe deployment
kubectl describe deployment my-appTraffic Not Switching (Blue-Green):
# Verify service selector
kubectl get service my-app -o yaml | grep selector
# Check endpoint health
kubectl get endpoints my-app
# Verify pod labels
kubectl get pods --show-labelsCanary Rollback Loop:
# Check analysis results
kubectl argo rollouts get rollout my-app
# View analysis logs
kubectl logs -n argo-rollouts deployment/argo-rollouts
# Review metrics query
kubectl describe analysistemplate success-ratePerformance Degradation
Symptoms:
- Increased latency during deployment
- Intermittent errors
- Resource exhaustion
Diagnosis:
# Check resource usage
kubectl top pods
# Review resource limits
kubectl describe pod <pod-name> | grep -A 5 "Limits:"
# Check readiness probe
kubectl describe pod <pod-name> | grep -A 10 "Readiness:"Solutions:
- Adjust
maxSurgeandmaxUnavailable - Increase resource requests/limits
- Extend readiness probe initial delay
- Reduce deployment batch size
Rollback Procedures
Immediate Rollback (Kubernetes):
# Rollback to previous revision
kubectl rollout undo deployment/my-app
# Rollback to specific revision
kubectl rollout history deployment/my-app
kubectl rollout undo deployment/my-app --to-revision=3
# Verify rollback
kubectl rollout status deployment/my-appBlue-Green Rollback:
# Switch traffic back to blue
kubectl patch service my-app -p '{"spec":{"selector":{"version":"blue"}}}'
# Verify traffic
kubectl get service my-app -o yaml | grep selectorCanary Rollback:
# Abort rollout (automatic rollback)
kubectl argo rollouts abort my-app
# Verify stable version
kubectl argo rollouts status my-appAdditional Resources
- Argo Rollouts documentation: https://argoproj.github.io/argo-rollouts/
- Flagger documentation: https://flagger.app/
- Istio traffic management: https://istio.io/latest/docs/concepts/traffic-management/
- AWS deployment strategies: https://docs.aws.amazon.com/whitepapers/latest/overview-deployment-options/deployment-strategies.html
Edge Functions
Deploy functions to global edge locations for low-latency responses.
Table of Contents
Cloudflare Workers
Execute code at Cloudflare's edge (200+ locations) with <5ms cold starts.
Performance Characteristics
- Cold Start: <5ms (V8 isolates, not containers)
- Locations: 200+ global edge locations
- Memory: 128MB per request
- CPU Time: 50ms (free), 15s (paid)
- Execution Model: V8 isolates (shared runtime, isolated contexts)
When to Use
- Global content delivery
- API gateway/middleware
- HTML rewriting (HTMLRewriter API)
- Edge authentication
- Low-latency API responses (<50ms)
Setup
# Install Wrangler
npm install -g wrangler
# Login
wrangler login
# Initialize project
wrangler init my-worker
# Develop locally
wrangler dev
# Deploy
wrangler deployBasic Worker
export default {
async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
return new Response('Hello from Cloudflare Workers!');
},
};With Hono Framework
import { Hono } from 'hono'
import { cors } from 'hono/cors'
import { logger } from 'hono/logger'
const app = new Hono()
app.use('*', cors())
app.use('*', logger())
app.get('/', (c) => {
return c.json({ message: 'Hello from edge!' })
})
app.get('/api/users/:id', async (c) => {
const id = c.req.param('id')
// Query edge database (Turso, D1)
const user = await c.env.DB
.prepare('SELECT * FROM users WHERE id = ?')
.bind(id)
.first()
if (!user) {
return c.json({ error: 'Not found' }, 404)
}
return c.json(user)
})
export default appEnvironment Variables & Secrets
wrangler.toml:
name = "my-worker"
main = "src/index.ts"
compatibility_date = "2024-01-01"
[vars]
ENVIRONMENT = "production"
# Secrets (set via wrangler secret)
# TURSO_AUTH_TOKEN
# DATABASE_URLSet Secrets:
# Set secret
wrangler secret put TURSO_AUTH_TOKEN
# List secrets
wrangler secret listAccess in Worker:
export default {
async fetch(request: Request, env: Env) {
const token = env.TURSO_AUTH_TOKEN; // Secret
const environment = env.ENVIRONMENT; // Variable
return new Response(`Environment: ${environment}`);
},
};D1 Database (SQLite)
Cloudflare's edge SQLite database.
import { Hono } from 'hono'
type Bindings = {
DB: D1Database
}
const app = new Hono<{ Bindings: Bindings }>()
app.get('/users', async (c) => {
const { results } = await c.env.DB
.prepare('SELECT * FROM users LIMIT 10')
.all()
return c.json(results)
})
app.post('/users', async (c) => {
const { name, email } = await c.req.json()
await c.env.DB
.prepare('INSERT INTO users (name, email) VALUES (?, ?)')
.bind(name, email)
.run()
return c.json({ success: true })
})
export default appwrangler.toml:
[[d1_databases]]
binding = "DB"
database_name = "my-database"
database_id = "<database-id>"KV Store (Key-Value)
import { Hono } from 'hono'
type Bindings = {
CACHE: KVNamespace
}
const app = new Hono<{ Bindings: Bindings }>()
app.get('/cache/:key', async (c) => {
const key = c.req.param('key')
const value = await c.env.CACHE.get(key)
if (!value) {
return c.json({ error: 'Not found' }, 404)
}
return c.text(value)
})
app.put('/cache/:key', async (c) => {
const key = c.req.param('key')
const value = await c.req.text()
// Store with 1 hour TTL
await c.env.CACHE.put(key, value, {
expirationTtl: 3600,
})
return c.json({ success: true })
})
export default appHTMLRewriter (Edge HTML Processing)
class LinkTransformer {
element(element: Element) {
// Add UTM parameters to all links
const href = element.getAttribute('href')
if (href) {
const url = new URL(href)
url.searchParams.set('utm_source', 'edge')
element.setAttribute('href', url.toString())
}
}
}
export default {
async fetch(request: Request) {
const response = await fetch(request)
return new HTMLRewriter()
.on('a', new LinkTransformer())
.transform(response)
},
}Deno Deploy
TypeScript-native edge runtime with global deployment.
Performance Characteristics
- Cold Start: <50ms
- Locations: Global edge (34 regions)
- Memory: 512MB per isolate
- CPU Time: 50ms per request
- Execution Model: V8 isolates
When to Use
- TypeScript APIs (no build step)
- Real-time applications (WebSockets)
- Edge rendering
- Deno-compatible code
Setup
# Install Deno
curl -fsSL https://deno.land/install.sh | sh
# Deploy via GitHub integration (recommended)
# Or use deployctl
# Install deployctl
deno install --allow-all --no-check -r -f https://deno.land/x/deploy/deployctl.ts
# Deploy
deployctl deploy --project=my-app main.tsBasic Server
import { serve } from "https://deno.land/std/http/server.ts";
serve((req: Request) => {
const url = new URL(req.url);
if (url.pathname === "/api/hello") {
return new Response(JSON.stringify({ message: "Hello from Deno!" }), {
headers: { "content-type": "application/json" },
});
}
return new Response("Not Found", { status: 404 });
});With Hono (Universal Framework)
import { Hono } from "https://deno.land/x/hono/mod.ts";
const app = new Hono();
app.get("/", (c) => c.json({ message: "Hello from Deno!" }));
app.get("/api/users/:id", async (c) => {
const id = c.req.param("id");
// Query Turso edge database
const response = await fetch(
`${Deno.env.get("TURSO_URL")}/v1/execute`,
{
method: "POST",
headers: {
Authorization: `Bearer ${Deno.env.get("TURSO_TOKEN")}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
statements: [`SELECT * FROM users WHERE id = ${id}`],
}),
}
);
const data = await response.json();
return c.json(data);
});
Deno.serve(app.fetch);Environment Variables
// Access environment variables
const apiKey = Deno.env.get("API_KEY");
const dbUrl = Deno.env.get("DATABASE_URL");
// Set via Deno Deploy dashboard or deployctl
deployctl deploy --project=my-app --env=API_KEY=secret main.tsKV Store
const kv = await Deno.openKv();
// Set value
await kv.set(["users", userId], userData);
// Get value
const entry = await kv.get(["users", userId]);
console.log(entry.value);
// Delete value
await kv.delete(["users", userId]);
// List keys
for await (const entry of kv.list({ prefix: ["users"] })) {
console.log(entry.key, entry.value);
}Vercel Edge Functions
Edge functions integrated with Next.js and Vercel platform.
Performance Characteristics
- Cold Start: <50ms
- Locations: 300+ global edge locations
- Memory: 128MB per function
- CPU Time: 30s per request
- Execution Model: V8 isolates
When to Use
- Next.js applications (tight integration)
- Edge middleware
- A/B testing
- Personalization
Setup (Next.js)
// app/api/hello/route.ts
import { NextRequest, NextResponse } from 'next/server'
export const runtime = 'edge' // Enable edge runtime
export async function GET(request: NextRequest) {
const geo = request.geo // Geolocation data
return NextResponse.json({
message: 'Hello from edge!',
location: geo?.city,
country: geo?.country,
})
}Edge Middleware
// middleware.ts
import { NextRequest, NextResponse } from 'next/server'
export function middleware(request: NextRequest) {
// A/B testing at edge
const bucket = Math.random() < 0.5 ? 'A' : 'B'
const response = NextResponse.next()
response.cookies.set('bucket', bucket)
// Rewrite based on bucket
if (bucket === 'B') {
return NextResponse.rewrite(new URL('/variant-b', request.url))
}
return response
}
export const config = {
matcher: '/pricing',
}Edge Config
Fast, global key-value store for Vercel Edge Functions.
import { get } from '@vercel/edge-config'
export const runtime = 'edge'
export async function GET() {
// Read from Edge Config (<10ms globally)
const featureFlags = await get('featureFlags')
return new Response(JSON.stringify(featureFlags))
}Hono Framework
Universal web framework that runs on all edge runtimes.
Why Hono
- Universal: Cloudflare Workers, Deno, Bun, Node.js, Vercel Edge
- Fast: 3-4x faster than Express
- Small: 14KB bundle size
- TypeScript-first: Full type safety
Installation
npm install honoBasic API
import { Hono } from 'hono'
import { cors } from 'hono/cors'
import { logger } from 'hono/logger'
import { etag } from 'hono/etag'
const app = new Hono()
// Middleware
app.use('*', cors())
app.use('*', logger())
app.use('*', etag())
// Routes
app.get('/', (c) => c.json({ message: 'Hello Hono!' }))
app.get('/users/:id', async (c) => {
const id = c.req.param('id')
// Fetch user...
return c.json({ id, name: 'John' })
})
app.post('/users', async (c) => {
const body = await c.req.json()
// Create user...
return c.json({ success: true }, 201)
})
// Export for different runtimes
export default appRuntime-Specific Adapters
Cloudflare Workers:
import { Hono } from 'hono'
const app = new Hono()
// ... routes ...
export default appDeno:
import { Hono } from "https://deno.land/x/hono/mod.ts"
const app = new Hono()
// ... routes ...
Deno.serve(app.fetch)Node.js:
import { Hono } from 'hono'
import { serve } from '@hono/node-server'
const app = new Hono()
// ... routes ...
serve(app)Bun:
import { Hono } from 'hono'
const app = new Hono()
// ... routes ...
export default appTypeScript Types
import { Hono } from 'hono'
type Bindings = {
DB: D1Database
CACHE: KVNamespace
TURSO_TOKEN: string
}
type Variables = {
userId: string
}
const app = new Hono<{ Bindings: Bindings; Variables: Variables }>()
app.use('*', async (c, next) => {
// Middleware with type safety
c.set('userId', '123')
await next()
})
app.get('/users', async (c) => {
const userId = c.get('userId') // TypeScript knows this is string
const token = c.env.TURSO_TOKEN // TypeScript knows this exists
return c.json({ userId })
})Comparison
Performance Matrix
| Platform | Cold Start | Global Locations | Memory | CPU Time | Free Tier |
|---|---|---|---|---|---|
| Cloudflare Workers | <5ms | 200+ | 128MB | 50ms (10s paid) | 100k requests/day |
| Deno Deploy | <50ms | 34 | 512MB | 50ms | 100k requests/month |
| Vercel Edge | <50ms | 300+ | 128MB | 30s | Included with Vercel |
Feature Comparison
| Feature | Cloudflare Workers | Deno Deploy | Vercel Edge |
|---|---|---|---|
| TypeScript | ✅ (build required) | ✅ Native | ✅ (build required) |
| WebSockets | ✅ Durable Objects | ✅ Native | ❌ |
| Edge Database | ✅ D1 (SQLite) | ✅ KV Store | ✅ Edge Config |
| KV Store | ✅ Workers KV | ✅ Deno KV | ✅ Edge Config |
| Cron Jobs | ✅ | ✅ | ❌ |
| Streaming | ✅ | ✅ | ✅ |
| File Upload | ✅ R2 | ❌ | ❌ |
Use Case Recommendations
Cloudflare Workers:
- ✅ Lowest cold starts (<5ms)
- ✅ Most global locations (200+)
- ✅ Edge database (D1)
- ✅ File storage (R2)
- ❌ WebSockets require Durable Objects
Deno Deploy:
- ✅ TypeScript-native (no build)
- ✅ WebSockets support
- ✅ Deno ecosystem
- ✅ Simple deployment
- ❌ Fewer locations (34)
Vercel Edge:
- ✅ Next.js integration
- ✅ Most global locations (300+)
- ✅ Edge Config (fast KV)
- ✅ Edge middleware
- ❌ Vendor lock-in
Best Practices
Edge-Friendly Patterns
1. Minimize Cold Start Time:
// ❌ Bad: Heavy imports
import { analyzeImage } from './ml-library'; // 5MB bundle
// ✅ Good: Lazy load or use edge-optimized libraries
const analyzeImage = await import('./edge-ml'); // 50KB2. Cache Aggressively:
import { Hono } from 'hono'
import { cache } from 'hono/cache'
const app = new Hono()
app.get(
'/api/data',
cache({
cacheName: 'api-cache',
cacheControl: 'public, max-age=3600',
}),
async (c) => {
const data = await fetch('https://api.example.com/data')
return c.json(await data.json())
}
)3. Use Edge Databases:
// ✅ Good: Edge database (Turso, D1)
const user = await c.env.DB
.prepare('SELECT * FROM users WHERE id = ?')
.bind(userId)
.first()
// ❌ Bad: Remote database (high latency)
const user = await fetch(`https://us-east-api.com/users/${userId}`)Error Handling
import { Hono } from 'hono'
const app = new Hono()
app.onError((err, c) => {
console.error(`${err}`)
if (err instanceof HTTPException) {
return c.json({ error: err.message }, err.status)
}
return c.json({ error: 'Internal Server Error' }, 500)
})
app.get('/users/:id', async (c) => {
const id = c.req.param('id')
try {
const user = await fetchUser(id)
if (!user) {
throw new HTTPException(404, { message: 'User not found' })
}
return c.json(user)
} catch (error) {
throw new HTTPException(500, { message: 'Database error' })
}
})Security
import { Hono } from 'hono'
import { secureHeaders } from 'hono/secure-headers'
import { csrf } from 'hono/csrf'
const app = new Hono()
// Security headers
app.use('*', secureHeaders())
// CSRF protection
app.use('*', csrf())
// Rate limiting (Cloudflare Workers)
app.use('*', async (c, next) => {
const ip = c.req.header('CF-Connecting-IP')
const key = `rate-limit:${ip}`
const count = await c.env.CACHE.get(key)
if (count && parseInt(count) > 100) {
return c.json({ error: 'Rate limit exceeded' }, 429)
}
await c.env.CACHE.put(key, String(parseInt(count || '0') + 1), {
expirationTtl: 60, // 1 minute window
})
await next()
})GitOps with ArgoCD and Flux
GitOps patterns for continuous deployment with ArgoCD and Flux.
Table of Contents
GitOps Principles
GitOps uses Git as the single source of truth for declarative infrastructure and applications.
Core Principles: 1. Declarative: Entire system state described declaratively 2. Versioned: System state versioned in Git 3. Immutable: Pull-based deployment (Git → Cluster) 4. Automated: Changes automatically applied 5. Reconciled: System continuously reconciles to desired state
Benefits:
- Audit trail (Git history)
- Rollback capability (git revert)
- Disaster recovery (redeploy from Git)
- Developer experience (git push to deploy)
ArgoCD Setup
ArgoCD is a declarative, GitOps continuous delivery tool for Kubernetes.
Installation
# Create namespace
kubectl create namespace argocd
# Install ArgoCD
kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml
# Access ArgoCD UI
kubectl port-forward svc/argocd-server -n argocd 8080:443
# Get initial admin password
kubectl -n argocd get secret argocd-initial-admin-secret -o jsonpath="{.data.password}" | base64 -dApplication Definition
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: my-app
namespace: argocd
spec:
project: default
source:
repoURL: https://github.com/org/repo
targetRevision: main
path: k8s/manifests
# For Helm charts
helm:
valueFiles:
- values-production.yaml
parameters:
- name: replicaCount
value: "3"
destination:
server: https://kubernetes.default.svc
namespace: production
syncPolicy:
automated:
prune: true # Delete resources not in Git
selfHeal: true # Force sync if cluster state differs
allowEmpty: false
syncOptions:
- CreateNamespace=true
retry:
limit: 5
backoff:
duration: 5s
factor: 2
maxDuration: 3mDirectory Structure for ArgoCD
git-repo/
├── apps/
│ ├── production/
│ │ ├── api/
│ │ │ ├── kustomization.yaml
│ │ │ ├── deployment.yaml
│ │ │ └── service.yaml
│ │ └── frontend/
│ │ └── ...
│ └── staging/
│ └── ...
├── base/
│ ├── api/
│ │ ├── kustomization.yaml
│ │ ├── deployment.yaml
│ │ └── service.yaml
│ └── frontend/
│ └── ...
└── argocd-apps/
├── production-apps.yaml
└── staging-apps.yamlMulti-Environment Pattern
App of Apps Pattern:
# argocd-apps/production-apps.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: production-apps
namespace: argocd
spec:
project: default
source:
repoURL: https://github.com/org/repo
targetRevision: main
path: apps/production
destination:
server: https://kubernetes.default.svc
namespace: argocd
syncPolicy:
automated:
prune: true
selfHeal: trueHealth Checks
ArgoCD automatically detects health based on resource type.
Custom Health Check:
-- Custom health check for CRD
hs = {}
if obj.status ~= nil then
if obj.status.conditions ~= nil then
for i, condition in ipairs(obj.status.conditions) do
if condition.type == "Ready" and condition.status == "True" then
hs.status = "Healthy"
hs.message = "Application is ready"
return hs
end
end
end
end
hs.status = "Progressing"
hs.message = "Application is not ready"
return hsArgoCD CLI Usage
# Install ArgoCD CLI
brew install argocd
# Login
argocd login localhost:8080
# List applications
argocd app list
# Get application details
argocd app get my-app
# Sync application
argocd app sync my-app
# Rollback to previous version
argocd app rollback my-app
# View diff before sync
argocd app diff my-app
# Set application parameters
argocd app set my-app --helm-set replicaCount=5Flux Setup
Flux is a GitOps toolkit for Kubernetes.
Installation
# Install Flux CLI
brew install fluxcd/tap/flux
# Check prerequisites
flux check --pre
# Bootstrap Flux (GitHub)
flux bootstrap github \
--owner=my-org \
--repository=my-repo \
--branch=main \
--path=clusters/production \
--personal
# Bootstrap creates:
# - flux-system namespace
# - source-controller
# - kustomize-controller
# - helm-controller
# - notification-controllerGitRepository Source
apiVersion: source.toolkit.fluxcd.io/v1
kind: GitRepository
metadata:
name: my-app-repo
namespace: flux-system
spec:
interval: 1m
url: https://github.com/org/repo
ref:
branch: main
secretRef:
name: git-credentialsKustomization
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
name: my-app
namespace: flux-system
spec:
interval: 5m
path: ./k8s/overlays/production
prune: true
sourceRef:
kind: GitRepository
name: my-app-repo
healthChecks:
- apiVersion: apps/v1
kind: Deployment
name: my-app
namespace: production
timeout: 2mHelmRelease
apiVersion: helm.toolkit.fluxcd.io/v2beta1
kind: HelmRelease
metadata:
name: my-app
namespace: production
spec:
interval: 10m
chart:
spec:
chart: my-app
version: "1.0.0"
sourceRef:
kind: HelmRepository
name: my-helm-repo
namespace: flux-system
values:
replicaCount: 3
image:
tag: v1.2.3
upgrade:
remediation:
retries: 3Directory Structure for Flux
git-repo/
├── clusters/
│ ├── production/
│ │ ├── flux-system/
│ │ │ ├── gotk-components.yaml
│ │ │ ├── gotk-sync.yaml
│ │ │ └── kustomization.yaml
│ │ └── apps/
│ │ ├── kustomization.yaml
│ │ ├── api.yaml
│ │ └── frontend.yaml
│ └── staging/
│ └── ...
└── apps/
├── base/
│ ├── api/
│ └── frontend/
└── overlays/
├── production/
└── staging/Flux Notifications
apiVersion: notification.toolkit.fluxcd.io/v1beta1
kind: Provider
metadata:
name: slack
namespace: flux-system
spec:
type: slack
channel: deployments
secretRef:
name: slack-webhook
---
apiVersion: notification.toolkit.fluxcd.io/v1beta1
kind: Alert
metadata:
name: deployment-alerts
namespace: flux-system
spec:
providerRef:
name: slack
eventSeverity: info
eventSources:
- kind: Kustomization
name: '*'
- kind: HelmRelease
name: '*'ArgoCD vs Flux
Comparison Matrix
| Feature | ArgoCD | Flux |
|---|---|---|
| UI | Rich web UI | CLI-focused (no UI) |
| Multi-tenancy | Built-in RBAC | Requires setup |
| CNCF Status | Graduated | Graduated |
| Architecture | Single controller | Multiple controllers |
| Complexity | Higher learning curve | Simpler, Kubernetes-native |
| Helm Support | Excellent | Excellent |
| Kustomize Support | Excellent | Excellent |
| Image Automation | Via Image Updater | Built-in |
| Notifications | Built-in | Built-in |
| Multi-cluster | Excellent | Good |
| Best for | Platform teams, multi-cluster | DevOps automation, single cluster |
When to Choose ArgoCD
- Multi-team platform (RBAC, SSO)
- Need web UI for visibility
- Centralized control plane
- Multi-cluster management
- Complex application dependencies
When to Choose Flux
- Kubernetes-native approach
- CI/CD pipeline integration
- Image automation workflows
- Simpler architecture preferred
- Strong Kustomize usage
Best Practices
Repository Structure
Monorepo Pattern:
gitops-repo/
├── apps/
│ ├── base/ # Base configurations
│ └── overlays/ # Environment-specific
│ ├── dev/
│ ├── staging/
│ └── production/
├── infrastructure/
│ ├── controllers/ # Ingress, cert-manager
│ └── monitoring/ # Prometheus, Grafana
└── clusters/
├── dev-cluster/
├── staging-cluster/
└── production-cluster/Multi-Repo Pattern:
app-repo/ # Application code
├── src/
└── k8s/ # Manifests
infra-repo/ # Infrastructure configs
├── base/
└── overlays/Secrets Management
Sealed Secrets (Recommended):
# Install kubeseal
brew install kubeseal
# Create sealed secret
kubectl create secret generic my-secret \
--from-literal=password=mysecret \
--dry-run=client -o yaml | \
kubeseal -o yaml > sealed-secret.yaml
# Commit sealed-secret.yaml to Git
git add sealed-secret.yaml
git commit -m "Add sealed secret"External Secrets Operator:
Works with AWS Secrets Manager, Vault, GCP Secret Manager.
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: database-credentials
spec:
refreshInterval: 1h
secretStoreRef:
name: aws-secretsmanager
target:
name: database-secret
data:
- secretKey: password
remoteRef:
key: prod/database/passwordProgressive Rollout
Canary Deployment with ArgoCD + Argo Rollouts:
apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
name: my-app
spec:
replicas: 10
strategy:
canary:
steps:
- setWeight: 10
- pause: {duration: 5m}
- setWeight: 30
- pause: {duration: 5m}
- setWeight: 60
- pause: {duration: 5m}
- setWeight: 100
template:
spec:
containers:
- name: my-app
image: my-app:v2Disaster Recovery
Backup Strategy: 1. Git is source of truth (commit history = backup) 2. Periodic cluster state snapshots (Velero) 3. Database backups (separate from GitOps)
Recovery Procedure:
# 1. Provision new cluster
# 2. Install ArgoCD/Flux
flux bootstrap github --owner=org --repository=repo
# 3. Applications auto-deploy from Git
# 4. Restore databases from backupsMonitoring and Alerting
Metrics to Monitor:
- Sync status (healthy, degraded, progressing)
- Sync frequency
- Failed syncs
- Out-of-sync duration
Prometheus Metrics (ArgoCD):
argocd_app_sync_total
argocd_app_health_status
argocd_app_sync_statusAlerts:
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
name: argocd-alerts
spec:
groups:
- name: argocd
rules:
- alert: AppOutOfSync
expr: argocd_app_sync_status{sync_status="OutOfSync"} > 0
for: 10m
annotations:
summary: "Application out of sync for 10 minutes"Troubleshooting
ArgoCD Sync Issues
# Check application status
argocd app get my-app
# View sync errors
argocd app sync my-app --dry-run
# Force sync (bypass hooks)
argocd app sync my-app --force
# Delete and recreate resources
argocd app sync my-app --prune --replaceFlux Reconciliation Issues
# Check Flux controllers
flux check
# View Kustomization status
flux get kustomizations
# Suspend reconciliation
flux suspend kustomization my-app
# Resume reconciliation
flux resume kustomization my-app
# Force reconciliation
flux reconcile kustomization my-app --with-sourceGit Authentication Issues
# ArgoCD: Add repository
argocd repo add https://github.com/org/repo \
--username git \
--password <PAT>
# Flux: Create secret
flux create secret git git-credentials \
--url=https://github.com/org/repo \
--username=git \
--password=<PAT>Kubernetes Deployment Patterns
Advanced Kubernetes patterns including Helm 4.0, service mesh, and autoscaling.
Table of Contents
Helm 4.0 Chart Structure
Helm 4.0 (November 2025 release) introduces architectural changes.
Basic Chart Structure
my-app-chart/
├── Chart.yaml # Chart metadata
├── values.yaml # Default configuration
├── values-production.yaml # Production overrides
├── values-staging.yaml # Staging overrides
├── templates/
│ ├── deployment.yaml # Kubernetes Deployment
│ ├── service.yaml # Kubernetes Service
│ ├── ingress.yaml # Ingress configuration
│ ├── configmap.yaml # ConfigMap
│ ├── secret.yaml # Secrets (use sealed-secrets)
│ └── hpa.yaml # Horizontal Pod Autoscaler
└── charts/ # Dependency chartsChart.yaml Example
apiVersion: v2
name: my-app
description: My production application
type: application
version: 1.0.0
appVersion: "1.0.0"
dependencies:
- name: postgresql
version: 12.x.x
repository: https://charts.bitnami.com/bitnami
condition: postgresql.enabledvalues.yaml Example
replicaCount: 3
image:
repository: my-app
pullPolicy: IfNotPresent
tag: "latest"
service:
type: ClusterIP
port: 80
ingress:
enabled: true
className: nginx
hosts:
- host: my-app.example.com
paths:
- path: /
pathType: Prefix
resources:
limits:
cpu: 500m
memory: 512Mi
requests:
cpu: 250m
memory: 256Mi
autoscaling:
enabled: true
minReplicas: 3
maxReplicas: 10
targetCPUUtilizationPercentage: 70Deployment Template
# templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "my-app.fullname" . }}
labels:
{{- include "my-app.labels" . | nindent 4 }}
spec:
{{- if not .Values.autoscaling.enabled }}
replicas: {{ .Values.replicaCount }}
{{- end }}
selector:
matchLabels:
{{- include "my-app.selectorLabels" . | nindent 6 }}
template:
metadata:
labels:
{{- include "my-app.selectorLabels" . | nindent 8 }}
spec:
containers:
- name: {{ .Chart.Name }}
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
ports:
- name: http
containerPort: 3000
protocol: TCP
livenessProbe:
httpGet:
path: /health
port: http
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
httpGet:
path: /ready
port: http
initialDelaySeconds: 5
periodSeconds: 5
resources:
{{- toYaml .Values.resources | nindent 12 }}
env:
- name: NODE_ENV
value: production
- name: DATABASE_URL
valueFrom:
secretKeyRef:
name: {{ include "my-app.fullname" . }}-secrets
key: database-urlDeployment Commands
# Install chart
helm install my-app ./my-app-chart
# Install with custom values
helm install my-app ./my-app-chart -f values-production.yaml
# Upgrade release
helm upgrade my-app ./my-app-chart -f values-production.yaml
# Rollback to previous version
helm rollback my-app 1
# List releases
helm list
# Get release status
helm status my-appService Mesh Comparison
Linkerd (Performance-Focused)
Performance Characteristics:
- 5-10% latency overhead
- Rust-based control plane
- Minimal resource usage
When to Use:
- Performance is critical
- Simple, opinionated configuration
- Lightweight footprint required
Installation:
# Install Linkerd CLI
curl --proto '=https' --tlsv1.2 -sSfL https://run.linkerd.io/install | sh
# Check pre-requisites
linkerd check --pre
# Install control plane
linkerd install --crds | kubectl apply -f -
linkerd install | kubectl apply -f -
# Verify installation
linkerd check
# Inject service mesh into namespace
kubectl annotate namespace default linkerd.io/inject=enabled
# Or inject into deployment
kubectl get deployment my-app -o yaml | linkerd inject - | kubectl apply -f -mTLS Verification:
# Check if traffic is encrypted
linkerd tap deployment/my-app
# Verify mTLS
linkerd edges deploymentIstio (Feature-Rich)
Performance Characteristics:
- 25-35% latency overhead
- Envoy-based (C++)
- Higher resource usage
When to Use:
- Advanced traffic management (canary, A/B testing)
- Rich observability (Kiali, Grafana)
- Complex routing requirements
Installation:
# Install Istio CLI
curl -L https://istio.io/downloadIstio | sh -
# Install Istio
istioctl install --set profile=default -y
# Label namespace for injection
kubectl label namespace default istio-injection=enabled
# Verify installation
istioctl verify-installTraffic Splitting (Canary):
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
name: my-app
spec:
hosts:
- my-app
http:
- match:
- headers:
canary:
exact: "true"
route:
- destination:
host: my-app
subset: v2
- route:
- destination:
host: my-app
subset: v1
weight: 90
- destination:
host: my-app
subset: v2
weight: 10Comparison Matrix
| Feature | Linkerd | Istio |
|---|---|---|
| Performance | 5-10% overhead | 25-35% overhead |
| Language | Rust | C++ (Envoy) |
| Complexity | Low | High |
| mTLS | Automatic | Automatic |
| Traffic Splitting | Yes | Yes (more advanced) |
| Circuit Breaking | Basic | Advanced |
| Retry Logic | Basic | Advanced |
| Observability | Built-in | Kiali, Grafana |
| Memory Usage | ~100MB | ~500MB+ |
| Learning Curve | Easy | Steep |
Autoscaling Strategies
Horizontal Pod Autoscaler (HPA)
Scale pod count based on metrics.
CPU-Based HPA:
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: my-app-hpa
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: my-app
minReplicas: 3
maxReplicas: 10
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70Memory-Based HPA:
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: my-app-hpa
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: my-app
minReplicas: 3
maxReplicas: 10
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70
- type: Resource
resource:
name: memory
target:
type: Utilization
averageUtilization: 80Custom Metrics (RPS):
Requires Prometheus adapter.
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: my-app-hpa
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: my-app
minReplicas: 3
maxReplicas: 20
metrics:
- type: Pods
pods:
metric:
name: http_requests_per_second
target:
type: AverageValue
averageValue: "1000"Vertical Pod Autoscaler (VPA)
Automatically adjust CPU/memory requests.
apiVersion: autoscaling.k8s.io/v1
kind: VerticalPodAutoscaler
metadata:
name: my-app-vpa
spec:
targetRef:
apiVersion: apps/v1
kind: Deployment
name: my-app
updatePolicy:
updateMode: "Auto"
resourcePolicy:
containerPolicies:
- containerName: my-app
minAllowed:
cpu: 100m
memory: 128Mi
maxAllowed:
cpu: 1000m
memory: 1GiCluster Autoscaler
Automatically add/remove nodes based on resource needs.
AWS EKS Example:
apiVersion: apps/v1
kind: Deployment
metadata:
name: cluster-autoscaler
namespace: kube-system
spec:
template:
spec:
containers:
- image: k8s.gcr.io/autoscaling/cluster-autoscaler:v1.28.0
name: cluster-autoscaler
command:
- ./cluster-autoscaler
- --v=4
- --stderrthreshold=info
- --cloud-provider=aws
- --skip-nodes-with-local-storage=false
- --expander=least-waste
- --node-group-auto-discovery=asg:tag=k8s.io/cluster-autoscaler/enabled,k8s.io/cluster-autoscaler/my-clusterSecurity Best Practices
Network Policies
Restrict pod-to-pod communication.
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: api-network-policy
spec:
podSelector:
matchLabels:
app: api
policyTypes:
- Ingress
- Egress
ingress:
- from:
- podSelector:
matchLabels:
app: frontend
ports:
- protocol: TCP
port: 8000
egress:
- to:
- podSelector:
matchLabels:
app: database
ports:
- protocol: TCP
port: 5432Pod Security Standards
apiVersion: v1
kind: Namespace
metadata:
name: production
labels:
pod-security.kubernetes.io/enforce: restricted
pod-security.kubernetes.io/audit: restricted
pod-security.kubernetes.io/warn: restrictedSecrets Management
Use external secrets operator with AWS Secrets Manager, Vault, or GCP Secret Manager.
apiVersion: external-secrets.io/v1beta1
kind: SecretStore
metadata:
name: aws-secretsmanager
spec:
provider:
aws:
service: SecretsManager
region: us-east-1
auth:
jwt:
serviceAccountRef:
name: external-secrets-sa
---
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: database-credentials
spec:
refreshInterval: 1h
secretStoreRef:
name: aws-secretsmanager
kind: SecretStore
target:
name: database-secret
creationPolicy: Owner
data:
- secretKey: database-url
remoteRef:
key: prod/database/urlRBAC Policies
Principle of least privilege.
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
namespace: production
name: deployment-manager
rules:
- apiGroups: ["apps"]
resources: ["deployments", "replicasets"]
verbs: ["get", "list", "watch", "create", "update", "patch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: deployment-manager-binding
namespace: production
subjects:
- kind: ServiceAccount
name: ci-cd-sa
namespace: production
roleRef:
kind: Role
name: deployment-manager
apiGroup: rbac.authorization.k8s.ioTroubleshooting
Pod Crashes
# Check pod status
kubectl get pods
# View pod logs
kubectl logs <pod-name>
# Describe pod (events section critical)
kubectl describe pod <pod-name>
# Previous container logs (if crashed)
kubectl logs <pod-name> --previous
# Interactive debugging
kubectl exec -it <pod-name> -- /bin/shResource Issues
# Check resource usage
kubectl top pods
kubectl top nodes
# Check resource requests vs limits
kubectl describe pod <pod-name> | grep -A 5 "Requests:"
# View events
kubectl get events --sort-by='.lastTimestamp'Service Mesh Issues
# Linkerd tap traffic
linkerd tap deployment/my-app
# Linkerd check
linkerd check
# Istio proxy status
istioctl proxy-status
# Istio analyze configuration
istioctl analyzePulumi Infrastructure as Code Guide
TypeScript-first Infrastructure as Code with Pulumi.
Context7 Research:
- Library:
/pulumi/docs(Trust: 94.6/100, 9,525 snippets) - Website:
/websites/pulumi(86.4 score, 6,034 snippets)
Table of Contents
- Core Concepts
- Project Structure
- AWS Patterns
- GCP Patterns
- Multi-Cloud Patterns
- Component Model
- Best Practices
Core Concepts
Resources
Infrastructure components (EC2, S3, Lambda, etc.).
import * as aws from "@pulumi/aws";
// Create S3 bucket (resource)
const bucket = new aws.s3.Bucket("my-bucket", {
acl: "private",
tags: {
Environment: "production",
},
});
// Export bucket name
export const bucketName = bucket.id;Stacks
Isolated environments (dev, staging, production).
import * as pulumi from "@pulumi/pulumi";
const config = new pulumi.Config();
const environment = config.require("environment");
// Stack-specific configuration
const instanceType = environment === "production" ? "t3.large" : "t3.micro";Outputs
Values available after deployment.
import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";
const bucket = new aws.s3.Bucket("bucket");
// Simple export
export const bucketName = bucket.id;
// Interpolated output
export const bucketUrl = pulumi.interpolate`https://${bucket.bucketDomainName}`;Configuration
Stack-specific values (secrets, environment variables).
# Pulumi.dev.yaml
config:
aws:region: us-east-1
myapp:dbSize: small
myapp:dbPassword:
secure: AAABAQCc... # Encrypted secret
# Pulumi.prod.yaml
config:
aws:region: us-east-1
myapp:dbSize: large
myapp:dbPassword:
secure: AAABAQD...Access in Code:
import * as pulumi from "@pulumi/pulumi";
const config = new pulumi.Config();
const dbSize = config.require("dbSize");
const dbPassword = config.requireSecret("dbPassword");Project Structure
Basic Structure
my-infrastructure/
├── index.ts # Main entry point
├── Pulumi.yaml # Project metadata
├── Pulumi.dev.yaml # Dev stack config
├── Pulumi.staging.yaml # Staging stack config
├── Pulumi.production.yaml # Production stack config
├── package.json
├── tsconfig.json
└── node_modules/Component-Based Structure
my-infrastructure/
├── index.ts # Stack definition
├── components/
│ ├── WebService.ts # Reusable web service
│ ├── Database.ts # Database component
│ └── Network.ts # VPC/networking
├── config/
│ ├── dev.ts # Dev configuration
│ ├── staging.ts # Staging configuration
│ └── production.ts # Production configuration
└── Pulumi.yamlPulumi.yaml
name: my-infrastructure
runtime: nodejs
description: Production infrastructure for my application
# Optional: Template for new stacks
template:
config:
aws:region:
description: AWS region
default: us-east-1
myapp:environment:
description: Environment name
myapp:dbSize:
description: Database instance size
default: smallAWS Patterns
ECS Fargate Service
Complete serverless container deployment.
import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";
import * as awsx from "@pulumi/awsx";
const config = new pulumi.Config();
const imageTag = config.require("imageTag");
const dbUrl = config.requireSecret("dbUrl");
// VPC with public/private subnets
const vpc = new awsx.ec2.Vpc("app-vpc", {
numberOfAvailabilityZones: 2,
natGateways: { strategy: "Single" },
});
// ECS Cluster
const cluster = new aws.ecs.Cluster("app-cluster", {
tags: { Environment: "production" },
});
// Application Load Balancer
const alb = new awsx.lb.ApplicationLoadBalancer("app-alb", {
subnetIds: vpc.publicSubnetIds,
});
// Fargate Service
const service = new awsx.ecs.FargateService("app-service", {
cluster: cluster.arn,
assignPublicIp: false,
taskDefinitionArgs: {
container: {
image: `my-repo:${imageTag}`,
cpu: 512,
memory: 1024,
essential: true,
environment: [
{ name: "NODE_ENV", value: "production" },
],
secrets: [
{
name: "DATABASE_URL",
valueFrom: dbUrl,
},
],
portMappings: [{
containerPort: 3000,
targetGroup: alb.defaultTargetGroup,
}],
},
},
desiredCount: 2,
});
export const url = pulumi.interpolate`http://${alb.loadBalancer.dnsName}`;Lambda Function with API Gateway
import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";
// IAM role for Lambda
const role = new aws.iam.Role("lambda-role", {
assumeRolePolicy: JSON.stringify({
Version: "2012-10-17",
Statement: [{
Action: "sts:AssumeRole",
Effect: "Allow",
Principal: { Service: "lambda.amazonaws.com" },
}],
}),
});
// Attach basic execution policy
new aws.iam.RolePolicyAttachment("lambda-policy", {
role: role.name,
policyArn: "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole",
});
// Lambda function
const lambda = new aws.lambda.Function("api", {
runtime: "nodejs20.x",
handler: "index.handler",
role: role.arn,
code: new pulumi.asset.FileArchive("./dist"),
environment: {
variables: {
NODE_ENV: "production",
},
},
timeout: 30,
memorySize: 512,
});
// API Gateway
const api = new aws.apigatewayv2.Api("api", {
protocolType: "HTTP",
});
const integration = new aws.apigatewayv2.Integration("api-integration", {
apiId: api.id,
integrationType: "AWS_PROXY",
integrationUri: lambda.arn,
payloadFormatVersion: "2.0",
});
const route = new aws.apigatewayv2.Route("api-route", {
apiId: api.id,
routeKey: "$default",
target: pulumi.interpolate`integrations/${integration.id}`,
});
const stage = new aws.apigatewayv2.Stage("api-stage", {
apiId: api.id,
name: "$default",
autoDeploy: true,
});
// Grant API Gateway permission to invoke Lambda
new aws.lambda.Permission("api-permission", {
action: "lambda:InvokeFunction",
function: lambda.name,
principal: "apigateway.amazonaws.com",
sourceArn: pulumi.interpolate`${api.executionArn}/*`,
});
export const apiUrl = pulumi.interpolate`${api.apiEndpoint}`;S3 + CloudFront (Static Website)
import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";
// S3 bucket for static files
const bucket = new aws.s3.Bucket("website-bucket", {
website: {
indexDocument: "index.html",
errorDocument: "index.html", // SPA routing
},
});
// Bucket policy for CloudFront
const bucketPolicy = new aws.s3.BucketPolicy("bucket-policy", {
bucket: bucket.id,
policy: bucket.arn.apply(arn => JSON.stringify({
Version: "2012-10-17",
Statement: [{
Effect: "Allow",
Principal: "*",
Action: "s3:GetObject",
Resource: `${arn}/*`,
}],
})),
});
// CloudFront Origin Access Identity
const oai = new aws.cloudfront.OriginAccessIdentity("oai", {
comment: "OAI for static website",
});
// CloudFront distribution
const cdn = new aws.cloudfront.Distribution("cdn", {
enabled: true,
defaultRootObject: "index.html",
origins: [{
originId: bucket.id,
domainName: bucket.bucketRegionalDomainName,
s3OriginConfig: {
originAccessIdentity: oai.cloudfrontAccessIdentityPath,
},
}],
defaultCacheBehavior: {
targetOriginId: bucket.id,
viewerProtocolPolicy: "redirect-to-https",
allowedMethods: ["GET", "HEAD", "OPTIONS"],
cachedMethods: ["GET", "HEAD"],
forwardedValues: {
queryString: false,
cookies: { forward: "none" },
},
compress: true,
minTtl: 0,
defaultTtl: 3600,
maxTtl: 86400,
},
restrictions: {
geoRestriction: {
restrictionType: "none",
},
},
viewerCertificate: {
cloudfrontDefaultCertificate: true,
},
customErrorResponses: [
{
errorCode: 404,
responseCode: 200,
responsePagePath: "/index.html", // SPA routing
},
],
});
// Sync local files to S3
const indexHtml = new aws.s3.BucketObject("index.html", {
bucket: bucket.id,
source: new pulumi.asset.FileAsset("./dist/index.html"),
contentType: "text/html",
acl: "public-read",
});
export const cdnUrl = cdn.domainName;
export const bucketName = bucket.id;GCP Patterns
Cloud Run Service
import * as pulumi from "@pulumi/pulumi";
import * as gcp from "@pulumi/gcp";
const config = new pulumi.Config();
const imageTag = config.require("imageTag");
const gcpProject = config.require("gcpProject");
// Cloud Run service
const service = new gcp.cloudrun.Service("api-service", {
location: "us-central1",
template: {
spec: {
containers: [{
image: `gcr.io/${gcpProject}/api:${imageTag}`,
ports: [{ containerPort: 8000 }],
envs: [
{ name: "ENV", value: "production" },
],
resources: {
limits: {
memory: "1Gi",
cpu: "1000m",
},
},
}],
containerConcurrency: 80,
},
metadata: {
annotations: {
"autoscaling.knative.dev/maxScale": "10",
"autoscaling.knative.dev/minScale": "0", // Scale to zero
},
},
},
traffics: [{
percent: 100,
latestRevision: true,
}],
});
// Allow public access
const iamPolicy = new gcp.cloudrun.IamMember("public-access", {
service: service.name,
location: service.location,
role: "roles/run.invoker",
member: "allUsers",
});
export const url = service.statuses[0].url;GKE Cluster
import * as pulumi from "@pulumi/pulumi";
import * as gcp from "@pulumi/gcp";
const cluster = new gcp.container.Cluster("gke-cluster", {
location: "us-central1-a",
removeDefaultNodePool: true,
initialNodeCount: 1,
releaseChannel: {
channel: "REGULAR",
},
workloadIdentityConfig: {
workloadPool: `${gcp.config.project}.svc.id.goog`,
},
});
const nodePool = new gcp.container.NodePool("primary-nodes", {
location: cluster.location,
cluster: cluster.name,
nodeCount: 3,
autoscaling: {
minNodeCount: 1,
maxNodeCount: 10,
},
nodeConfig: {
machineType: "e2-medium",
oauthScopes: [
"https://www.googleapis.com/auth/cloud-platform",
],
metadata: {
"disable-legacy-endpoints": "true",
},
},
});
// Generate kubeconfig
export const kubeconfig = pulumi.all([
cluster.name,
cluster.endpoint,
cluster.masterAuth,
]).apply(([name, endpoint, masterAuth]) => {
const context = `gke_${gcp.config.project}_${cluster.location}_${name}`;
return `apiVersion: v1
clusters:
- cluster:
certificate-authority-data: ${masterAuth.clusterCaCertificate}
server: https://${endpoint}
name: ${context}
contexts:
- context:
cluster: ${context}
user: ${context}
name: ${context}
current-context: ${context}
kind: Config
preferences: {}
users:
- name: ${context}
user:
exec:
apiVersion: client.authentication.k8s.io/v1beta1
command: gke-gcloud-auth-plugin
installHint: Install gke-gcloud-auth-plugin
provideClusterInfo: true
`;
});Multi-Cloud Patterns
Multi-Region Deployment
import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";
const regions = ["us-east-1", "eu-west-1", "ap-southeast-1"];
// Deploy Lambda to multiple regions
const lambdas = regions.map(region => {
const provider = new aws.Provider(`provider-${region}`, { region });
const lambda = new aws.lambda.Function(`api-${region}`, {
runtime: "nodejs20.x",
handler: "index.handler",
role: role.arn,
code: new pulumi.asset.FileArchive("./dist"),
}, { provider });
return { region, lambda };
});
export const lambdaUrls = lambdas.map(({ region, lambda }) => ({
region,
arn: lambda.arn,
}));AWS + Cloudflare
import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";
import * as cloudflare from "@pulumi/cloudflare";
// AWS resources
const bucket = new aws.s3.Bucket("assets");
const cdn = new aws.cloudfront.Distribution("cdn", {
// ... CloudFront config
});
// Cloudflare DNS
const zone = cloudflare.getZoneOutput({
name: "example.com",
});
const record = new cloudflare.Record("cdn-record", {
zoneId: zone.id,
name: "assets",
type: "CNAME",
value: cdn.domainName,
proxied: true, // Cloudflare proxy
});
export const assetUrl = pulumi.interpolate`https://assets.example.com`;Component Model
Reusable Component
import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";
import * as awsx from "@pulumi/awsx";
interface WebServiceArgs {
imageTag: string;
environment: pulumi.Input<{ [key: string]: pulumi.Input<string> }>;
cpu: number;
memory: number;
desiredCount: number;
}
export class WebService extends pulumi.ComponentResource {
public readonly url: pulumi.Output<string>;
public readonly cluster: aws.ecs.Cluster;
constructor(name: string, args: WebServiceArgs, opts?: pulumi.ComponentResourceOptions) {
super("custom:WebService", name, {}, opts);
// VPC
const vpc = new awsx.ec2.Vpc(`${name}-vpc`, {
numberOfAvailabilityZones: 2,
}, { parent: this });
// ECS Cluster
this.cluster = new aws.ecs.Cluster(`${name}-cluster`, {
tags: { ManagedBy: "Pulumi" },
}, { parent: this });
// ALB
const alb = new awsx.lb.ApplicationLoadBalancer(`${name}-alb`, {
subnetIds: vpc.publicSubnetIds,
}, { parent: this });
// Fargate Service
const service = new awsx.ecs.FargateService(`${name}-service`, {
cluster: this.cluster.arn,
taskDefinitionArgs: {
container: {
image: `my-repo:${args.imageTag}`,
cpu: args.cpu,
memory: args.memory,
environment: args.environment,
portMappings: [{
containerPort: 3000,
targetGroup: alb.defaultTargetGroup,
}],
},
},
desiredCount: args.desiredCount,
}, { parent: this });
this.url = pulumi.interpolate`http://${alb.loadBalancer.dnsName}`;
this.registerOutputs({
url: this.url,
cluster: this.cluster,
});
}
}Usage:
import { WebService } from "./components/WebService";
const webService = new WebService("my-app", {
imageTag: "v1.2.3",
environment: {
NODE_ENV: "production",
},
cpu: 512,
memory: 1024,
desiredCount: 3,
});
export const appUrl = webService.url;Best Practices
Stack Management
# Create new stack
pulumi stack init production
# Select stack
pulumi stack select staging
# List stacks
pulumi stack ls
# Stack configuration
pulumi config set aws:region us-east-1
pulumi config set --secret dbPassword mySecurePassword
# Preview changes
pulumi preview
# Deploy
pulumi up
# Destroy
pulumi destroySecrets Management
import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";
const config = new pulumi.Config();
const dbPassword = config.requireSecret("dbPassword");
// Store secret in AWS Secrets Manager
const secret = new aws.secretsmanager.Secret("db-password");
const secretVersion = new aws.secretsmanager.SecretVersion("db-password-version", {
secretId: secret.id,
secretString: dbPassword,
});
// Use secret ARN in ECS task
const taskDefinition = new aws.ecs.TaskDefinition("task", {
containerDefinitions: pulumi.jsonStringify([{
name: "app",
image: "my-app:latest",
secrets: [{
name: "DATABASE_PASSWORD",
valueFrom: secretVersion.arn,
}],
}]),
});Tagging Strategy
import * as pulumi from "@pulumi/pulumi";
const config = new pulumi.Config();
const environment = config.require("environment");
const tags = {
Environment: environment,
ManagedBy: "Pulumi",
Project: pulumi.getProject(),
Stack: pulumi.getStack(),
};
// Apply to all resources
const bucket = new aws.s3.Bucket("bucket", {
tags: tags,
});Import Existing Resources
# Import existing S3 bucket
pulumi import aws:s3/bucket:Bucket my-bucket my-existing-bucket-name
# Import existing EC2 instance
pulumi import aws:ec2/instance:Instance my-instance i-1234567890abcdef0Testing (Policy as Code)
import * as policy from "@pulumi/policy";
const policies = new policy.PolicyPack("aws-policies", {
policies: [
{
name: "s3-no-public-read",
description: "Prohibits public read access on S3 buckets",
enforcementLevel: "mandatory",
validateResource: policy.validateResourceOfType(aws.s3.Bucket, (bucket, args, reportViolation) => {
if (bucket.acl === "public-read") {
reportViolation("S3 buckets cannot have public-read ACL");
}
}),
},
],
});#!/usr/bin/env python3
"""
Generate Kubernetes manifests from templates.
This script is executed WITHOUT loading into context (token-free).
Usage:
python scripts/generate_k8s_manifests.py --app-name my-app --replicas 3 --namespace production
Features:
- Generates Deployment, Service, ConfigMap, Ingress
- Configurable replicas, namespace, resources
- Outputs valid YAML to stdout or file
"""
import argparse
import sys
from typing import Dict, Any
def generate_deployment(app_name: str, replicas: int, namespace: str, image: str, port: int, resources: Dict[str, Any]) -> str:
"""Generate Kubernetes Deployment manifest."""
return f"""apiVersion: apps/v1
kind: Deployment
metadata:
name: {app_name}
namespace: {namespace}
labels:
app: {app_name}
spec:
replicas: {replicas}
selector:
matchLabels:
app: {app_name}
template:
metadata:
labels:
app: {app_name}
spec:
containers:
- name: {app_name}
image: {image}
ports:
- containerPort: {port}
name: http
env:
- name: PORT
value: "{port}"
resources:
requests:
cpu: {resources['requests']['cpu']}
memory: {resources['requests']['memory']}
limits:
cpu: {resources['limits']['cpu']}
memory: {resources['limits']['memory']}
livenessProbe:
httpGet:
path: /health
port: http
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
httpGet:
path: /ready
port: http
initialDelaySeconds: 5
periodSeconds: 5
"""
def generate_service(app_name: str, namespace: str, port: int) -> str:
"""Generate Kubernetes Service manifest."""
return f"""apiVersion: v1
kind: Service
metadata:
name: {app_name}
namespace: {namespace}
labels:
app: {app_name}
spec:
type: ClusterIP
ports:
- port: 80
targetPort: {port}
protocol: TCP
name: http
selector:
app: {app_name}
"""
def generate_configmap(app_name: str, namespace: str, environment: str) -> str:
"""Generate Kubernetes ConfigMap manifest."""
return f"""apiVersion: v1
kind: ConfigMap
metadata:
name: {app_name}-config
namespace: {namespace}
data:
environment: {environment}
log_level: info
"""
def generate_ingress(app_name: str, namespace: str, host: str) -> str:
"""Generate Kubernetes Ingress manifest."""
return f"""apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: {app_name}
namespace: {namespace}
annotations:
kubernetes.io/ingress.class: nginx
cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
tls:
- hosts:
- {host}
secretName: {app_name}-tls
rules:
- host: {host}
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: {app_name}
port:
number: 80
"""
def generate_hpa(app_name: str, namespace: str, min_replicas: int, max_replicas: int, target_cpu: int) -> str:
"""Generate Horizontal Pod Autoscaler manifest."""
return f"""apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: {app_name}
namespace: {namespace}
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: {app_name}
minReplicas: {min_replicas}
maxReplicas: {max_replicas}
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: {target_cpu}
"""
def main():
parser = argparse.ArgumentParser(description="Generate Kubernetes manifests")
parser.add_argument("--app-name", required=True, help="Application name")
parser.add_argument("--namespace", default="default", help="Kubernetes namespace")
parser.add_argument("--replicas", type=int, default=2, help="Number of replicas")
parser.add_argument("--image", default="my-app:latest", help="Container image")
parser.add_argument("--port", type=int, default=3000, help="Container port")
parser.add_argument("--cpu-request", default="100m", help="CPU request")
parser.add_argument("--memory-request", default="128Mi", help="Memory request")
parser.add_argument("--cpu-limit", default="500m", help="CPU limit")
parser.add_argument("--memory-limit", default="512Mi", help="Memory limit")
parser.add_argument("--environment", default="production", help="Environment (dev/staging/production)")
parser.add_argument("--host", help="Ingress host (optional)")
parser.add_argument("--enable-hpa", action="store_true", help="Enable Horizontal Pod Autoscaler")
parser.add_argument("--min-replicas", type=int, default=2, help="HPA min replicas")
parser.add_argument("--max-replicas", type=int, default=10, help="HPA max replicas")
parser.add_argument("--target-cpu", type=int, default=70, help="HPA target CPU utilization")
parser.add_argument("--output", help="Output file (default: stdout)")
args = parser.parse_args()
resources = {
"requests": {
"cpu": args.cpu_request,
"memory": args.memory_request,
},
"limits": {
"cpu": args.cpu_limit,
"memory": args.memory_limit,
},
}
manifests = []
# Generate manifests
manifests.append(generate_deployment(
args.app_name,
args.replicas,
args.namespace,
args.image,
args.port,
resources
))
manifests.append(generate_service(args.app_name, args.namespace, args.port))
manifests.append(generate_configmap(args.app_name, args.namespace, args.environment))
if args.host:
manifests.append(generate_ingress(args.app_name, args.namespace, args.host))
if args.enable_hpa:
manifests.append(generate_hpa(
args.app_name,
args.namespace,
args.min_replicas,
args.max_replicas,
args.target_cpu
))
# Join manifests with separator
output = "---\n".join(manifests)
# Write to file or stdout
if args.output:
with open(args.output, "w") as f:
f.write(output)
print(f"✅ Manifests written to {args.output}", file=sys.stderr)
else:
print(output)
if __name__ == "__main__":
main()
Related skills
FAQ
How does it choose a deployment target?
Via a decision tree by workload: complex microservices to Kubernetes, variable traffic to serverless, consistent load to containers, global low-latency to edge.
Which IaC tools does it cover?
Pulumi (TypeScript), OpenTofu (HCL), and SST v3 for serverless TypeScript.