Skip to main content

Deployment Guide

This guide covers deployment strategies, best practices, and production considerations for your Wrist Agent system.

Deployment Methods

Local Deployment

Deploy directly from your development machine using AWS CDK.

Prerequisites:

  • AWS CLI configured with appropriate credentials
  • Node.js 18+ and npm installed
  • Go 1.22+ installed

Steps:

# Clone and setup
git clone https://github.com/Stealinglight/wrist-agent.git
cd wrist-agent

# Install dependencies
cd cdk && npm install && cd ..
cd lambda && go mod tidy && cd ..

# Bootstrap CDK (first time only)
cd cdk
npx cdk bootstrap

# Deploy
npx cdk deploy --require-approval never

# Note the outputs

When to use:

  • Initial setup and testing
  • Development iterations
  • Personal deployments
  • Quick prototyping

GitHub Actions Deployment

Automated deployment using GitHub Actions with OIDC authentication.

Prerequisites:

  • GitHub repository (fork or your own)
  • AWS OIDC provider configured
  • IAM role for GitHub Actions

Setup:

# 1. Create OIDC provider (one-time)
aws iam create-open-id-connect-provider \
--url https://token.actions.githubusercontent.com \
--client-id-list sts.amazonaws.com \
--thumbprint-list 6938fd4d98bab03faadb97b34396831e3780aea1

# 2. Create IAM role for GitHub Actions
cat > github-trust-policy.json << 'EOF'
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Federated": "arn:aws:iam::ACCOUNT_ID:oidc-provider/token.actions.githubusercontent.com"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"token.actions.githubusercontent.com:aud": "sts.amazonaws.com"
},
"StringLike": {
"token.actions.githubusercontent.com:sub": "repo:YOUR_GITHUB_USERNAME/wrist-agent:*"
}
}
}
]
}
EOF

# Replace ACCOUNT_ID and YOUR_GITHUB_USERNAME
aws iam create-role \
--role-name WristAgentGitHubActions \
--assume-role-policy-document file://github-trust-policy.json

# 3. Attach permissions
# Note: PowerUserAccess grants broad permissions. For production deployments,
# create a custom policy with only the required permissions:
# - cloudformation:* (for CDK stack management)
# - lambda:* (for function deployment)
# - iam:GetRole, iam:PassRole (limited IAM access)
# - ssm:GetParameter, ssm:PutParameter (for parameter store)
# - bedrock:InvokeModel (for Bedrock access)
# - logs:CreateLogGroup, logs:PutRetentionPolicy (for CloudWatch)
# For quick setup, you can use PowerUserAccess:
aws iam attach-role-policy \
--role-name WristAgentGitHubActions \
--policy-arn arn:aws:iam::aws:policy/PowerUserAccess

# 4. Configure GitHub secrets
# Repository → Settings → Secrets and variables → Actions
# Add secret: AWS_ROLE_ARN = arn:aws:iam::ACCOUNT_ID:role/WristAgentGitHubActions

Workflow:

# .github/workflows/deploy.yml
name: Deploy to AWS

on:
push:
branches: [main]

permissions:
id-token: write
contents: read

jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: ${{ secrets.AWS_ROLE_ARN }}
aws-region: us-west-2

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'

- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: '1.22'

- name: Deploy CDK
run: |
cd cdk
npm ci
npx cdk bootstrap --require-approval never
npx cdk deploy --require-approval never

When to use:

  • Production deployments
  • Team environments
  • Continuous delivery
  • Automated testing

Environment Configuration

Development Environment

Optimized for fast iteration and debugging.

# .env.development
AWS_REGION=us-west-2
AWS_PROFILE=dev
BEDROCK_MODEL_ID=anthropic.claude-haiku-4-5-20251001-v1:0
CLIENT_TOKEN_PARAM_NAME=/wrist-agent/dev/client-token
LAMBDA_TIMEOUT=60
LAMBDA_MEMORY=512
LOG_LEVEL=DEBUG

Characteristics:

  • Extended timeouts for debugging
  • Debug logging enabled
  • Separate SSM parameter namespace
  • Lower cost priority over performance

Staging Environment

Production-like environment for testing.

# .env.staging
AWS_REGION=us-west-2
AWS_PROFILE=staging
BEDROCK_MODEL_ID=anthropic.claude-haiku-4-5-20251001-v1:0
CLIENT_TOKEN_PARAM_NAME=/wrist-agent/staging/client-token
LAMBDA_TIMEOUT=30
LAMBDA_MEMORY=256
LOG_LEVEL=INFO

Characteristics:

  • Production-equivalent configuration
  • Same region as production
  • Separate authentication tokens
  • Full monitoring enabled

Production Environment

Optimized for reliability and performance.

