Deploy Safe Settings to AWS Lambda for a serverless, auto-scaling solution with pay-per-execution pricing.
Overview
Safe Settings can be deployed to AWS Lambda using a Docker container approach that provides:
- Docker-based deployment using the official Safe Settings source
- Dual Lambda functions for webhooks and scheduled sync operations
- GitHub Actions CI/CD with automated testing and deployment
- Production-ready architecture with proper error handling and monitoring
- Auto-scaling with serverless infrastructure
- Cost-effective pay-per-execution pricing model
Production-Ready Template
The recommended way to deploy to AWS Lambda is using the SafeSettings-Template:
Quick Start
Click “Use this template” to get started immediately
Modern Architecture
Docker-based deployment with dual Lambda functions
Full CI/CD
GitHub Actions with automated testing and deployment
Smart Routing
Handles both webhooks and scheduled sync operations
Architecture
The Lambda deployment uses a dual function architecture:
Lambda Functions
safe-settings-lambda: Handles GitHub webhook events via Function URL
safe-settings-scheduler: Handles scheduled sync operations via EventBridge
- Shared Docker Image: Both functions use the same container with different entry points
- Smart Handler Routing: Automatically routes events to appropriate handlers
Prerequisites
- AWS Account with ECR and Lambda access
- GitHub repository with Actions enabled
- Node.js 20+ (Latest LTS recommended) for local development
- npm 10+ (comes with Node.js 20+)
- GitHub App created with proper permissions
- AWS CLI configured locally (optional, for manual setup)
Quick Setup
Go to SafeSettings-Template
Click “Use this template” button
Create a new repository in your organization
Clone your new repository locally:
git clone https://github.com/your-org/your-safe-settings-lambda.git
cd your-safe-settings-lambda
Create the required AWS resources:
# Set variables
export AWS_REGION=us-east-1
export AWS_ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)
# Create ECR repository
aws ecr create-repository \
--repository-name safe-settings-lambda \
--region $AWS_REGION
# Create IAM role for Lambda
aws iam create-role \
--role-name lambda-safe-settings-role \
--assume-role-policy-document '{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Principal": {"Service": "lambda.amazonaws.com"},
"Action": "sts:AssumeRole"
}]
}'
# Attach basic Lambda execution policy
aws iam attach-role-policy \
--role-name lambda-safe-settings-role \
--policy-arn arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
Both functions use the same Docker image but different entry points (command).
# Create main webhook function
aws lambda create-function \
--function-name safe-settings-lambda \
--role arn:aws:iam::$AWS_ACCOUNT_ID:role/lambda-safe-settings-role \
--code ImageUri=$AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/safe-settings-lambda:latest \
--package-type Image \
--timeout 30 \
--memory-size 512 \
--image-config '{"Command":["safe-settings-handler.webhooks"]}'
# Create scheduler function
aws lambda create-function \
--function-name safe-settings-scheduler \
--role arn:aws:iam::$AWS_ACCOUNT_ID:role/lambda-safe-settings-role \
--code ImageUri=$AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/safe-settings-lambda:latest \
--package-type Image \
--timeout 60 \
--memory-size 512 \
--image-config '{"Command":["safe-settings-handler.scheduler"]}'
Create a public Function URL for GitHub webhooks:
aws lambda create-function-url-config \
--function-name safe-settings-lambda \
--auth-type NONE \
--cors 'AllowOrigins=["*"],AllowMethods=["POST"]'
# Get the Function URL
aws lambda get-function-url-config \
--function-name safe-settings-lambda \
--query FunctionUrl \
--output text
Use this URL as your GitHub App webhook URL.
Configure in Settings → Secrets and variables → Actions → Variables:
AWS_REGION=us-east-1
AWS_ACCOUNT_ID=123456789012
ECR_REPOSITORY=safe-settings-lambda
LAMBDA_FUNCTION_NAME=safe-settings-lambda
SCHEDULER_FUNCTION_NAME=safe-settings-scheduler
GH_ORG=your-organization
APP_ID=123456
WEBHOOK_SECRET=your-webhook-secret
SAFE_SETTINGS_GITHUB_CLIENT_ID=Iv1.xxx
Configure in Settings → Secrets and variables → Actions → Secrets:
AWS_ACCESS_KEY_ID=AKIA...
AWS_SECRET_ACCESS_KEY=...
PRIVATE_KEY=-----BEGIN RSA PRIVATE KEY-----...
SAFE_SETTINGS_GITHUB_CLIENT_SECRET=...
Never commit secrets to your repository. Always use GitHub Secrets for sensitive data.
Push to the master branch to trigger deployment:
The GitHub Actions workflow will:
Run tests and generate coverage reports
Build the Docker image using multi-stage build
Push to ECR with SHA and latest tags
Update both Lambda functions
Configure environment variables
Container Architecture
The template uses a multi-stage Docker build:
# Stage 1: Copy Safe Settings source
FROM ghcr.io/github/safe-settings:2.1.17 AS source
# Stage 2: Create Lambda runtime
FROM public.ecr.aws/lambda/nodejs:20
# Copy Safe Settings source
COPY --from=source /opt/safe-settings /var/task
# Add Lambda adapter and handler
COPY safe-settings-handler.js ${LAMBDA_TASK_ROOT}/
COPY utils/ ${LAMBDA_TASK_ROOT}/utils/
# Install Probot Lambda adapter
RUN npm install @probot/adapter-aws-lambda-serverless
CMD ["safe-settings-handler.webhooks"]
Handler Logic
The main handler intelligently routes events:
// safe-settings-handler.js
const { createLambdaFunction } = require('@probot/adapter-aws-lambda-serverless')
const appFn = require('./index.js')
// Smart routing
exports.webhooks = async (event, context) => {
// Handle GitHub webhook events
const probot = createLambdaFunction(appFn)
return await probot(event, context)
}
exports.scheduler = async (event, context) => {
// Handle scheduled sync events
if (event.source === 'aws.events' || event.sync === true) {
// Run full sync
return await runFullSync()
}
}
Environment Variables
Set environment variables on both Lambda functions:
# Update webhook function
aws lambda update-function-configuration \
--function-name safe-settings-lambda \
--environment "Variables={
APP_ID=123456,
WEBHOOK_SECRET=your-secret,
PRIVATE_KEY=your-base64-key,
GH_ORG=your-org,
LOG_LEVEL=info,
NODE_ENV=production
}"
# Update scheduler function
aws lambda update-function-configuration \
--function-name safe-settings-scheduler \
--environment "Variables={
APP_ID=123456,
PRIVATE_KEY=your-base64-key,
GH_ORG=your-org,
LOG_LEVEL=info,
NODE_ENV=production
}"
The GitHub Actions workflow automatically sets these environment variables during deployment.
Scheduling Sync Operations
Set up EventBridge to trigger periodic syncs:
# Create rule for every 5 minutes
aws events put-rule \
--name safe-settings-sync-schedule \
--schedule-expression "rate(5 minutes)" \
--state ENABLED
# Or use cron expression for specific times
aws events put-rule \
--name safe-settings-sync-schedule \
--schedule-expression "cron(0 */4 * * ? *)" \
--state ENABLED
aws events put-targets \
--rule safe-settings-sync-schedule \
--targets "Id"="1","Arn"="arn:aws:lambda:$AWS_REGION:$AWS_ACCOUNT_ID:function:safe-settings-scheduler"
Grant EventBridge Permission
aws lambda add-permission \
--function-name safe-settings-scheduler \
--statement-id allow-eventbridge \
--action lambda:InvokeFunction \
--principal events.amazonaws.com \
--source-arn arn:aws:events:$AWS_REGION:$AWS_ACCOUNT_ID:rule/safe-settings-sync-schedule
Manual Sync
Trigger a manual sync operation:
# Invoke scheduler function
aws lambda invoke \
--function-name safe-settings-scheduler \
--payload '{"sync": true}' \
response.json
# View response
cat response.json
Local Development
Test the Lambda function locally:
# Run all tests
npm test
# Run with coverage
npm run test:coverage
# Watch mode for development
npm run test:watch
docker build -t safe-settings-lambda .
Test Locally with Lambda Runtime
docker run -p 9000:8080 \
-e APP_ID=123456 \
-e WEBHOOK_SECRET=test-secret \
-e PRIVATE_KEY="$(cat private-key.pem | base64)" \
safe-settings-lambda
# Send test event
curl -XPOST "http://localhost:9000/2015-03-31/functions/function/invocations" \
-d '{"sync": true}'
Monitoring
CloudWatch Logs
View Lambda execution logs:
# Tail webhook function logs
aws logs tail /aws/lambda/safe-settings-lambda --follow
# Tail scheduler function logs
aws logs tail /aws/lambda/safe-settings-scheduler --follow
# Filter for errors
aws logs filter-log-events \
--log-group-name /aws/lambda/safe-settings-lambda \
--filter-pattern "ERROR"
CloudWatch Metrics
Monitor function metrics:
# Invocation count
aws cloudwatch get-metric-statistics \
--namespace AWS/Lambda \
--metric-name Invocations \
--dimensions Name=FunctionName,Value=safe-settings-lambda \
--start-time 2024-01-01T00:00:00Z \
--end-time 2024-01-02T00:00:00Z \
--period 3600 \
--statistics Sum
# Error rate
aws cloudwatch get-metric-statistics \
--namespace AWS/Lambda \
--metric-name Errors \
--dimensions Name=FunctionName,Value=safe-settings-lambda \
--start-time 2024-01-01T00:00:00Z \
--end-time 2024-01-02T00:00:00Z \
--period 3600 \
--statistics Sum
GitHub Actions
Monitor deployments in the Actions tab:
- Test results and coverage reports
- Docker build logs
- Deployment status
- Security audit results
Alternative: Serverless Framework
For the original Serverless Framework approach, you can use serverless.yml:
org: your-org
app: safe-settings
service: safe-settings
frameworkVersion: '3'
provider:
name: aws
runtime: nodejs20.x
lambdaHashingVersion: 20201221
environment:
APP_ID: ${param:APP_ID}
PRIVATE_KEY: ${param:PRIVATE_KEY}
WEBHOOK_SECRET: ${param:WEBHOOK_SECRET}
NODE_ENV: production
LOG_LEVEL: debug
functions:
webhooks:
handler: handler.webhooks
events:
- httpApi:
path: /api/github/webhooks
method: post
cronHandler:
handler: handler.scheduler
memorySize: 256
timeout: 900 # 15 mins (max)
environment:
NODE_ENV: production
LOG_LEVEL: debug
events:
- schedule:
rate: rate(4 hours)
enabled: true
input: {}
Deploy with Serverless:
npm install -g serverless
serverless deploy \
--param="APP_ID=123456" \
--param="PRIVATE_KEY=$(cat private-key.pem | base64)" \
--param="WEBHOOK_SECRET=your-secret"
Proxy Support
The AWS Lambda handler supports HTTP/HTTPS proxies:
// Set proxy environment variables
process.env.http_proxy = 'http://proxy.example.com:8080'
process.env.https_proxy = 'http://proxy.example.com:8080'
Update Lambda environment variables:
aws lambda update-function-configuration \
--function-name safe-settings-lambda \
--environment "Variables={
http_proxy=http://proxy.example.com:8080,
https_proxy=http://proxy.example.com:8080
}"
Troubleshooting
Common Issues
Module Not Found Errors
Ensure utils/ directory is included in Dockerfile:
COPY utils/ ${LAMBDA_TASK_ROOT}/utils/
Environment Variables
Verify all required secrets are set:
aws lambda get-function-configuration \
--function-name safe-settings-lambda \
--query Environment
Permission Errors
Check IAM roles have necessary permissions:
aws iam get-role --role-name lambda-safe-settings-role
Webhook URL Issues
Verify the Function URL is set correctly:
aws lambda get-function-url-config \
--function-name safe-settings-lambda
Update GitHub App webhook URL to match.
Docker Build Failures
Check base image is accessible:
docker pull ghcr.io/github/safe-settings:2.1.17
Debug Mode
Enable debug logging:
aws lambda update-function-configuration \
--function-name safe-settings-lambda \
--environment "Variables={LOG_LEVEL=debug}"
Handler Routing Issues
Verify handler commands:
# Check webhook function
aws lambda get-function-configuration \
--function-name safe-settings-lambda \
--query ImageConfigResponse.ImageConfig.Command
# Check scheduler function
aws lambda get-function-configuration \
--function-name safe-settings-scheduler \
--query ImageConfigResponse.ImageConfig.Command
Should return:
- Webhook:
["safe-settings-handler.webhooks"]
- Scheduler:
["safe-settings-handler.scheduler"]
Cost Optimization
Adjust Memory and Timeout
Optimize based on your workload:
# Webhook function - quick responses
aws lambda update-function-configuration \
--function-name safe-settings-lambda \
--memory-size 512 \
--timeout 30
# Scheduler function - longer running
aws lambda update-function-configuration \
--function-name safe-settings-scheduler \
--memory-size 512 \
--timeout 60
Monitor Costs
Check AWS Cost Explorer for Lambda costs:
- Invocations count
- Duration in GB-seconds
- Data transfer costs
Next Steps
Template Repository
Get the production-ready template
Configure Settings
Set up your repository settings
Monitoring
Set up CloudWatch monitoring
GitHub Actions
Alternative deployment method