AWS Builder Center
CI/CD for Beginners

CI/CD for Beginners

Build Your First Automated Pipeline with Python and AWS

Why this article?

If you have ever uploaded files to a server by hand, forgotten a step, and broken your website at midnight, CI/CD is the fix. It is one of the most useful skills for any developer, tester, or cloud engineer, and it connects directly to AWS developer tools and the Operational Excellence pillar you meet in the AWS Cloud Practitioner exam.
By the end you will be able to:
  1. Explain CI, Continuous Delivery, and Continuous Deployment in plain words
  1. Read a pipeline flowchart and describe what happens at each stage
  1. Name the AWS services used for CI/CD and what each one does
  1. Build a Python project that is linted, tested, and deployed to AWS Lambda automatically
  1. Prove the safety net works by breaking the code on purpose
  1. Follow security and cost best practices
No DevOps experience is needed. You need basic Python knowledge, a free GitHub account, and an AWS account.



Part 1: What is CI/CD?

The problem it solves

Without automation, releasing software looks like this: a developer finishes a feature, copies files to a server, runs a few commands from memory, and hopes nothing breaks. Different people do it differently. Mistakes are common, releases are scary, so teams release rarely, and every release is big and risky.
CI/CD replaces the manual routine with an automatic, repeatable one.

Continuous Integration (CI)

In plain words: Every time someone changes the code, the system automatically builds the project and runs tests. Developers merge their work into the shared codebase often, and problems are caught in minutes instead of weeks.
Analogy: A group of people writing a book together. Instead of everyone writing alone for a month and then discovering that the chapters contradict each other, an editor checks each page the moment it is submitted and says "this part doesn't fit" right away.

Continuous Delivery (CD)

In plain words: After the code passes the tests, the system automatically prepares it for release. The final step to production is a manual approval: a human clicks "release".

Continuous Deployment (also CD)

In plain words: Same idea, but with no manual approval. Every change that passes all the automated checks goes live automatically.
Analogy for both: A car factory line. Continuous Delivery is a finished, inspected car parked at the loading dock, ready to ship whenever the manager says go. Continuous Deployment is the car driving itself onto the delivery truck the moment it passes inspection.
Term
Automatic build and test?
Automatic release to production?
Continuous Integration
Yes
No (not its job)
Continuous Delivery
Yes
Ready, but needs manual approval
Continuous Deployment
Yes
Yes, fully automatic
Heads up: The letters "CD" are used for both Delivery and Deployment. The difference is the manual approval step.



Part 2: The anatomy of a pipeline (with a flowchart)

A pipeline is the automated path your code travels from your laptop to your users.
text
Developer pushes a code change
|
v
+------------------------------+
| 1. SOURCE |
| Git repo detects the change |
+------------------------------+
|
v
+------------------------------+
| 2. BUILD |
| Install deps, package code |
+------------------------------+
|
v
+------------------------------+
| 3. TEST |
| Lint, unit tests, scans |
+------------------------------+
|
v
< Tests pass? >
/ \
NO / \ YES
v v
+-----------------+ +--------------------------+
| STOP | | Manual approval |
| Pipeline fails, | | (Continuous Delivery |
| developer is | | only; skipped in |
| notified | | Continuous Deployment) |
+-----------------+ +--------------------------+
| |
v v
Developer fixes the code +--------------------------+
and pushes again | 4. DEPLOY |
(back to step 1) | S3, Lambda, EC2, ... |
+--------------------------+
|
v
+--------------------------+
| 5. MONITOR |
| CloudWatch, logs, |
| rollback |
+--------------------------+
|
v
Feedback for the next change
(back to step 1)


How to read the flowchart

Stage
What happens
Kitchen analogy
1. Source
A code change (a commit or pull request) triggers the pipeline
A new order arrives
2. Build
Code is packaged; dependencies are installed
Ingredients are prepared and cooked
3. Test
Automated checks: style (lint), unit tests, security scans
The chef tastes the dish before it leaves the kitchen
Decision: Tests pass?
No: the pipeline stops, nothing is released, the developer is told. Yes: continue
The dish goes back to the stove if it is not right
Manual approval
Only in Continuous Delivery. A person says "go"
The manager signs off
4. Deploy
The tested package is released to servers, containers, or functions
The dish is served
5. Monitor
Watch the live system; roll back if something is wrong
You check if the customer is happy
The two "back to step 1" notes are the heart of the idea: failures loop straight back to the developer, and what you learn from production feeds the next change.