# .env.production
AWS_REGION=us-west-2
AWS_PROFILE=production
BEDROCK_MODEL_ID=anthropic.claude-haiku-4-5-20251001-v1:0
CLIENT_TOKEN_PARAM_NAME=/wrist-agent/client-token
LAMBDA_TIMEOUT=30
LAMBDA_MEMORY=256
LOG_LEVEL=INFO
ENABLE_XRAY=true

Characteristics:

  • Optimized memory allocation
  • Production timeout limits
  • X-Ray tracing enabled
  • CloudWatch alarms configured

Multi-Region Deployment

Deploy to multiple regions for redundancy or regional requirements.

Primary Region (us-west-2)

export AWS_REGION=us-west-2
cd cdk
npx cdk deploy --context region=us-west-2

Secondary Region (eu-west-1)

export AWS_REGION=eu-west-1
cd cdk
npx cdk bootstrap
npx cdk deploy --context region=eu-west-1

Region Selection Considerations

us-west-2 (Oregon):

  • ✅ Full Bedrock model availability
  • ✅ Lower latency for US West Coast
  • ✅ Typically lower costs

us-east-1 (N. Virginia):

  • ✅ Full Bedrock model availability
  • ✅ Lower latency for US East Coast
  • ✅ Most services available first

eu-west-1 (Ireland):

  • ✅ GDPR compliance
  • ✅ Lower latency for Europe
  • ⚠️ Check Bedrock model availability

Blue-Green Deployment

Zero-downtime deployment strategy.

// cdk/lib/blue-green-stack.ts
import * as cdk from 'aws-cdk-lib';
import * as lambda from 'aws-cdk-lib/aws-lambda';

export class BlueGreenStack extends cdk.Stack {
constructor(scope: cdk.App, id: string, props?: cdk.StackProps) {
super(scope, id, props);

// Blue version (current)
const blueFunction = new lambda.Function(this, 'BlueFunction', {
// ... configuration
});

// Green version (new)
const greenFunction = new lambda.Function(this, 'GreenFunction', {
// ... configuration
});

// Alias for gradual traffic shifting
const alias = new lambda.Alias(this, 'LiveAlias', {
aliasName: 'live',
version: blueFunction.currentVersion,
});

// CodeDeploy for traffic shifting
new codedeploy.LambdaDeploymentGroup(this, 'DeploymentGroup', {
alias: alias,
deploymentConfig: codedeploy.LambdaDeploymentConfig.LINEAR_10PERCENT_EVERY_1MINUTE,
});
}
}

Infrastructure as Code Best Practices

Version Control

# Tag releases
git tag -a v1.0.0 -m "Production release 1.0.0"
git push origin v1.0.0

# Deploy specific version
git checkout v1.0.0
cd cdk
npx cdk deploy

CDK Stack Organization

// cdk/bin/app.ts
import * as cdk from 'aws-cdk-lib';
import { WristAgentStack } from '../lib/wrist-agent-stack';
import { MonitoringStack } from '../lib/monitoring-stack';

const app = new cdk.App();

// Main application stack
const appStack = new WristAgentStack(app, 'WristAgentStack', {
env: {
account: process.env.CDK_DEFAULT_ACCOUNT,
region: process.env.CDK_DEFAULT_REGION,
},
});

// Monitoring stack (separate lifecycle)
new MonitoringStack(app, 'WristAgentMonitoring', {
functionArn: appStack.function.functionArn,
});

Parameter Management

// Use SSM for configuration
import * as ssm from 'aws-cdk-lib/aws-ssm';

const config = {
modelId: ssm.StringParameter.valueFromLookup(
this,
'/wrist-agent/config/model-id'
),
timeout: Number(
ssm.StringParameter.valueFromLookup(
this,
'/wrist-agent/config/timeout'
)
),
};

Monitoring and Observability

CloudWatch Dashboards

import * as cloudwatch from 'aws-cdk-lib/aws-cloudwatch';

const dashboard = new cloudwatch.Dashboard(this, 'WristAgentDashboard', {
dashboardName: 'WristAgent',
});

dashboard.addWidgets(
new cloudwatch.GraphWidget({
title: 'Lambda Invocations',
left: [fn.metricInvocations()],
}),
new cloudwatch.GraphWidget({
title: 'Error Rate',
left: [fn.metricErrors()],
}),
new cloudwatch.GraphWidget({
title: 'Duration',
left: [fn.metricDuration()],
})
);

Alarms

// Error rate alarm
fn.metricErrors().createAlarm(this, 'ErrorAlarm', {
threshold: 5,
evaluationPeriods: 2,
alarmDescription: 'Lambda errors exceed threshold',
});

// Duration alarm
fn.metricDuration().createAlarm(this, 'DurationAlarm', {
threshold: 25000, // 25 seconds
evaluationPeriods: 2,
alarmDescription: 'Lambda duration approaching timeout',
});

X-Ray Tracing

