Building a Scheduled AWS Lambda with Docker and DynamoDB: A Complete Guide
Introduction
In this post, we’ll create a fully containerized AWS Lambda function that interacts with DynamoDB, triggered on a schedule by EventBridge. The function will:
We’ll use:
At the end, we’ll have a robust setup that dynamically changes its behavior based on which EventBridge rule triggered the Lambda function.
Prerequisites
Region Choice:
We’ll use us-east-1 consistently. Ensure you select us-east-1 in the AWS Console when verifying or editing resources.
Step 1: Create the “Ted” IAM User
We are going to create a fictitious user in AWS Identity Account Manager (IAM) and do everything under the Ted user account to highlight assigning users in AWS. However, for simplicity, we are going to assign full admin access for Ted but in the real world you would practice assigning least privledges for best practices. That subject we'll cover in a future post.
1. Sign in to AWS Management Console as admin.
2. Go to IAM > Users > Add user:
3. Save Ted’s credentials and log out. Log back in as Ted. Open AWS CloudShell (icon in the top right of AWS Console or open a browser in incognito mode).
For most of this exercize we will use AWS CloudShell.
Step 2: Set Up the Lambda Execution Role
cat > lambda-execution-trust-policy.json <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": { "Service": "lambda.amazonaws.com" },
"Action": "sts:AssumeRole"
}
]
}
EOF
aws iam create-role \
--role-name MyLambdaExecutionRole \
--assume-role-policy-document file://lambda-execution-trust-policy.json \
--region us-east-1
aws iam attach-role-policy \
--role-name MyLambdaExecutionRole \
--policy-arn arn:aws:iam::aws:policy/AmazonDynamoDBFullAccess \
--region us-east-1
aws iam attach-role-policy \
--role-name MyLambdaExecutionRole \
--policy-arn arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole \
--region us-east-1
Step 3: Create the DynamoDB Table
aws dynamodb create-table \
--table-name MyDockerLambdaTable \
--attribute-definitions AttributeName=id,AttributeType=S \
--key-schema AttributeName=id,KeyType=HASH \
--provisioned-throughput ReadCapacityUnits=5,WriteCapacityUnits=5 \
--region us-east-1
Step 4: Create the ECR Repository
aws ecr create-repository \
--repository-name my-docker-lambda \
--region us-east-1
Make a note of the repository URI returned.
Step 5: Set Up GitHub OIDC for CI/CD
Create OIDC Provider (if not done):
aws iam create-open-id-connect-provider \
--url https://token.actions.githubusercontent.com \
--thumbprint-list xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx \
--client-id-list sts.amazonaws.com \
--region us-east-1
To obtain your --thumbprint-list, Run the following OpenSSL command in your terminal:
echo | openssl s_client -servername token.actions.githubusercontent.com -connect token.actions.githubusercontent.com:443 2>/dev/null | openssl x509 -fingerprint -noout -sha1
Expected output:
SHA1 Fingerprint=69:38:FD:4D:98:BA:B0:3F:AA:DB:97:B3:43:96:83:1E:37:80:AE:A1
Remove the colons (:) from the fingerprint to use it as --thumbprint-list:
6938fd4d98bab03faadb97b34396831e3780aea1
Replaces the xxx's from the aws commands from earlier with your --thumbprint-list
Create Trust Policy for GitHub OIDC Role: Replace <ACCOUNT_ID> with your AWS Account ID and YourGitHubOrg/YourRepo with your repo:
cat > github-oidc-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:sub": "repo:YourGitHubOrg/YourRepo:ref:refs/heads/main"
}
}
}
]
}
EOF
aws iam create-role \
--role-name YourGitHubOIDCDeployRole \
--assume-role-policy-document file://github-oidc-trust-policy.json \
--region us-east-1
领英推荐
Attach a Policy for ECR & Lambda Updates:
cat > github-oidc-policy.json <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"ecr:GetAuthorizationToken",
"ecr:BatchCheckLayerAvailability",
"ecr:InitiateLayerUpload",
"ecr:UploadLayerPart",
"ecr:CompleteLayerUpload",
"ecr:PutImage",
"lambda:UpdateFunctionCode"
],
"Resource": "*"
},
{
"Effect": "Allow",
"Action": "iam:PassRole",
"Resource": "arn:aws:iam::<ACCOUNT_ID>:role/MyLambdaExecutionRole"
}
]
}
EOF
aws iam put-role-policy \
--role-name YourGitHubOIDCDeployRole \
--policy-name GitHubOIDCPolicy \
--policy-document file://github-oidc-policy.json \
--region us-east-1
Step 6: The Lambda Code and Dockerfile
We’ll modify the code to determine its action (add or clear) based on the EventBridge rule ARN in event.resources. The 5-minute rule triggers “add,” the 20-minute rule triggers “clear.”
Update <REGION> and <ACCOUNT_ID> before use.
index.js:
const AWS = require('aws-sdk');
const dynamo = new AWS.DynamoDB.DocumentClient();
const TABLE_NAME = process.env.TABLE_NAME || 'MyDockerLambdaTable';
// Replace with your actual region and account ID
const REGION = 'us-east-1';
const ACCOUNT_ID = '<ACCOUNT_ID>';
const FIVE_MINUTE_RULE_ARN = `arn:aws:events:${REGION}:${ACCOUNT_ID}:rule/MyFiveMinuteRule`;
const TWENTY_MINUTE_RULE_ARN = `arn:aws:events:${REGION}:${ACCOUNT_ID}:rule/MyTwentyMinuteRule`;
exports.handler = async (event) => {
console.log('Received event:', JSON.stringify(event, null, 2));
let action = 'add'; // default action
if (Array.isArray(event.resources)) {
if (event.resources.includes(TWENTY_MINUTE_RULE_ARN)) {
action = 'clear';
} else if (event.resources.includes(FIVE_MINUTE_RULE_ARN)) {
action = 'add';
}
}
console.log(`Action determined: ${action}`);
if (action === 'add') {
const now = Date.now();
const newItem = { id: `item-${now}`, timestamp: now };
console.log('Adding a new item:', newItem);
await dynamo.put({ TableName: TABLE_NAME, Item: newItem }).promise();
console.log('Item added successfully.');
const data = await dynamo.scan({ TableName: TABLE_NAME }).promise();
console.log('Current items in DynamoDB:', data.Items || []);
return { statusCode: 200, body: 'Items added/read successfully.' };
} else if (action === 'clear') {
console.log('Clearing table...');
const data = await dynamo.scan({ TableName: TABLE_NAME }).promise();
const items = data.Items || [];
console.log(`Found ${items.length} items to delete.`);
for (let item of items) {
await dynamo.delete({ TableName: TABLE_NAME, Key: { id: item.id } }).promise();
}
console.log('Table cleared. Now empty.');
return { statusCode: 200, body: 'Table cleared.' };
} else {
console.error(`Unknown action: ${action}`);
return { statusCode: 400, body: 'Unknown action.' };
}
};
package.json:
{
"name": "iamlambda",
"version": "1.0.0",
"dependencies": {
"aws-sdk": "^2.1000.0"
}
}
Dockerfile:
FROM public.ecr.aws/lambda/nodejs:18
# Copy code
COPY index.js package*.json ./
# Install dependencies
RUN npm install
CMD [ "index.handler" ]
Push these files to your GitHub repository’s main branch.
Step 7: Create the Lambda Function Initially
aws lambda create-function \
--function-name MyDockerLambdaFunction \
--package-type Image \
--code ImageUri=<ACCOUNT_ID>.dkr.ecr.us-east-1.amazonaws.com/my-docker-lambda:latest \
--role arn:aws:iam::<ACCOUNT_ID>:role/MyLambdaExecutionRole \
--environment Variables="{TABLE_NAME=MyDockerLambdaTable}" \
--region us-east-1
If the image isn’t pushed yet, run the GitHub Actions workflow after setting it up, then re-run this command.
Step 8: GitHub Actions Workflow
Create .github/workflows/deploy.yml:
name: Deploy Lambda
on:
push:
branches: [ main ]
jobs:
build-and-deploy:
runs-on: ubuntu-latest
permissions:
id-token: write
contents: read
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Configure AWS credentials via OIDC
uses: aws-actions/configure-aws-credentials@v2
with:
role-to-assume: arn:aws:iam::<ACCOUNT_ID>:role/YourGitHubOIDCDeployRole
aws-region: us-east-1
- name: Build Docker image
run: docker build -t my-docker-lambda:latest .
- name: Login to ECR
run: |
aws ecr get-login-password --region us-east-1 \
| docker login --username AWS --password-stdin <ACCOUNT_ID>.dkr.ecr.us-east-1.amazonaws.com
- name: Tag Docker image
run: docker tag my-docker-lambda:latest <ACCOUNT_ID>.dkr.ecr.us-east-1.amazonaws.com/my-docker-lambda:latest
- name: Push Docker image
run: docker push <ACCOUNT_ID>.dkr.ecr.us-east-1.amazonaws.com/my-docker-lambda:latest
- name: Update Lambda to use new container image
run: |
aws lambda update-function-code \
--function-name MyDockerLambdaFunction \
--image-uri <ACCOUNT_ID>.dkr.ecr.us-east-1.amazonaws.com/my-docker-lambda:latest
Push this to main. The workflow will build, push the image, and update the Lambda.
Step 9: Set Up EventBridge Rules in AWS Console
We need two rules:
MyFiveMinuteRule (every 5 minutes)
This rule’s ARN will match our code and result in action = add.
MyTwentyMinuteRule (every 20 minutes) Repeat the process just like the 5 minute rule:
This rule’s ARN will cause action = clear in the code.
No input transformers are needed here since we’re distinguishing based on resources array in the event.
Step 10: Verification
Check DynamoDB table to see the cycle of adding and clearing.
Step 11: Cleanup
When finished:
aws lambda delete-function --function-name MyDockerLambdaFunction --region us-east-1
Delete the DynamoDB table:
aws dynamodb delete-table --table-name MyDockerLambdaTable --region us-east-1
Delete the ECR repository:
aws ecr delete-repository --repository-name my-docker-lambda --force --region us-east-1
Detach policies and delete IAM roles:
aws iam detach-role-policy --role-name MyLambdaExecutionRole --policy-arn arn:aws:iam::aws:policy/AmazonDynamoDBFullAccess
aws iam detach-role-policy --role-name MyLambdaExecutionRole --policy-arn arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
aws iam delete-role --role-name MyLambdaExecutionRole
aws iam delete-role-policy --role-name YourGitHubOIDCDeployRole --policy-name GitHubOIDCPolicy || true
aws iam delete-role --role-name YourGitHubOIDCDeployRole
Go back into your own accuont and delete Ted’s user (remove login profile and policies first if any):
aws iam delete-login-profile --user-name Ted
aws iam delete-user --user-name Ted
If the above commands do not work, you will have to go into your main aws account and manually delete Ted's policies and then delete the Ted User from your users in IAM.
Conclusion
In this final, working solution, we:
By following this guide, you have a robust, scheduled, containerized Lambda solution that integrates DynamoDB, AWS CI/CD best practices, and dynamic behavior based on EventBridge triggers.