Key words you will hear

  • Repository (repo): where your code lives (for example on GitHub)
  • Commit / push: saving a change and sending it to the repo
  • Branch: a separate line of work; Ā mainĀ  is the usual production branch
  • Pull request (PR): a request to merge your branch into Ā mainĀ , often triggering tests first
  • Artifact: the output of the build (a zip file, a container image, a website folder)
  • Environment: a place where the app runs, such as dev, staging, or production
  • Rollback: returning to the previous working version
  • Trigger: the event that starts the pipeline (a push, a PR, a schedule, or a manual click)
  • Lint: an automatic style and mistake checker for code
  • Smoke test: a quick check after deployment that the live system basically works

Why teams love it

  • Speed: releases take minutes, not days
  • Quality: tests run every time, so bugs are caught early
  • Consistency: the same steps run the same way every time (no "it works on my machine")
  • Confidence: small, frequent releases are easier to fix than big, rare ones
  • Less stress: nobody does risky manual work at midnight



Part 3: How AWS fits in

You can build CI/CD on AWS in two main ways: use AWS's own developer tools, or use an external tool (such as GitHub Actions) that deploys to AWS. Many companies mix both.
Service
Role in the pipeline
Simple explanation
AWS CodeCommit
Source
Managed Git repositories hosted on AWS
AWS CodeBuild
Build and test
A fully managed build server; you pay for build minutes
AWS CodeDeploy
Deploy
Automates deployments to EC2, on-premises servers, Lambda, or ECS
AWS CodePipeline
Orchestrate
The conveyor belt connecting source, build, test, approval, and deploy
AWS CodeArtifact
Packages
A private store for software packages and libraries
Amazon ECR
Container images
A registry for Docker images
AWS CloudFormation / CDK
Infrastructure as code
Create AWS infrastructure from templates, repeatably
Amazon CloudWatch
Monitor
Metrics, logs, and alarms for the live system
AWS X-Ray
Trace
Find slow or failing parts of a distributed application
Availability note: AWS stopped accepting new CodeCommit customers in July 2024, then announced in November 2025 that CodeCommit returned to general availability. Service availability can change, so check the current AWS documentation before designing around a specific service. This tutorial uses GitHub as the source, so it does not depend on CodeCommit.
Analogy: a restaurant chain. The Git repo is the recipe book. CodeBuild is the kitchen that cooks following the recipe. CodeDeploy is the delivery driver. CodePipeline is the manager who makes sure the order goes kitchen, then driver, then customer. CloudWatch is the customer feedback desk.

Where the deployment goes

Your app
Typical AWS target
Static website (HTML, CSS, JS)
Amazon S3 + CloudFront
Serverless function (our project)
AWS Lambda
Virtual machine app
Amazon EC2 (via CodeDeploy)
Container app
Amazon ECS / EKS / App Runner
Simple web app platform
AWS Elastic Beanstalk
Infrastructure itself
CloudFormation stacks



Part 4: Mini project. A Python function that tests itself and deploys to AWS Lambda

What we are building

A tiny Python function that counts the words in a piece of text. It is small on purpose: the point is not the function, it is the pipeline around it.
text
You edit app.py and run: git push to main
|
v
GitHub Actions starts (deploy.yml)
|
------------------ JOB 1: test (CI) ------------------
|
v
Set up Python, install dev tools
|
v
flake8 (style check)
|
v
pytest (unit tests)
|
v
< All checks pass? >
/ \
NO / \ YES (needs: test)
v |
+--------------------+ |
| STOP | |
| Deploy job skipped | |
| Live function is | |
| unchanged | |
| You get a failure | |
| notification | |
+--------------------+ |
v
------------------ JOB 2: deploy (CD) ----------------
|
v
Request a GitHub OIDC token
|
v
AWS STS checks the token against the role's
trust policy (repo + branch) and returns
TEMPORARY credentials
|
v
Zip the code (function.zip)
|
v
aws lambda update-function-code
|
v
aws lambda wait function-updated
|
v
Smoke test: invoke the function and
check the answer
|
v
< Correct result? >
/ \
YES / \ NO
v v
+-----------------------+ +---------------------------+
| LIVE | | ALERT |
| New version is | | Deploy ran but the smoke |
| running | | test failed: roll back |
+-----------------------+ | (git revert, push) |
+---------------------------+


Why these choices?