import * as lambda from 'aws-cdk-lib/aws-lambda';

const fn = new lambda.Function(this, 'Handler', {
// ... other config
tracing: lambda.Tracing.ACTIVE,
});

Security Hardening

Network Isolation (Optional)

Deploy Lambda in VPC for enhanced security:

import * as ec2 from 'aws-cdk-lib/aws-ec2';

const vpc = new ec2.Vpc(this, 'VPC', {
maxAzs: 2,
natGateways: 1,
});

const fn = new lambda.Function(this, 'Handler', {
// ... other config
vpc: vpc,
vpcSubnets: { subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS },
});

Secret Rotation

Automate token rotation with Lambda:

import * as events from 'aws-cdk-lib/aws-events';
import * as targets from 'aws-cdk-lib/aws-events-targets';

const rotationFn = new lambda.Function(this, 'TokenRotation', {
runtime: lambda.Runtime.NODEJS_18_X,
handler: 'index.handler',
code: lambda.Code.fromAsset('lambda/rotation'),
});

// Rotate monthly
new events.Rule(this, 'RotationSchedule', {
schedule: events.Schedule.rate(cdk.Duration.days(30)),
targets: [new targets.LambdaFunction(rotationFn)],
});

Resource Policies

// Restrict Lambda Function URL access
import * as iam from 'aws-cdk-lib/aws-iam';

fn.addPermission('FunctionUrlInvoke', {
principal: new iam.AnyPrincipal(),
action: 'lambda:InvokeFunctionUrl',
functionUrlAuthType: lambda.FunctionUrlAuthType.NONE,
});

Cost Optimization

Right-Size Lambda

# Test different memory configurations
for memory in 128 256 512 1024; do
echo "Testing with ${memory}MB memory"
# Update Lambda memory
aws lambda update-function-configuration \
--function-name WristAgentStack-WristAgentHandler \
--memory-size $memory

# Run load test
# Measure duration and cost
done

Reserved Capacity

For consistent usage, consider reserved capacity:

// Add provisioned concurrency during peak hours
import * as applicationautoscaling from 'aws-cdk-lib/aws-applicationautoscaling';

const alias = fn.currentVersion.addAlias('live');

const target = new applicationautoscaling.ScalableTarget(this, 'ScalableTarget', {
serviceNamespace: applicationautoscaling.ServiceNamespace.LAMBDA,
maxCapacity: 10,
minCapacity: 1,
resourceId: `function:${fn.functionName}:${alias.aliasName}`,
scalableDimension: 'lambda:function:ProvisionedConcurrentExecutions',
});

// Scale based on utilization
target.scaleToTrackMetric('ProvisonedConcurrencyTracking', {
targetValue: 0.7,
predefinedMetric: applicationautoscaling.PredefinedMetric.LAMBDA_PROVISIONED_CONCURRENCY_UTILIZATION,
});

Cost Monitoring

# Set up cost alerts
aws cloudwatch put-metric-alarm \
--alarm-name WristAgent-MonthlyCost \
--alarm-description "Alert when monthly costs exceed budget" \
--metric-name EstimatedCharges \
--namespace AWS/Billing \
--statistic Maximum \
--period 86400 \
--threshold 100 \
--comparison-operator GreaterThanThreshold \
--dimensions Name=Currency,Value=USD

Backup and Recovery

Backup Strategy

# Export CDK template
cd cdk
npx cdk synth > backup/template-$(date +%Y%m%d).json

# Backup SSM parameters
aws ssm get-parameter \
--name "/wrist-agent/client-token" \
--with-decryption \
--query 'Parameter.Value' \
--output text > backup/token-$(date +%Y%m%d).txt

# Backup CloudFormation stack
aws cloudformation get-template \
--stack-name WristAgentStack \
--query 'TemplateBody' > backup/stack-$(date +%Y%m%d).json

Disaster Recovery

# Full stack recovery
# 1. Restore from backup
cd wrist-agent/cdk

# 2. Deploy to new region
export AWS_REGION=us-east-1
npx cdk bootstrap
npx cdk deploy

# 3. Restore token
aws ssm put-parameter \
--name "/wrist-agent/client-token" \
--value "$(cat backup/token-YYYYMMDD.txt)" \
--type String

# 4. Update Apple Shortcut with new Function URL

Rollback Procedures

Immediate Rollback

# Rollback to previous version
aws lambda update-function-configuration \
--function-name WristAgentStack-WristAgentHandler \
--revision-id PREVIOUS_REVISION_ID

# Or destroy and redeploy previous version
git checkout v1.0.0
cd cdk
npx cdk deploy --require-approval never

Gradual Rollback

Using Lambda aliases and weighted routing:

// Shift traffic back to previous version
alias.addVersion(previousVersion, 1.0); // 100% to old
alias.addVersion(newVersion, 0.0); // 0% to new

Next Steps