Build an Event-Driven Software Factory
Wire S3, EventBridge, Lambda, and CodeBuild into a serverless build pipeline.
Introduction
30 Second Summary
Think about a factory assembly line. The moment raw materials arrive at the loading dock, machines start processing them automatically.
In this project, you will build that same kind of reactive system for software on AWS. You will wire together five serverless services into an event-driven pipeline that detects code uploads and kicks off a build with zero manual intervention.
What You'll Build
You will upload a .zip of your Node.js source code to an S3 bucket and watch a build fire off within seconds, triggered entirely by the upload.
By the end of this project, you'll have:
- A fully automated build pipeline where uploading code to S3 kicks off a CodeBuild job within seconds.
- The ability to trace the full event chain in real time, from S3 events flowing through EventBridge to a Lambda function to CodeBuild.
- Hands-on experience debugging an IAM permissions failure and fixing it, a core skill for production AWS work.
- Secret Mission: Wire up SNS email notifications so you know the moment a build succeeds or fails.
Do I need anything before starting?
You will need an existing AWS account and a Node.js project on your local machine. Everything else, including the AWS CLI, is set up in Step 1.
Before We Start
Before we start building, let's get clear on what you're creating and why each piece of the pipeline matters. This is your moment to commit to the project's goal before any hands-on work begins.
Install the AWS CLI and Configure Credentials
Your software factory runs entirely on AWS. Right now, you have no way to interact with it from your terminal.
The AWS CLI is the command-line tool that controls every AWS service from your terminal. In this step, you'll install it, connect it to your AWS account, and make sure the connection works.
In this step, get ready to:
- Install the AWS CLI.
- Configure your AWS credentials.
- Verify the connection to your AWS account.
Install the AWS CLI on macOS
The AWS CLI lets you create and manage AWS resources from your terminal instead of clicking through the console. Let's check if it's already installed.
- Press Cmd+Space to open your search bar.
- Type Terminal and press Enter to open it.
- Check if the AWS CLI is already installed by running this command:
aws --version
✔️ I see a version number
You should see something like aws-cli/2.x.x in the output. The AWS CLI is ready to go. Skip ahead to the next substep.
ⓧ Command not found
No problem. You'll download and install the AWS CLI now.
- The installer uses sudo, which will prompt for your Mac's password. Type it and press Enter (the characters won't appear as you type).
- Download and install the AWS CLI by running these commands:
curl "https://awscli.amazonaws.com/AWSCLIV2.pkg" -o "AWSCLIV2.pkg"
sudo installer -pkg AWSCLIV2.pkg -target /
- Verify the installation succeeded by running:
aws --version
You should see a version number starting with aws-cli/2. That confirms the install worked.
Still seeing command not found?
Close your Terminal window and open a fresh one, then run aws --version again. Some installs require a new shell session to pick up the updated path.
If the issue persists, help me troubleshoot the AWS CLI installation on macOS.
Configure your AWS credentials
The AWS CLI needs credentials to prove who you are when it talks to AWS. These credentials come from an IAM user in your AWS account.
Where do I get access keys?
You need an IAM user with AdministratorAccess permissions. If you already have one with access keys, skip to the aws configure command below.
To create access keys for an existing IAM user:
- Sign in to the AWS Management Console and open the IAM console at https://console.aws.amazon.com/iam/.
- In the navigation pane, choose Users, then select your IAM user.
- Select the Security credentials tab, then under Access keys, choose Create access key.
- Choose Command Line Interface (CLI), then click Next.
- Click Create access key, then choose Download .csv file to save your credentials securely.
Keep this file safe. You can only view the secret access key at the moment of creation.
- The aws configure command will prompt you for four values, one at a time. Start it by running:
aws configure
- When prompted for AWS Access Key ID, paste the access key ID from your downloaded CSV file.
- When prompted for AWS Secret Access Key, paste the secret access key from your downloaded CSV file.
- When prompted for Default region name, enter us-east-1.
- When prompted for Default output format, enter json.
What does aws configure do?
This command stores your credentials in a file at ~/.aws/credentials and your region and output preferences in ~/.aws/config. Every aws command you run from now on uses these settings automatically.
Verify the connection to your AWS account
Everything is configured. Before you run the next command, think about what you expect to see if the CLI is properly connected to your AWS account.
- Test the connection by running this command:
aws sts get-caller-identity
You should see JSON output with three fields: your Account ID, your Arn (the IAM user's Amazon Resource Name), and your UserId.
What should I see?
The output looks something like this:
{
"UserId": "AIDASAMPLEUSERID",
"Account": "123456789012",
"Arn": "arn:aws:iam::123456789012:user/YourUserName"
}
Your values will be different. The important thing is that you see your own account number and IAM user name, not an error.
🙋♀️ Seeing an error instead of JSON?
- If you see a credentials error, double check that you pasted the correct access key ID and secret access key. Run aws configure again to re-enter them.
- If you see a region error, run aws configure again and make sure you entered us-east-1 as the default region.
If neither fix works, help me debug my AWS CLI connection.
Your terminal is now connected to AWS. Next up, you'll create an S3 bucket and upload your source code so the pipeline has something to build.
Create the S3 Source Bucket and Prepare Your Code
Your AWS CLI is connected and ready to go. Now you need somewhere for your source code to land when it enters the pipeline.
Every software factory needs a loading dock. S3 is that loading dock for your pipeline. In this step, you will create an S3 bucket, package your Node.js project with a build configuration file, and upload it so the rest of the pipeline has something to work with.
In this step, get ready to:
- Create an S3 bucket with a globally unique name.
- Add a buildspec.yml build configuration file to your project and zip it up.
- Upload the zipped source code to S3.
Create an S3 bucket
S3 bucket names must be globally unique across all of AWS. Two people cannot have a bucket with the same name. You will include your initials and a random number to keep the name unique.
- Create your S3 source bucket by running this command:
aws s3 mb s3://[[BUCKET_NAME="software-factory-source-yourname-123"]] --region us-east-1
What does this command do?
The mb subcommand stands for "make bucket". It creates a new S3 bucket in the region you specify.
Replace the bucket name with something unique to you. Use only lowercase letters, numbers, and hyphens. For example, software-factory-source-jd-4829.
You should see a confirmation like make_bucket: s3://software-factory-source-jd-4829 printed in your terminal.
Seeing a BucketAlreadyExists error?
This means someone else already owns a bucket with that exact name. Pick a more unique name by adding extra numbers or changing your initials, then run the command again.
Help me fix my S3 bucket creation error.
Add a buildspec.yml and package your code
CodeBuild needs a file called buildspec.yml to know how to build your project. This file defines the phases that run during a build: installing dependencies, running the build, and reporting results.
- In your terminal, navigate to your Node.js project directory by running:
cd [[PROJECT_DIR="path/to/your-node-project"]]
- Check whether your project already has a package.json by running:
ls package.json
✔️ I see package.json
Your project already has a package.json. You are ready to create the buildspec file.
ⓧ No such file or directory
Your project needs a package.json so that npm install works during the build.
- Create a default package.json by running:
npm init -y
You should see a confirmation that package.json was created with default values.
- Create a new file named buildspec.yml in the root of your project directory by running:
touch buildspec.yml
- Confirm the file was created by running:
ls buildspec.yml
You should see buildspec.yml listed in the output.
- Open buildspec.yml in your preferred text editor and paste the following code:
version: 0.2
phases:
install:
commands:
- echo "Installing dependencies..."
- node --version
- npm install
build:
commands:
- echo "Build started on $(date)"
- ls -la
post_build:
commands:
- echo "Build completed successfully on $(date)"
What does this buildspec do?
- install phase: prints the Node.js version and runs npm install to download your project's dependencies.
- build phase: logs the build start time and lists all files in the project directory.
- post_build phase: logs a success message with the completion time.
CodeBuild runs these phases in order. If any command fails, the build stops and reports an error.
- Save buildspec.yml.
✔️ Awesome, I've got everything!
Great. Make sure you have saved buildspec.yml in the root of your project directory, right next to package.json.
ⓧ I'd like to double check the full code
Here is the complete buildspec.yml file. Make sure yours matches exactly:
version: 0.2
phases:
install:
commands:
- echo "Installing dependencies..."
- node --version
- npm install
build:
commands:
- echo "Build started on $(date)"
- ls -la
post_build:
commands:
- echo "Build completed successfully on $(date)"
Now you need to package the entire project into a single .zip file that CodeBuild can download from S3.
- Zip your project directory into source.zip by running:
zip -r source.zip . -x "node_modules/*" ".git/*"
What does this command do?
The -r flag tells zip to include all files and subdirectories recursively. The -x flag excludes the node_modules and .git directories. CodeBuild will install dependencies fresh from your package.json.
- Confirm source.zip was created by running:
ls source.zip
You should see source.zip listed in the output.
Command not found for zip?
The zip utility comes pre-installed on macOS. If you see this error, try installing it with Homebrew by running brew install zip.
Help me fix my zip command error.
Upload the code to S3
Your code is packaged and ready. Now you will upload it to the S3 bucket so the rest of the pipeline can access it.
- Upload source.zip to your bucket by running:
aws s3 cp source.zip s3://[[BUCKET_NAME="software-factory-source-yourname-123"]]/source.zip
What does this command do?
The cp subcommand copies a local file to S3. The first argument is the local file path. The second argument is the S3 destination, made up of your bucket name and the object key (source.zip).
Before you verify, what do you think you will see when you list the contents of your bucket?
- Verify the upload by listing your bucket contents:
aws s3 ls s3://[[BUCKET_NAME="software-factory-source-yourname-123"]]/
You should see a line showing the upload date, the file size in bytes, and source.zip listed as an object in your bucket.
Not seeing source.zip in the bucket?
- Make sure the bucket name in your aws s3 ls command matches the name you used in the aws s3 mb command exactly.
- Check that the aws s3 cp command completed without errors. If it showed an "Access Denied" error, check that your AWS CLI credentials have S3 permissions.
- Include the trailing / at the end of the S3 URI to list bucket contents instead of matching bucket names.
Help me troubleshoot why my S3 upload is not showing up.
Your source code is sitting in S3, waiting to be built. Next up, you will create a CodeBuild project and prove the build logic works by running it manually.
Create a CodeBuild Project and Run Your First Build
Your code is sitting in S3, zipped and ready. But nothing knows how to build it.
In this step, you create a CodeBuild project that acts as the factory worker. It pulls your source.zip from S3, runs the commands in your buildspec.yml, and reports whether the build passed or failed. Running a build manually first proves the build logic works before you automate the trigger.
In this step, get ready to:
- Create a CodeBuild project that reads source code from your S3 bucket.
- Start a manual build and watch the phases progress.
- Inspect the build logs to confirm your buildspec ran successfully.
Create a CodeBuild project in the console
A build project tells CodeBuild where to find your source code, which build environment to use, and which commands to run. You configure one in the CodeBuild console.
- Open the AWS CodeBuild console at https://console.aws.amazon.com/codesuite/codebuild/home.
- Make sure you are in the us-east-1 region by checking the region selector in the top-right corner of the console.
- In the navigation pane on the left, expand Build, then choose Build projects.
- Click Create build project.
What is a build project?
A build project is the job description for your factory worker. It tells CodeBuild everything it needs to run a build: where to get the source code, which operating system and runtime to use, and where to put the output.
Now fill in the project settings. Start with the project name and source configuration.
- In the Project configuration section, for Project name, enter software-factory-build.
- Scroll down to the Source section.
- For Source provider, choose Amazon S3.
- For Bucket, select your bucket (your unique bucket name).
- For S3 object key, enter source.zip.
Next, configure the build environment. This is the machine that runs your build commands.
- Scroll down to the Environment section.
- For Environment image, leave Managed image selected.
- For Operating system, choose Amazon Linux.
- For Runtime(s), choose Standard.
- For Image, select the latest image available in the dropdown.
- For Image version, select Always use the latest image for this runtime version.
What is a managed image?
A managed image is a pre-built Docker container maintained by AWS. It comes with common runtimes and tools already installed, so you do not have to build your own environment from scratch.
The Amazon Linux Standard image includes Node.js, which is exactly what your buildspec needs.
Now configure the service role, buildspec, and artifacts.
- In the Service role section, leave New service role selected and keep the auto-generated Role name unchanged.
- In the Buildspec section, leave Use a buildspec file selected.
- In the Artifacts section, for Type, choose No artifacts.
- Click Create build project at the bottom of the page.
Why no artifacts?
Artifacts are the output files from a build, like a compiled binary or a packaged zip. For this project, the goal is to prove the event-driven pipeline works, not to produce a deployable package. Build logs are enough to verify success.
You should now see the project details page for software-factory-build.
Don't see your project?
Double-check that you are in the us-east-1 region using the region selector in the top-right corner. If you created the project in a different region, it will not appear here.
Help me find my CodeBuild project.
Start the build manually
Your project is configured but has never run. Starting a build manually confirms that CodeBuild can pull your source from S3 and execute the buildspec commands.
- On the project details page for software-factory-build, click Start build.
- On the Build configuration page, accept the defaults and click Start build again.
The build starts. You can watch the build phases progress in real time on the build details page.
What are build phases?
CodeBuild executes your build in a series of phases: DOWNLOAD_SOURCE, INSTALL, PRE_BUILD, BUILD, and POST_BUILD. Each phase maps to a section of your buildspec.yml.
You can see each phase's status and duration in the build details view.
Wait for the build to complete. This typically takes one to two minutes.
Build stuck in QUEUED?
CodeBuild provisions a fresh container for each build. If the build stays in QUEUED or PROVISIONING for more than a few minutes, wait a bit longer. First builds can take slightly longer as the environment is being set up.
Help me debug my stuck CodeBuild build.
Inspect the build logs
The build should now show a status. Before you check the logs, take a moment to predict the outcome.
Based on the commands in your buildspec.yml, what output do you expect to see in the build logs?
- On the build details page, click the Build logs tab to view the full log output.
The build status should show SUCCEEDED. In the logs, you should see the echo statements from your buildspec: the Installing dependencies... message, the node --version output showing the installed Node.js version, and the Build completed successfully message at the end.
What do these logs tell you?
Each line in the log corresponds to a command in your buildspec.yml. The node --version output confirms the managed image includes Node.js. The npm install command installed any dependencies from your package.json.
The ls -la output shows all the files CodeBuild extracted from source.zip. This proves CodeBuild successfully downloaded and unpacked your code from S3.
Build status shows FAILED?
Check the build logs for the specific phase that failed. Common causes include:
- The S3 object key does not match the file name you uploaded. Confirm it is source.zip (case-sensitive).
- The buildspec.yml was not at the root of your zip file. Re-zip your project so that buildspec.yml is at the top level, not nested inside a subfolder.
- The bucket is in a different region than the CodeBuild project. Both must be in us-east-1.
Help me debug my failed CodeBuild build.
Your factory worker is proven. CodeBuild can pull your code from S3 and build it successfully. But right now, you have to press Start build by hand every time. Next up, you wire up an event-driven trigger so builds start automatically the instant new code lands in S3.
Wire Up the Event-Driven Trigger
In the last step, you proved that CodeBuild can successfully build your Node.js app from S3. The buildspec works, the logs look clean, and the build status reads SUCCEEDED.
There is one problem. You had to press Start build by hand. A real software factory reacts the instant new code arrives. In this step, you will connect S3 to EventBridge to Lambda to CodeBuild so uploads trigger builds automatically.
In this step, get ready to:
- Enable EventBridge notifications on your S3 bucket so uploads generate events.
- Create a Lambda function that calls CodeBuild's start_build API.
- Create an EventBridge rule that connects S3 upload events to your Lambda function.
Enable EventBridge notifications on the S3 bucket
Before EventBridge can react to uploads, the S3 bucket needs to send events to the default event bus. By default, S3 buckets do not send events to EventBridge.
- Open the S3 console and click on your software-factory-source-your-bucket-name bucket.
- Click the Properties tab.
- Scroll down to the Event notifications section.
- Find the Amazon EventBridge subsection and click Edit.
- Set Send notifications to Amazon EventBridge for all events in this bucket to On.
- Click Save changes.
You should see the Amazon EventBridge section now shows On under the Properties tab.
Why does this take a few minutes?
After enabling EventBridge, it takes around five minutes for the changes to take effect. S3 needs to configure the internal plumbing that routes events to the default event bus.
You can use this time to create the Lambda function in the next substep. By the time you test the full chain, EventBridge will be ready.
🙋♀️ Don't see the Amazon EventBridge section?
Make sure you are on the Properties tab, not the Permissions or Objects tab. The EventBridge toggle is inside the Event notifications section, which is near the bottom of the Properties page.
help me find the EventBridge notifications setting on my S3 bucket
Create a Lambda function that starts CodeBuild
Your Lambda function is a small piece of glue code. It receives the S3 event from EventBridge, extracts the bucket and object details, and calls CodeBuild's start_build API.
Why Python for the Lambda function?
The Lambda function is written in Python because boto3 (the AWS SDK for Python) is pre-installed in the Lambda Python runtime. No packaging or dependency bundling needed.
Your Node.js app is what CodeBuild builds. The Lambda is just the messenger that tells CodeBuild to start.
- Open the Lambda console at https://console.aws.amazon.com/lambda/home#/functions.
- Click Create function.
- Select Author from scratch.
- In the Basic information pane, enter software-factory-trigger for Function name.
- For Runtime, choose Python 3.12.
- Leave Architecture set to x86_64.
- Leave the Permissions section at its defaults. Lambda will create a default execution role with CloudWatch Logs permissions only.
- Click Create function.
You should see a success banner and the function's code editor open in the Code tab.
- In the code editor, click the lambda_function.py tab.
- Select all the existing code and replace it with the following:
import json
import boto3
codebuild = boto3.client('codebuild')
def lambda_handler(event, context):
print("Received event:", json.dumps(event))
bucket = event['detail']['bucket']['name']
key = event['detail']['object']['key']
print(f"New object uploaded: s3://{bucket}/{key}")
What does this code do?
- boto3.client('codebuild') creates a client for the CodeBuild API. This lets your Lambda call CodeBuild operations.
- event['detail']['bucket']['name'] extracts the bucket name from the EventBridge event. EventBridge wraps S3 events in a standard envelope with a detail field.
- The two print() statements log the raw event and the uploaded file path to CloudWatch Logs. These logs are how you will verify the chain is working.
- Now add the second half of the function directly below the code you just pasted:
response = codebuild.start_build(
projectName='software-factory-build'
)
build_id = response['build']['id']
print(f"Started CodeBuild build: {build_id}")
return {
'statusCode': 200,
'body': json.dumps({
'message': 'Build started successfully',
'buildId': build_id
})
}
What does this code do?
- codebuild.start_build(projectName='software-factory-build') calls the CodeBuild API to kick off a build for your project. This is the same action as clicking Start build in the console, but triggered by code.
- response['build']['id'] grabs the unique build ID from the API response so you can track which build was started.
- The function returns a JSON response with the build ID. This is good practice for Lambda functions so callers can see the result.
- Click Deploy in the code editor to save your function code.
You should see a banner that says the function was updated successfully.
✔️ Awesome, I've got everything!
Great. Make sure you saved by clicking Deploy and seeing the success banner.
ⓧ I'd like to double check the full code
Your complete lambda_function.py should look exactly like this:
import json
import boto3
codebuild = boto3.client('codebuild')
def lambda_handler(event, context):
print("Received event:", json.dumps(event))
bucket = event['detail']['bucket']['name']
key = event['detail']['object']['key']
print(f"New object uploaded: s3://{bucket}/{key}")
response = codebuild.start_build(
projectName='software-factory-build'
)
build_id = response['build']['id']
print(f"Started CodeBuild build: {build_id}")
return {
'statusCode': 200,
'body': json.dumps({
'message': 'Build started successfully',
'buildId': build_id
})
}
Function code not saving?
Make sure you clicked Deploy (not just Test). The Deploy button is in the code editor toolbar. If you see "Changes not deployed" above the editor, your code has not been saved yet.
help me deploy my Lambda function code
Create an EventBridge rule targeting the Lambda
You now have a Lambda function ready to start builds and an S3 bucket sending events to EventBridge. The missing piece is a rule that connects them. The rule tells EventBridge which events to watch for and where to send them.
- Open the Amazon EventBridge console at https://console.aws.amazon.com/events/.
- In the navigation pane, click Rules.
- Click Create rule.
Which builder should I use?
EventBridge offers two rule builders. The Advanced Builder lets you paste a JSON event pattern directly. The Enhanced Builder provides a visual drag-and-drop canvas. Either works. These instructions use the Advanced Builder because you will paste a custom JSON pattern.
- If prompted, choose Advanced Builder.
- Enter software-factory-s3-trigger for the rule Name.
- For Event bus, make sure default is selected. AWS service events always go to the default event bus.
- For Rule type, choose Rule with an event pattern.
- Click Next.
Now you need to define the event pattern. This tells EventBridge exactly which events should trigger your Lambda.
- For Creation method, choose Custom pattern (JSON editor).
- Paste the following event pattern into the Event pattern editor, replacing the bucket name with your actual bucket name:
{
"source": ["aws.s3"],
"detail-type": ["Object Created"],
"detail": {
"bucket": {
"name": ["[[S3_BUCKET=\"software-factory-source-your-unique-suffix\"]]"]
}
}
}
What does this event pattern do?
- "source": ["aws.s3"] filters for events coming from the S3 service only.
- "detail-type": ["Object Created"] matches only when a new object is uploaded. It ignores deletions, access events, and other activity.
- "detail.bucket.name" scopes the rule to your specific bucket. Without this, the rule would fire for uploads to any bucket in your account.
- Click Next to proceed to the targets page.
- For Target types, choose AWS service.
- For Select a target, choose Lambda function from the dropdown.
- For Function, select software-factory-trigger.
- Click Next through any remaining configuration pages.
- Review the rule details and click Create rule.
You should see your new rule software-factory-s3-trigger listed in the EventBridge Rules page with status Enabled.
Rule not showing as Enabled?
Double-check that you selected default for the event bus. If you accidentally created the rule on a custom event bus, S3 events will not reach it.
help me create my EventBridge rule for S3 events
Time to test the full event chain. Before you run this, think about what you expect to happen. The Lambda function's default execution role only has CloudWatch Logs permissions. Do you think the build will start successfully, or will something go wrong?
- Re-upload source.zip to your S3 bucket by running this command:
aws s3 cp source.zip s3://[[S3_BUCKET="software-factory-source-your-unique-suffix"]]/source.zip
- Wait about 30 seconds for the event chain to fire.
- Open the CloudWatch console at https://console.aws.amazon.com/cloudwatch/.
- In the left navigation, click Log groups.
- Find and click the log group named /aws/lambda/software-factory-trigger.
- Click the most recent log stream.
You will see your Lambda function was invoked. The logs show the raw event from EventBridge and the uploaded file path. Then you will see an error.
The function threw an AccessDeniedException when it tried to call codebuild:StartBuild. This is the expected result. The Lambda's default execution role only has CloudWatch Logs permissions. It does not have permission to start CodeBuild builds.
Why did we set this up to fail?
This permissions gap is intentional. In real AWS work, you will constantly run into situations where a service can reach another service but lacks the IAM permissions to actually do anything. Diagnosing and fixing these permission errors is a core AWS debugging skill.
The event chain itself is working perfectly. S3 fired the event, EventBridge matched it, and Lambda ran your code. The only missing piece is a single IAM permission. You will fix that in the next step.
🙋♀️ Don't see the log group?
If /aws/lambda/software-factory-trigger does not appear in CloudWatch Logs, the Lambda function was never invoked. This usually means EventBridge notifications on the S3 bucket have not taken effect yet. Wait five minutes after enabling EventBridge on the bucket, then re-upload source.zip again.
Also confirm your EventBridge rule's event pattern includes your exact bucket name. A typo in the bucket name means the rule will not match.
help me figure out why my Lambda function is not being invoked by EventBridge
The event chain is live. S3 talks to EventBridge, EventBridge talks to Lambda, and Lambda tries to talk to CodeBuild. In the next step, you will add the missing IAM permission and watch the full pipeline work end to end.
Fix Permissions and Complete the Pipeline
In Step 4, you proved the event chain works end to end. S3 fires an event to EventBridge, EventBridge invokes your Lambda function, and Lambda attempts to start a CodeBuild build.
There is one problem. The Lambda function's IAM role only has permission to write logs to CloudWatch. It does not have permission to call codebuild:StartBuild. In this step, you will add that single missing permission and watch the entire pipeline come to life.
In this step, get ready to:
- Add an inline IAM policy granting codebuild:StartBuild to the Lambda execution role.
- Re-upload source.zip to S3 and confirm the Lambda starts a build successfully.
- Verify the build completes with no manual intervention.
Add CodeBuild permissions to the Lambda role
The Lambda function's default execution role only includes CloudWatch Logs permissions. You need to add a single permission that lets the function call codebuild:StartBuild on your software-factory-build project.
- Open the IAM console at https://console.aws.amazon.com/iam/.
- In the left navigation pane, click Roles.
- In the search bar, type software-factory-trigger.
- Click the role name that appears (it looks something like software-factory-trigger-role-XXXXX).
- Click the Permissions tab.
- Click Add permissions.
- Select Create inline policy.
The policy editor opens. You will paste a JSON policy that grants the exact permission your Lambda needs.
- In the policy editor, click JSON.
- Delete any existing content in the editor.
- Paste the following policy:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "codebuild:StartBuild",
"Resource": "arn:aws:codebuild:*:*:project/software-factory-build"
}
]
}
What does this policy do?
This inline policy grants exactly one permission: codebuild:StartBuild. The Resource field scopes it to the software-factory-build project only. The Lambda cannot start builds on any other CodeBuild project.
This follows the principle of least privilege. You give a service only the permissions it needs for the specific resources it works with.
- Click Next.
- In the Policy name field, enter codebuild-start-build.
- Click Create policy.
You should be taken back to the role summary page. Under the Permissions tab, your new codebuild-start-build inline policy is now listed alongside the existing CloudWatch Logs policy.
Don't see the new policy listed?
- Refresh the page. The inline policy should appear under the Permissions tab.
- Make sure you clicked Create policy on the review page. If you navigated away before clicking it, the policy was not saved.
- Confirm you are looking at the correct role (the one containing software-factory-trigger in its name).
Still stuck? Help me add an inline policy to my Lambda execution role.
Re-upload code and watch the full chain
The permission is in place. Time to trigger the full pipeline by uploading code to S3 again.
Before you run this, think about what you expect to happen. The Lambda code has not changed. The only difference is the IAM permission you just added. Will the Lambda succeed this time?
- Return to your terminal and re-upload source.zip to S3 by running this command:
aws s3 cp source.zip s3://[[BUCKET_NAME="software-factory-source-UNIQUE"]]/source.zip
What does this trigger?
This upload kicks off the full chain. S3 emits an event to EventBridge. EventBridge matches the event against your software-factory-s3-trigger rule and invokes the Lambda function. This time, Lambda has the codebuild:StartBuild permission, so the call should succeed.
- Open the Lambda console and select your software-factory-trigger function.
- Click the Monitor tab.
- Click View CloudWatch logs.
- Click the most recent log stream at the top of the list.
In the log stream, you should see output from your Lambda function including a line that says Started CodeBuild build followed by a build ID. No more AccessDeniedException.
What changed from last time?
In Step 4, the Lambda's CloudWatch Logs showed an AccessDeniedException because the execution role lacked codebuild:StartBuild. Now that you have added the inline policy, the same Lambda code runs successfully.
The only difference between a broken pipeline and a working one was a single IAM permission. This is a common pattern in AWS debugging.
🙋♀️ Still seeing AccessDeniedException?
- Go back to the IAM console and confirm the codebuild-start-build inline policy is listed under the role's Permissions tab.
- Make sure you are looking at the newest log stream. The most recent invocation appears at the top of the list. Older streams still show the previous AccessDeniedException.
- Double-check that you uploaded source.zip to the correct bucket after adding the IAM policy.
Still stuck? Help me verify my Lambda CloudWatch Logs after re-uploading code to S3.
Verify the automated build
CloudWatch Logs confirmed the Lambda kicked off a build. Now confirm the build itself completed successfully with no manual action on your part.
Do you think the build succeeded? You did not click Start build this time. Head to the CodeBuild console to find out.
- Open the CodeBuild console and click Build projects in the left navigation pane.
- Click software-factory-build.
- Scroll down to the Build history section and look at the most recent build entry.
The most recent build should show status Succeeded. This build was triggered entirely by your S3 upload, flowing through EventBridge and Lambda with zero manual intervention.
What just happened?
You completed a fully automated, event-driven pipeline using five AWS services working together:
- S3 detected the upload and emitted an event to EventBridge.
- EventBridge matched the event against your rule and invoked the Lambda function.
- Lambda called codebuild:StartBuild to kick off the build.
- CodeBuild downloaded, installed, and built your Node.js code.
- IAM governed every interaction, ensuring each service had exactly the permissions it needed.
🙋♀️ Build not showing as Succeeded?
- If the build status is In progress, wait a minute and refresh the page. Builds can take a minute or two to complete.
- If the build shows Failed, click the build ID to check the build logs. Look for errors in the INSTALL or BUILD phase that could indicate an issue with your buildspec.yml or package.json.
- If no new build appears at all, go back to CloudWatch Logs and confirm the Lambda logged Started CodeBuild build. If it did not, re-upload source.zip and check again.
Still stuck? Help me troubleshoot why my automated CodeBuild build is not succeeding.
Your fully automated pipeline is alive. Upload code, and it builds itself. In the next step, you will swap the Lambda glue for a Step Functions state machine, turning your pipeline into a visual workflow that can grow to handle multi-stage builds.
Orchestrate Builds with Step Functions
Your event-driven pipeline works end to end. Upload code to S3, and a build starts automatically with zero manual intervention.
Your Lambda function works, but it is a pass-through that calls one API. If you need to add build stages, run them in parallel, or retry on failure, you would have to write all that logic yourself.
Step Functions handles sequencing, parallelism, and error handling as configuration, not code. In this step, you will replace the Lambda glue with a Step Functions state machine.
In this step, get ready to:
- Create a Step Functions state machine that starts a CodeBuild build and waits for it to complete.
- Update the EventBridge rule to target the state machine instead of the Lambda function.
- Test the full pipeline with Step Functions as the orchestrator.
Create the Step Functions state machine
You will create a state machine with a single task: start your CodeBuild build and wait for it to finish. Step Functions calls this the Run a Job (.sync) pattern.
What is AWS Step Functions?
Step Functions is a serverless workflow service. You define your pipeline as a series of states in a JSON document called the Amazon States Language (ASL). Step Functions executes each state in order, passes data between them, and handles retries and error branching automatically.
For this project, one state is all you need. In a production pipeline, you would add states for security scanning, testing, and deployment, all orchestrated by the same state machine.
- Open the Step Functions console at https://console.aws.amazon.com/states/home.
- Click Create state machine.
- Choose Create from blank.
- Enter software-factory-orchestrator as the state machine name.
- Click Continue to open Workflow Studio.
Workflow Studio opens with a blank canvas. You will paste the state machine definition directly in Code mode rather than using the visual drag-and-drop editor.
- Click the Code button at the top of Workflow Studio to switch to the code editor.
- Select all the existing content in the editor and replace it with the following definition:
{
"Comment": "Start a CodeBuild build and wait for completion",
"StartAt": "Start Build",
"States": {
"Start Build": {
"Type": "Task",
"Resource": "arn:aws:states:::codebuild:startBuild.sync",
"Parameters": {
"ProjectName": "software-factory-build"
},
"End": true
}
}
}
What does this definition do?
- StartAt tells Step Functions which state to run first. You only have one state, so it starts and ends with Start Build.
- Resource uses the .sync suffix. This tells Step Functions to start the CodeBuild build and wait for it to complete before moving on. Without .sync, Step Functions would fire-and-forget.
- Parameters passes the CodeBuild project name in PascalCase. Step Functions translates this into the startBuild API call for you.
- Click the Config button at the top of Workflow Studio.
- Under State machine settings, leave the type set to Standard.
- Under Permissions, leave Create new role selected. Step Functions will auto-generate an IAM role with the permissions your state machine needs.
- Click Create.
You should see the state machine detail page with your software-factory-orchestrator state machine. The visual graph shows a single Start Build step.
Why Step Functions instead of Lambda?
Your Lambda function works, but all it does is call codebuild.start_build() and return. If you want to add a second build stage, retry on failure, or run stages in parallel, you have to write and maintain that logic yourself.
Step Functions handles sequencing, retries, and parallelism as configuration. Adding a second build stage is one more state in the JSON, not a rewrite of your Lambda.
🙋♀️ State machine creation failed?
If you see a validation error, make sure you pasted the JSON exactly as shown. A missing comma or bracket will prevent the state machine from being created. Switch back to Code mode and check for red underlines in the editor.
help me create my Step Functions state machine for CodeBuild
Update the EventBridge rule and test the pipeline
Your EventBridge rule currently targets the Lambda function. You will update it to target the Step Functions state machine instead.
- Open the EventBridge console at https://console.aws.amazon.com/events/.
- In the left navigation pane, click Rules.
- Click your software-factory-s3-trigger rule.
- Click the Targets tab.
- Click Edit.
The rule editor opens. You need to skip to the targets page and swap the Lambda target for your state machine.
- Click Next to skip past the event pattern.
- Under Target 1, change Select a target from Lambda function to Step Functions state machine.
- For State machine, select software-factory-orchestrator.
- Under Execution role, select Create a new role for this specific resource. EventBridge will auto-create an IAM role that allows it to start your state machine.
- Click Next through any remaining pages.
- Click Update rule.
The rule details page should now show your Step Functions state machine as the target instead of the Lambda function.
Don't see your state machine in the dropdown?
Make sure you are in the same region as the state machine you created. Check the region selector in the top right corner of the console. Also confirm the state machine was created successfully by visiting the Step Functions console.
help me update my EventBridge rule to target Step Functions
Time to test the full pipeline with Step Functions as the orchestrator. Before you run this, think about what will happen. The same S3 upload event fires, but now EventBridge sends it to Step Functions instead of Lambda. Will the build still run?
- Re-upload source.zip to S3 by running this command:
aws s3 cp source.zip s3://[[S3_BUCKET="software-factory-source-your-unique-suffix"]]/source.zip
- Open the Step Functions console at https://console.aws.amazon.com/states/home.
- Click software-factory-orchestrator.
- In the Executions tab, you should see a new execution with status Running or Succeeded.
- Click the execution ID to open it.
The execution detail page shows a visual graph with your Start Build state highlighted in green. The .sync pattern means Step Functions waited for the build to complete before marking the execution as Succeeded.
What just happened?
You replaced the Lambda glue function with a Step Functions state machine. The pipeline now flows: S3 upload, EventBridge event, Step Functions execution, CodeBuild build. Step Functions handled the build orchestration without a single line of custom code.
The visual execution graph gives you a clear picture of what ran and whether it succeeded. In a multi-stage pipeline, each state lights up green or red as it completes.
🙋♀️ Execution not appearing or showing Failed?
- If no execution appears, confirm that the EventBridge rule target was updated to the state machine. Open the rule and check the Targets tab.
- If the execution shows Failed, click the execution ID and check the error output. The most common cause is the state machine's IAM role missing codebuild:StartBuild permissions. Verify the role in the IAM console.
- Wait at least 30 seconds after uploading before checking. The event chain needs time to fire.
help me debug my Step Functions execution
Secret mission
Add Build Notifications with SNS
Your pipeline builds code automatically, but it does so silently. Real software factories alert operators when builds succeed or fail. In this secret mission, you will add email notifications using SNS so you know the moment a build finishes.
Clean Up Your Resources
Clean Up Your Resources
Decide whether to keep your resources running, pause them to come back later, or delete them entirely. This project uses several AWS services, but costs stay near zero when the pipeline is idle.
Cost warning
CodeBuild offers 100 free build minutes per month on general1.small. Lambda offers 1 million free requests per month. Step Functions offers 4,000 free state transitions per month. S3 storage and EventBridge usage are negligible.
If you leave the pipeline running and upload many builds, you could exceed the free tier. Delete or pause resources if you do not plan to use them again soon.
Resources you used:
- S3 bucket software-factory-source-UNIQUE (stores source.zip).
- CodeBuild project software-factory-build.
- Lambda function software-factory-trigger.
- Step Functions state machine software-factory-orchestrator.
- EventBridge rule software-factory-s3-trigger.
- EventBridge rule software-factory-build-notifications (Secret Mission).
- IAM roles for Lambda, CodeBuild, Step Functions, and EventBridge (auto-created).
- SNS topic software-factory-notifications (Secret Mission).
- Local project files (buildspec.yml, lambda_function.py, source.zip, event pattern JSON files).
Keep everything running
No action needed. Choose this if you are still actively building or plan to extend the pipeline.
- The pipeline is dormant until you upload code to S3. Costs stay near zero while idle.
- The AWS CLI remains installed on your machine.
- Your local project files stay on your Desktop.
Pause - I'll come back to this later
Shut down the automatic triggers to prevent accidental builds while keeping the CodeBuild project available for manual runs.
- Open the EventBridge console at https://console.aws.amazon.com/events/.
- Click Rules in the left sidebar.
- Select the radio button next to software-factory-s3-trigger.
- Click Actions, then Delete.
- Type the rule name to confirm, then click Delete.
- If you completed the Secret Mission, repeat for the rule software-factory-build-notifications.
- Keep the CodeBuild project, Lambda function, and S3 bucket for when you return.
Delete - I don't want to use this again
Remove all project resources and start fresh. Follow these steps in order.
Delete EventBridge rules
- Open the EventBridge console at https://console.aws.amazon.com/events/.
- Click Rules in the left sidebar.
- Select the radio button next to software-factory-s3-trigger.
- Click Actions, then Delete.
- Type the rule name to confirm, then click Delete.
- If you completed the Secret Mission, repeat for the rule software-factory-build-notifications.
Delete the Lambda function
- Delete the Lambda function by running this command:
aws lambda delete-function --function-name software-factory-trigger
Delete the Step Functions state machine
- Delete the state machine by running this command:
aws stepfunctions delete-state-machine --state-machine-arn $(aws stepfunctions list-state-machines --query "stateMachines[?name=='software-factory-orchestrator'].stateMachineArn" --output text)
Delete the CodeBuild project
- Delete the CodeBuild project by running this command:
aws codebuild delete-project --name software-factory-build
Empty and delete the S3 bucket
- Empty the bucket by running this command:
aws s3 rm s3://[[BUCKET_NAME="software-factory-source-UNIQUE"]] --recursive
- Delete the now-empty bucket by running:
aws s3 rb s3://[[BUCKET_NAME="software-factory-source-UNIQUE"]]
Delete the SNS topic (Secret Mission)
If you completed the Secret Mission, delete the SNS topic. You need the topic ARN first.
- List your SNS topics by running:
aws sns list-topics
- Find the ARN that contains software-factory-notifications in the output.
- Delete the topic by running this command with your ARN:
aws sns delete-topic --topic-arn [[TOPIC_ARN="arn:aws:sns:us-east-1:123456789012:software-factory-notifications"]]
Delete the IAM roles
Several IAM roles were auto-created during the project. Search for and delete each one in the IAM console.
- Open the IAM console at https://console.aws.amazon.com/iam/.
- Click Roles in the left sidebar.
- Search for software-factory-trigger.
- Select the Lambda execution role.
- Click Delete, type the role name to confirm, then click Delete.
- Search for software-factory-build.
- Select the CodeBuild service role.
- Click Delete, type the role name to confirm, then click Delete.
Delete local files
- Remove your local project files by running:
rm buildspec.yml lambda_function.py eventbridge-s3-pattern.json eventbridge-codebuild-pattern.json source.zip
Nice Work!
Nice Work!
You just built a fully automated, serverless software factory on AWS. You can now drop a .zip file into an S3 bucket and watch five services chain together to build your code, with zero manual intervention.
You've learned how to:
- Build an event-driven pipeline where uploading code to S3 automatically triggers builds through EventBridge, Lambda, and CodeBuild.
- Create a CodeBuild project with a buildspec.yml that installs dependencies and builds a Node.js application.
- Diagnose and fix a real IAM permissions failure, a core AWS debugging skill that shows up constantly in production work.
- Secret Mission: Added SNS email notifications for build results, closing the feedback loop so you know the moment a build succeeds or fails.
Ready to quiz yourself?