Choice
Why
A Python function
Python is beginner friendly and fully supported by AWS Lambda
AWS Lambda
No server to set up or pay for while idle; deploying means uploading a zip file, so the pipeline stays simple
pytest
The most popular Python test tool; tests are plain functions
flake8
A linter that catches style problems and simple mistakes before they reach production
GitHub Actions
Free for learners and lives next to your code
OIDC + IAM role
Lets GitHub deploy to AWS without storing any password or access key
A smoke test after deploying
Tests before deployment prove the code is fine; a smoke test proves the live function works

The project flowchart in plain words

  1. You push to Ā mainĀ 
  1. Job 1 (CI): GitHub sets up Python, installs tools, runs flake8 and pytest
  1. If anything fails, the pipeline stops. The deploy job is skipped and your live function is untouched
  1. If everything passes, Job 2 (CD) starts. It asks GitHub for a short-lived identity token
  1. AWS checks the token against the role's trust policy and hands back temporary credentials
  1. The job zips the code, updates the Lambda function, waits for the update to finish, and invokes the function to check the answer
  1. Correct answer: the new version is live. Wrong answer: the job fails so you know to roll back
Cost: Lambda has a generous free tier, and this project makes only a handful of calls. Check the current Lambda and CloudWatch Logs pricing, set an AWS Budget alert first, and follow the cleanup step at the end.



Step 1: Create the repository and the project files

Create a new GitHub repository (for example Ā wordcount-pipelineĀ ) with this structure:
Plain text
wordcount-pipeline/
ā”œā”€ā”€ app.py
ā”œā”€ā”€ requirements-dev.txt
ā”œā”€ā”€ setup.cfg
ā”œā”€ā”€ tests/
│ └── test_app.py
└── .github/
└── workflows/
└── deploy.yml

Ā app.pyĀ  (the function we will deploy)
Python
import json

def count_words(text):
"""Return the number of words in a piece of text."""
return len(text.split())

def lambda_handler(event, context):
"""AWS Lambda entry point: reads 'text' from the event and returns its word count."""
text = event.get("text", "")
return {
"statusCode": 200,
"body": json.dumps({"words": count_words(text)}),
}

Why it is written this way: Ā count_wordsĀ  holds the real logic and knows nothing about AWS, so it is easy to test. Ā lambda_handlerĀ  is the thin wrapper that AWS calls. Keeping logic separate from the cloud glue is a habit worth building early.
Ā tests/test_app.pyĀ  (the safety net)
Python
import json

from app import count_words, lambda_handler

def test_count_words_simple():
assert count_words("hello cloud world") == 3

def test_count_words_empty():
assert count_words("") == 0

def test_count_words_extra_spaces():
assert count_words(" many spaces here ") == 3

def test_lambda_handler_returns_json_body():
response = lambda_handler({"text": "ci cd on aws"}, None)
assert response["statusCode"] == 200
assert json.loads(response["body"]) == {"words": 4}

Why these tests: they cover a normal case, an empty input, messy spacing, and the exact shape of what Lambda returns. When someone changes the code later and breaks one of these behaviours, the pipeline notices.
Ā requirements-dev.txtĀ  (tools used only in the pipeline, not shipped to Lambda)
Plain text
pytest
flake8

Ā setup.cfgĀ  (tells flake8 how strict to be)
ini
[flake8]
max-line-length = 100
exclude = .git,__pycache__,.venv




Step 2: Run the checks on your own computer first

Bash
python -m venv .venv
source .venv/bin/activate # on Windows: .venv\Scripts\activate
pip install -r requirements-dev.txt
flake8 .
python -m pytest

You should see no flake8 output (silence means success) and a pytest summary like Ā 4 passedĀ .
Why run them locally first? The pipeline should confirm what you already know, not be where you discover typos. Finding a problem on your laptop takes seconds; finding it in the pipeline takes minutes.
Why Ā python -m pytestĀ  instead of just Ā pytestĀ ? The Ā -mĀ  form adds your project folder to Python's search path, so Ā from app import ...Ā  works. Plain Ā pytestĀ  can fail with "ModuleNotFoundError" in a layout like this one. This is one of the most common beginner stumbling blocks.



Step 3: Create the Lambda function (one time)

  1. Open the AWS Lambda console and choose Create function
  1. Select Author from scratch
  1. Name: Ā wordcounterĀ 
  1. Runtime: Python (choose a current Python version that Lambda offers)
  1. Create the function
  1. On the Code tab, scroll to Runtime settings and choose Edit. Change the Handler to Ā app.lambda_handlerĀ 
