
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:
- Explain CI, Continuous Delivery, and Continuous Deployment in plain words
- Read a pipeline flowchart and describe what happens at each stage
- Name the AWS services used for CI/CD and what each one does
- Build a Python project that is linted, tested, and deployed to AWS Lambda automatically
- Prove the safety net works by breaking the code on purpose
- 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 / \ YESv v+-----------------+ +--------------------------+| STOP | | Manual approval || Pipeline fails, | | (Continuous Delivery || developer is | | only; skipped in || notified | | Continuous Deployment) |+-----------------+ +--------------------------+| |v vDeveloper fixes the code +--------------------------+and pushes again | 4. DEPLOY |(back to step 1) | S3, Lambda, EC2, ... |+--------------------------+|v+--------------------------+| 5. MONITOR || CloudWatch, logs, || rollback |+--------------------------+|vFeedback 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|vGitHub Actions starts (deploy.yml)|------------------ JOB 1: test (CI) ------------------|vSet up Python, install dev tools|vflake8 (style check)|vpytest (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) ----------------|vRequest a GitHub OIDC token|vAWS STS checks the token against the role'strust policy (repo + branch) and returnsTEMPORARY credentials|vZip the code (function.zip)|vaws lambda update-function-code|vaws lambda wait function-updated|vSmoke test: invoke the function andcheck the answer|v< Correct result? >/ \YES / \ NOv 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
- You push to Ā
mainĀ
- Job 1 (CI): GitHub sets up Python, installs tools, runs flake8 and pytest
- If anything fails, the pipeline stops. The deploy job is skipped and your live function is untouched
- If everything passes, Job 2 (CD) starts. It asks GitHub for a short-lived identity token
- AWS checks the token against the role's trust policy and hands back temporary credentials
- The job zips the code, updates the Lambda function, waits for the update to finish, and invokes the function to check the answer
- 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 jsondef 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 jsonfrom app import count_words, lambda_handlerdef test_count_words_simple():assert count_words("hello cloud world") == 3def test_count_words_empty():assert count_words("") == 0def test_count_words_extra_spaces():assert count_words(" many spaces here ") == 3def test_lambda_handler_returns_json_body():response = lambda_handler({"text": "ci cd on aws"}, None)assert response["statusCode"] == 200assert 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
pytestflake8
Ā
setup.cfgĀ (tells flake8 how strict to be)ini
[flake8]max-line-length = 100exclude = .git,__pycache__,.venv

Step 2: Run the checks on your own computer first
Bash
python -m venv .venvsource .venv/bin/activate # on Windows: .venv\Scripts\activatepip install -r requirements-dev.txtflake8 .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)
- Open the AWS Lambda console and choose Create function
- Select Author from scratch
- Name: Ā
wordcounterĀ
- Runtime: Python (choose a current Python version that Lambda offers)
- Create the function
- 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
- Open IAM, then Identity providers, then Add provider
- Choose OpenID Connect
- Provider URL: Ā
https://token.actions.githubusercontent.comĀ
- Audience: Ā
sts.amazonaws.comĀ
- 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 deployon:push:branches: [main]workflow_dispatch:permissions:id-token: write # allows the job to request the OIDC tokencontents: readenv:FUNCTION_NAME: wordcounterAWS_REGION: <YOUR_REGION>jobs:test:runs-on: ubuntu-lateststeps:- uses: actions/checkout@v4- name: Set up Pythonuses: actions/setup-python@v5with:python-version: "3.12"- name: Install dev toolsrun: pip install -r requirements-dev.txt- name: Lint with flake8run: flake8 .- name: Run unit testsrun: python -m pytestdeploy:needs: test # only runs if the test job succeededruns-on: ubuntu-lateststeps:- uses: actions/checkout@v4- name: Configure AWS credentials (OIDC)uses: aws-actions/configure-aws-credentials@v6with:role-to-assume: arn:aws:iam::<ACCOUNT_ID>:role/<ROLE_NAME>aws-region: ${{ env.AWS_REGION }}- name: Package the coderun: zip function.zip app.py- name: Update the Lambda functionrun: aws lambda update-function-code --function-name "$FUNCTION_NAME" --zip-file fileb://function.zip- name: Wait for the update to finishrun: aws lambda wait function-updated --function-name "$FUNCTION_NAME"- name: Smoke test the live functionrun: |aws lambda invoke --function-name "$FUNCTION_NAME" --cli-binary-format raw-in-base64-out --payload '{"text": "ci cd on aws"}' response.jsonpython3 -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
- Commit everything and push to Ā
mainĀ
- Open the Actions tab in GitHub
- Watch the test job turn green, then the deploy job
- 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 HEADgit 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 S3run: 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.2phases:install:commands:- pip install -r requirements-dev.txtbuild:commands:- flake8 .- python -m pytestartifacts: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.0os: linuxfiles:- source: /destination: /var/www/htmlhooks:AfterInstall:- location: scripts/restart_server.shtimeout: 60runas: 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
- Which AWS service orchestrates the stages of a release pipeline?
- Which service compiles code and runs tests as a managed build server?
- What is the difference between Continuous Delivery and Continuous Deployment?
- In our workflow, which line stops the deploy job from running when tests fail?
- Why is an IAM role with OIDC safer than storing an access key in GitHub?
- Which deployment strategy lets you test a full new copy and then switch traffic over?
<details> <summary>Answers</summary>
- AWS CodePipeline
- AWS CodeBuild
- Delivery has a manual approval before production; Deployment is fully automatic
- Ā
needs: testĀ
- It uses short-lived temporary credentials, so there is no permanent secret to leak or rotate
- Blue/green deployment
</details>

Your next steps
- Add more tests (for example, text with punctuation or line breaks) and watch the pipeline protect you
- Add a pull request trigger so tests run before merging
- Describe the Lambda function and role in a CloudFormation or CDK template
- Rebuild the same project with CodePipeline + CodeBuild
- Add a manual approval stage and see how Delivery differs from Deployment
- 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.
Enjoyed reading this content? Let the author know!
Your likes, comments, shares, and saves help creators reach more builders.
Loading recommendations
Loading article