Why change the handler? The handler setting is Ā file_name.function_nameĀ . Lambda's default is Ā lambda_function.lambda_handlerĀ , but our file is Ā app.pyĀ . If you skip this, the pipeline deploys successfully and then the function fails with an import error. It is the number one mistake in this project.
Why create the function by hand instead of in the pipeline? The pipeline's job is to update the code, not to build infrastructure. Creating infrastructure is a separate concern; later you can automate it with CloudFormation or the AWS CDK (see "Next steps").
Copy the function ARN from the function page; you need it in the next step.



Step 4: Let GitHub deploy to AWS without passwords (OIDC)

The problem: the pipeline needs permission to update your function. The beginner mistake is to create an IAM user, generate an access key, and paste it into GitHub. Long-lived keys can leak and must be rotated.
The better way: OpenID Connect (OIDC). GitHub proves to AWS "this request really comes from my repository", and AWS gives the workflow a temporary credential for an IAM role. There is nothing secret to store.
Analogy: Instead of giving a delivery driver a permanent copy of your house key, the building's security desk checks their ID badge and hands over a visitor pass that expires within the hour.
4a. Add GitHub as an identity provider
  1. Open IAM, then Identity providers, then Add provider
  1. Choose OpenID Connect
  1. Provider URL: Ā https://token.actions.githubusercontent.comĀ 
  1. Audience: Ā sts.amazonaws.comĀ 
  1. Add the provider
4b. Create the IAM role
Create a role of type Web identity, pick that provider, and use this trust policy (replace the placeholders):
JSON
{
"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:<GITHUB_OWNER>/<REPO_NAME>:ref:refs/heads/main"
}
}
}
]
}

Why the Ā subĀ  line matters: it says that only your repository, running on the Ā mainĀ  branch, may use this role. Without it, a different repository could try to claim your role. Never use a wildcard for a whole account or organisation.
4c. Attach a least-privilege permissions policy
JSON
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"lambda:UpdateFunctionCode",
"lambda:GetFunction",
"lambda:GetFunctionConfiguration",
"lambda:InvokeFunction"
],
"Resource": "arn:aws:lambda:<REGION>:<ACCOUNT_ID>:function:wordcounter"
}
]
}

Why only these four actions? Update the code, check its status while waiting, and invoke it for the smoke test. The role cannot delete the function, read other functions, or touch any other service. If the pipeline were ever compromised, the damage would be limited to this one function. This is least privilege.
Copy the role ARN.



Step 5: Write the workflow

Create Ā .github/workflows/deploy.ymlĀ :
YAML
name: Test and deploy

on:
push:
branches: [main]
workflow_dispatch:

permissions:
id-token: write # allows the job to request the OIDC token
contents: read

env:
FUNCTION_NAME: wordcounter
AWS_REGION: <YOUR_REGION>

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

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.12"

- name: Install dev tools
run: pip install -r requirements-dev.txt

- name: Lint with flake8
run: flake8 .

- name: Run unit tests
run: python -m pytest

deploy:
needs: test # only runs if the test job succeeded
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Configure AWS credentials (OIDC)
uses: aws-actions/configure-aws-credentials@v6
with:
role-to-assume: arn:aws:iam::<ACCOUNT_ID>:role/<ROLE_NAME>
aws-region: ${{ env.AWS_REGION }}

- name: Package the code
run: zip function.zip app.py

- name: Update the Lambda function
run: aws lambda update-function-code --function-name "$FUNCTION_NAME" --zip-file fileb://function.zip

- name: Wait for the update to finish
run: aws lambda wait function-updated --function-name "$FUNCTION_NAME"

- name: Smoke test the live function
run: |
aws lambda invoke --function-name "$FUNCTION_NAME" --cli-binary-format raw-in-base64-out --payload '{"text": "ci cd on aws"}' response.json
python3 -c "import json; r = json.load(open('response.json')); assert json.loads(r['body'])['words'] == 4, r"

Version note: action version numbers change over time. Check the Ā aws-actions/configure-aws-credentialsĀ  and GitHub Actions pages for current recommended versions. Use a Python version in the workflow that your Lambda runtime also supports.

Why every part is there

Part
Why it exists
on: push: branches: [main]
The trigger: every change to main is checked and released
workflow_dispatch
Lets you re-run the pipeline manually from the Actions tab
permissions: id-token: write
Without it the job cannot ask GitHub for the OIDC token, and AWS login fails
test job
The CI half: catch problems before anything is released
flake8 then pytest
Cheap style check first (seconds), longer tests second; fail fast
needs: test
The gate: if the test job fails, deploy never starts. This single line protects production
configure-aws-credentials
Exchanges the GitHub token for temporary AWS credentials
zip function.zip app.py
Lambda accepts code as a zip file; this is the artifact
wait function-updated
An update is not instant; invoking too early can run the old code or fail
Smoke test
Proves the deployed function returns the right answer, not just that the upload worked



Step 6: Push and watch it run

  1. Commit everything and push to Ā mainĀ 
  1. Open the Actions tab in GitHub
  1. Watch the test job turn green, then the deploy job
  1. In the Lambda console, open the Test tab, use Ā {"text": "hello from my first pipeline"}Ā  as the event, and run it. You should get Ā {"words": 5}Ā  inside the response body
You now have a working CI/CD pipeline.



Step 7: Break it on purpose (the best part)

A pipeline you have never seen fail is a pipeline you do not yet trust. In Ā app.pyĀ , change the function to:
Python
def count_words(text):
return len(text) # bug: counts characters, not words

Commit and push. What you should see:
  • The test job fails at the pytest step
  • The deploy job is skipped
  • Your live Lambda function is unchanged and still works
That is the entire value of CI/CD in one experiment: a broken change was stopped automatically before it reached users.
Now put the correct line back and push again, and watch the pipeline go green and deploy.

Step 8: Practise a rollback

Imagine a bad change did reach production. Because every release comes from Git, you can fix it quickly:
Bash
git revert HEAD
git push

Ā git revertĀ  creates a new commit that undoes the last one, and the pipeline automatically deploys that earlier, working version. Why revert instead of editing history? It keeps a clear record of what happened, and the pipeline treats it like any other change.

Step 9: Clean up

  • Delete the Lambda function Ā wordcounterĀ 
  • Delete its CloudWatch log group Ā /aws/lambda/wordcounterĀ  (Lambda creates it automatically when the function runs, and it is easy to forget)
  • Delete the IAM role and its policy
  • Delete the GitHub OIDC identity provider if you no longer need it
  • Check Billing and your Budget the next day to confirm nothing is still running



Variation: deploy a static website to S3 instead

Everything above stays the same (repo, tests job, OIDC role, Ā needs: testĀ ). Only the deploy target changes.
  • Permissions policy: replace the Lambda actions with Ā s3:PutObjectĀ  and Ā s3:DeleteObjectĀ  on Ā arn:aws:s3:::<BUCKET_NAME>/*Ā  and Ā s3:ListBucketĀ  on Ā arn:aws:s3:::<BUCKET_NAME>Ā . Add Ā cloudfront:CreateInvalidationĀ  if you use CloudFront
  • Deploy step:
YAML
- name: Upload to S3
run: aws s3 sync . s3://<BUCKET_NAME> --delete --exclude ".git/*" --exclude ".github/*"

  • Keep the bucket private and serve it through Amazon CloudFront with Origin Access Control rather than making the bucket public



Part 5: The same idea with AWS-native tools

Here is how the same project looks with AWS's own services, so you recognise it in real teams and in exams.

CodeBuild and Ā buildspec.ymlĀ 

CodeBuild reads a Ā buildspec.ymlĀ  from your repository that lists what to run:
YAML
version: 0.2

phases:
install:
commands:
- pip install -r requirements-dev.txt
build:
commands:
- flake8 .
- python -m pytest

artifacts:
files:
- app.py


CodeDeploy and Ā appspec.ymlĀ 

To deploy to EC2, CodeDeploy reads an Ā appspec.ymlĀ  that says where files go and which scripts to run:
YAML
version: 0.0
os: linux
files:
- source: /
destination: /var/www/html
hooks:
AfterInstall:
- location: scripts/restart_server.sh
timeout: 60
runas: root


CodePipeline

CodePipeline connects the stages: Source (GitHub or CodeCommit), Build (CodeBuild), an optional Manual approval, and Deploy (CodeDeploy, Lambda, S3, ECS, CloudFormation, and more). Adding the manual approval action is exactly what turns Continuous Deployment into Continuous Delivery.

Deployment strategies you should know

Strategy
How it works
Analogy
All at once / in-place
Replace the old version everywhere
Changing a tyre while parked on the roadside; there is downtime
Rolling
Update servers in small batches
Repainting a hotel one floor at a time
Blue/green
Build a full new copy (green), test it, then switch traffic from the old (blue)
Opening a new restaurant next door and moving the customers across when it is ready
Canary
Send a small percentage of users to the new version first
Letting one taster try the dish before serving everyone
Blue/green and canary make rollback fast: switch the traffic back.



Part 6: Best practices checklist

Security
  • Use IAM roles and OIDC, not long-lived access keys
  • Apply least privilege: only the exact actions and resources the pipeline needs
  • Restrict the role's trust policy to your repo and branch
  • Never commit secrets to Git; use AWS Secrets Manager or encrypted pipeline secrets
  • Add dependency and vulnerability scanning to the test stage
Quality
  • Keep builds fast
  • Run tests on every pull request, not only on Ā mainĀ 
  • Protect the Ā mainĀ  branch: require passing checks before merging
  • Make builds repeatable: same input, same output
Operations
  • Keep the pipeline definition in Git (pipeline as code)
  • Define infrastructure with CloudFormation or CDK, not by clicking
  • Add monitoring and alarms in CloudWatch, and have a rollback plan
  • Use separate environments (dev, staging, production), with a manual approval before production if your team needs it
Cost
  • Delete unused test resources, including log groups
  • Set AWS Budgets alerts
  • Use build caching to cut build minutes

Common beginner mistakes

Mistake
Better approach
Putting AWS access keys in the repo
Use OIDC with an IAM role
Giving the role AdministratorAccess
Give only the permissions needed
Forgetting to change the Lambda handler name
Set it to app.lambda_handler
Running plain pytest and getting import errors
Use python -m pytest
No tests, so the pipeline just copies files
Add at least a few real tests and grow from there
Deploying straight to production on day one
Add a test environment first
Pipeline fails and nobody notices
Turn on notifications (GitHub, SNS, email, chat)
Forgetting to clean up
Delete the resources and set a budget



Part 7: How this connects to the AWS Cloud Practitioner exam

Exam idea
Link to this article
CodeBuild, CodeDeploy, CodePipeline
Know each one's job: build, deploy, orchestrate
CloudFormation
Infrastructure as code: repeatable stacks from templates
Operational Excellence pillar
Automation, small reversible changes, learning from failure
IAM roles over access keys
Temporary credentials are safer than long-term keys
AWS Lambda
Serverless: you upload code, AWS runs it, you pay for use
Elastic Beanstalk
PaaS that handles deployment details for you
X-Ray and CloudWatch
Observe and troubleshoot after deployment
Quick quiz
  1. Which AWS service orchestrates the stages of a release pipeline?
  1. Which service compiles code and runs tests as a managed build server?
  1. What is the difference between Continuous Delivery and Continuous Deployment?
  1. In our workflow, which line stops the deploy job from running when tests fail?
  1. Why is an IAM role with OIDC safer than storing an access key in GitHub?
  1. Which deployment strategy lets you test a full new copy and then switch traffic over?
<details> <summary>Answers</summary>
  1. AWS CodePipeline
  1. AWS CodeBuild
  1. Delivery has a manual approval before production; Deployment is fully automatic
  1. Ā needs: testĀ 
  1. It uses short-lived temporary credentials, so there is no permanent secret to leak or rotate
  1. Blue/green deployment
</details>



Your next steps

  1. Add more tests (for example, text with punctuation or line breaks) and watch the pipeline protect you
  1. Add a pull request trigger so tests run before merging
  1. Describe the Lambda function and role in a CloudFormation or CDK template
  1. Rebuild the same project with CodePipeline + CodeBuild
  1. Add a manual approval stage and see how Delivery differs from Deployment
  1. Put the function behind Amazon API Gateway so it has a web address, and extend the smoke test to call it

Final thoughts

CI/CD is not a tool; it is a habit: integrate often, test automatically, release safely. You started with a flowchart and finished with a real function that tests itself and deploys itself. Every pipeline you build after this one gets easier.
If this article helped, share it with someone starting their DevOps journey, and tell me in the comments what you deployed first.


Disclaimer: This is an independent learning guide. AWS services, pricing, Free Tier rules, runtimes, and third-party actions change over time. Verify the details against the official Ā AWS documentationĀ  and the GitHub Actions documentation before using them, and always clean up resources and set a budget alert so you do not get unexpected charges.
Any opinions in this article are those of the individual author and may not reflect the opinions of AWS.
Enjoyed reading this content? Let the author know!

Your likes, comments, shares, and saves help creators reach more builders.

Loading recommendations

Loading article