Deploy an Incident Triage API on AWS
Build and deploy a Docker-tested incident triage API with AWS SAM.
Introduction
30 Second Summary
A rushed incident report can leave out the detail that determines who responds first. Under pressure, that missing detail can turn a quick decision into another failure to investigate.
In this project, you will build a tested incident-triage API on AWS Lambda. You will take it from Docker-based local emulation to a deployed endpoint with traceable events in Amazon CloudWatch Logs.
What You'll Build
Your final demo shows a fabricated incident returning a P1 response whose request ID leads straight to its structured event in Amazon CloudWatch Logs.
By the end of this project, you'll have:
- A local incident endpoint that turns each submitted incident into a P1, P2, or P3 classification.
- Five automated tests proving severity rules plus safe handling for incomplete or malformed input.
- A live AWS endpoint whose response request ID connects each incident to its cloud log.
- Secret Mission: Run a P1 observability drill that generates mixed severities before isolating only P1 events from the deployed logs.
Are there any prerequisites?
You need a supported 64-bit Windows 10 or Windows 11 computer with at least 8 GB of RAM plus hardware virtualization.
Python 3.13.7 plus Visual Studio Code should already be installed.
Before We Start
Before the hands-on work begins, this checkpoint helps you commit to building an incident-triage API. It also anchors the operational problem your API solves.
Set Up AWS, Docker, and SAM
Local AWS Lambda emulation depends on a running Docker Desktop engine. Cloud deployment depends on authenticated AWS tools that avoid long-lived access keys.
You will create an AWS Free Plan account. You will prepare Windows Subsystem for Linux 2 before installing AWS CLI and AWS SAM CLI.
In this step, get ready to:
- Create an active AWS Free Plan account.
- Prepare Docker Desktop for local Lambda emulation.
- Install authenticated AWS tooling for deployment.
Create your AWS Free Plan account
The AWS Free Plan gives this project a controlled cloud environment. Your account starts with credits that cover the small workload you deploy later.
Keep This Lab in the Free Plan
New AWS customers receive $100 in credits immediately. The Free Plan lasts for six months or until those credits run out.
AWS does not charge you unless you upgrade to a Paid Plan or activate a paid-only service. Stay on the Free Plan throughout this project.
Use fabricated incident data because Free Plan accounts are intended for evaluation. The API never needs real customer details or operational secrets.
Creating a cloud account can feel risky because billing details may appear during verification. Most new customers do not need a payment method, although AWS may request one to confirm identity.
Most sign-ups finish quickly. AWS notes that a new account can take up to 24 hours to gain access to every service.
- Open the AWS Free Tier page in your browser.
- Select Create your free AWS account.
- Choose a supported login provider or enter your email address.
- Enter your name when the sign-up flow requests it.
- Verify your email address with the code AWS sends you.
- Create a strong password for your account.
- Enter your contact details.
- Complete any additional identity verification requested by AWS.
You will reach the AWS Management Console after AWS provisions your account. This confirms that the account is active.
- Use the console search field to open Billing and Cost Management.
- Confirm that your account shows the Free Plan.
- Review the displayed credit balance and remaining plan duration.
Your AWS account is ready. You now have a cloud destination for the incident API without creating long-lived access keys.
Install Docker Desktop with WSL 2
Docker Desktop uses the WSL 2 backend to run Linux containers on Windows. AWS SAM uses that container engine to reproduce the Lambda runtime on your computer.
- Press the Windows key to open Windows Search.
- Type PowerShell into Windows Search.
- Press Enter to open PowerShell.
- Check your installed WSL version by running this command:
wsl --version
What does this command do?
The command prints the installed WSL components and their versions. Docker Desktop requires WSL version 2.1.5 or later for the WSL 2 backend.
✔️ I see the required version
Your WSL version is 2.1.5 or later. Keep this PowerShell window available for the next check.
ⓧ I see an older version
- Close the current PowerShell window.
- Press the Windows key to open Windows Search.
- Type PowerShell into Windows Search.
- Select Run as administrator for PowerShell.
- Update WSL by running this command:
wsl --update
What does this command do?
The command downloads the current WSL update from Microsoft. Windows may request a restart after the update.
- Restart Windows if the update requests it.
- Return to PowerShell through Windows Search.
- Confirm the updated version by running:
wsl --version
What should I see?
The output should now report WSL version 2.1.5 or later. This version supports Docker Desktop's WSL 2 backend.
ⓧ Command not found
- Close the current PowerShell window.
- Press the Windows key to open Windows Search.
- Type PowerShell into Windows Search.
- Select Run as administrator for PowerShell.
- Install WSL by running this command:
wsl --install
What does this command do?
The command enables WSL and installs its current components. Windows may request a restart to finish the setup.
- Restart Windows when prompted.
- Return to PowerShell through Windows Search.
- Confirm the installation by running:
wsl --version
What should I see?
The output should contain WSL version details. Confirm that the version is 2.1.5 or later before continuing.
WSL is ready to support Linux containers. The next check determines whether the required Docker Desktop release is already installed.
- Check your Docker Desktop version by running this command in PowerShell:
docker desktop version --short
What does this command do?
The command asks the Docker Desktop CLI plugin for its version number. Use the result to choose the matching path below.
The Docker Desktop installer is a large download. Give it a few minutes if your connection is slow.
✔️ I see the required version
Your output shows 4.97.0. Docker Desktop is already on the version used by this project.
ⓧ I see an older version
- Open the official Docker Desktop release notes.
- Find release 4.97.0.
- Select the Windows download for that release.
- Open Docker Desktop Installer.exe after the download finishes.
- Keep the recommended per-user installation mode.
- Choose WSL 2 on the configuration screen.
- Authorize the installer if Windows requests permission.
- Select Close when installation finishes.
Why use per-user mode?
Per-user mode is Docker's recommended Windows installation for most developers. It uses the WSL 2 backend without requiring administrator privileges for routine Docker Desktop updates.
- Confirm the installed release by running this command in a new PowerShell window:
docker desktop version --short
What should I see?
The command should print 4.97.0. This confirms that the project uses the pinned Docker Desktop release.
ⓧ Command not found
- Open the official Docker Desktop release notes.
- Find release 4.97.0.
- Select the Windows download for that release.
- Open Docker Desktop Installer.exe after the download finishes.
- Keep the recommended per-user installation mode.
- Choose WSL 2 on the configuration screen.
- Authorize the installer if Windows requests permission.
- Select Close when installation finishes.
Why use the WSL 2 backend?
The WSL 2 backend runs the Linux containers required by AWS SAM. It also avoids the extra virtual machine management required by other backends.
- Confirm the installed release by running this command in a new PowerShell window:
docker desktop version --short
What should I see?
The command should print 4.97.0. PowerShell can now reach Docker Desktop.
Docker Desktop may show its subscription agreement on first launch. Starting the engine can also take a minute while the WSL 2 backend initializes.
- Press the Windows key to open Windows Search.
- Type Docker Desktop into Windows Search.
- Press Enter to start Docker Desktop.
- Accept the Docker Subscription Service Agreement if prompted.
- Wait until Docker Desktop reports that the engine is running.
- Select Settings in Docker Desktop.
- Select General in the settings navigation.
- Confirm that Use WSL 2 based engine is selected.
Before you check, do you expect PowerShell to reach only the Docker client or both the client and server?
- Verify the Docker client and engine by running these commands:
docker version
docker ps
What do these commands prove?
- The first command requests version details from both the Docker client and the running Docker engine.
- The second command asks the engine for its currently running containers.
You should see separate Docker Client and Server details. You should also see an empty container list or a list of containers already running on your computer.
Docker server details missing?
- Return to Docker Desktop and confirm that its engine reports a running state.
- Restart Docker Desktop if the engine is still initializing.
- Update WSL if Docker reports that the installed WSL version is unsupported.
Need a hand with the container engine?
Why Docker through AWS SAM?
AWS SAM uses Docker to reproduce the Lambda runtime during local builds and requests. This gives your Python handler a Lambda-like environment without making every test a cloud deployment.
The deployed application still uses a standard Lambda package. Containers provide local runtime parity while the cloud architecture stays focused on Lambda and API Gateway.
Install AWS tooling and authenticate
The AWS CLI handles account authentication. The AWS SAM CLI builds the serverless application before deploying it through AWS CloudFormation.
- Keep using the PowerShell window from the Docker verification.
- Check your AWS CLI version by running:
aws --version
What does this command do?
The command prints the installed AWS CLI release. This project uses AWS CLI 2.39.3 because it supports temporary browser authentication through aws login.
✔️ I see the required version
Your output contains AWS CLI version 2.39.3. The authentication command required by this project is available.
ⓧ I see an older version
- Install AWS CLI 2.39.3 for your current Windows user by running these commands:
irm https://awscli.amazonaws.com/v2/install.ps1 | iex
aws --version
What do these commands do?
The first command downloads the official current-user installer and installs the latest AWS CLI release. The second command prints the installed version for confirmation.
You should now see AWS CLI version 2.39.3 in the output.
ⓧ Command not found
- Install AWS CLI 2.39.3 for your current Windows user by running these commands:
irm https://awscli.amazonaws.com/v2/install.ps1 | iex
aws --version
What do these commands do?
The first command installs AWS CLI for your Windows user. The second command confirms that PowerShell can find the new installation.
You should now see AWS CLI version 2.39.3 in the output.
The AWS CLI is ready. Next, the SAM CLI adds local Lambda builds and cloud deployment commands.
- Check your AWS SAM CLI version by running:
sam --version
What does this command do?
The command prints the installed AWS SAM CLI release. This project uses version 1.171.0 for its local Docker workflow and deployment commands.
✔️ I see the required version
Your output shows AWS SAM CLI version 1.171.0. SAM is ready to work with Docker Desktop.
ⓧ I see an older version
- Open the official AWS SAM CLI release page for version 1.171.0.
- Expand the release assets.
- Download AWS_SAM_CLI_64_PY3.msi.
- Open the downloaded installer.
- Complete the installation wizard.
- Close the current PowerShell window.
- Press the Windows key to open Windows Search.
- Type PowerShell into Windows Search.
Why reopen PowerShell?
The installer adds SAM to the command path used by new terminal sessions. Reopening PowerShell makes that updated path available.
- Press Enter to open a new PowerShell window.
- Confirm the installed release by running:
sam --version
What should I see?
The output should show AWS SAM CLI version 1.171.0.
ⓧ Command not found
- Open the official AWS SAM CLI release page for version 1.171.0.
- Expand the release assets.
- Download AWS_SAM_CLI_64_PY3.msi.
- Open the downloaded installer.
- Complete the installation wizard.
- Close the current PowerShell window.
- Press the Windows key to open Windows Search.
- Type PowerShell into Windows Search.
Why reopen PowerShell?
A new PowerShell session reads the command path created by the SAM installer. This lets Windows locate the sam command.
- Press Enter to open a new PowerShell window.
- Confirm the installation by running:
sam --version
What should I see?
The output should show AWS SAM CLI version 1.171.0.
SAM reports a path-length problem?
AWS SAM can create paths that exceed the Windows default limit. Follow the official Windows long-path guidance if SAM reports a path-length problem later.
Restart Windows after changing the long-path setting so new processes can use it.
Need help enabling Windows long paths?
Authentication is the last piece. The next command opens a browser sign-in flow and caches temporary credentials for up to 12 hours.
- Start the temporary AWS sign-in flow by running:
aws login
Why use temporary login?
The login flow uses browser authentication instead of permanent access keys stored on your computer. AWS CLI refreshes the temporary session while it remains valid.
- Select us-east-1 when the login flow requests a Region.
- Complete the AWS sign-in in your browser.
- Return to PowerShell after the browser confirms success.
Before you run the final check, do you expect every command to succeed in the same PowerShell session?
- Verify Docker, AWS CLI, AWS SAM CLI, and your temporary AWS identity by running:
docker version; docker ps; aws --version; sam --version; aws sts get-caller-identity
What does this final check prove?
- The Docker commands prove that PowerShell can reach the client and the running server.
- The version commands prove that both AWS command-line tools are installed.
- The identity command returns the AWS user or role behind your temporary credentials.
You should see Docker Client and Server details. You should also see AWS CLI version 2.39.3.
The SAM output should show version 1.171.0. The final output should identify your authenticated AWS account session.
One of the final checks failed?
- Start Docker Desktop if the Docker output does not include Server details.
- Return to the matching installation tab if an AWS tool reports the wrong version.
- Repeat the browser login if the identity command cannot find valid credentials.
Help me diagnose the failed setup check.
That is the full toolchain ready. Next, you will connect a SAM template, a Python Lambda handler, Docker emulation, and a local HTTP route.
Run the Smallest API in Docker
Your AWS Free Plan session is authenticated. Docker is running on WSL 2.
Before adding triage rules you need proof that every layer connects. This step sends one request through an AWS SAM template into an AWS Lambda handler running locally.
In this step, get ready to:
- Define the project structure through a SAM template.
- Add a minimal Lambda handler that returns a JSON response.
- Prove the local endpoint returns its first response.
Create the project structure
A SAM template describes your serverless application as infrastructure as code. It gives the function runtime plus the local HTTP route one repeatable definition.
- Switch back to Visual Studio Code from earlier.
- Select File from the menu bar.
- Select Open Folder....
- Select your Desktop in the folder dialog.
- Select New Folder.
- Type incident-triage-api as the folder name.
What happens when the folder opens?
Visual Studio Code treats the folder as a workspace. A Workspace Trust dialog may appear because workspace code can run on your computer.
You are creating these files yourself. You can trust this workspace.
- Select the new incident-triage-api folder.
- Select Select Folder.
- Select Yes, I trust the authors if the trust dialog appears.
You will see incident-triage-api at the top of the Explorer view. This confirms that every file you create next lands in the correct folder.
- Select the Explorer view from the Activity Bar.
- Select New File....
- Type template.yaml.
- Press Enter.
- Add the template foundation by pasting this block into template.yaml:
AWSTemplateFormatVersion: "2010-09-09"
Transform: AWS::Serverless-2016-10-31
Description: Docker-tested incident triage API
Globals:
Function:
Runtime: python3.13
Architectures:
- x86_64
Timeout: 3
MemorySize: 128
What does this configuration do?
- The Transform value tells CloudFormation to process SAM resources.
- The Globals section supplies shared settings for every function in this template.
- The python3.13 runtime matches the Python version used by the Lambda environment.
- The memory plus timeout values keep this small API tightly scoped.
- Press Ctrl+S to save template.yaml.
- Confirm template.yaml remains listed beneath incident-triage-api in Explorer.
Does the YAML look misaligned?
YAML uses indentation to show nesting. Make sure each child level in Globals is indented with two additional spaces.
Avoid replacing spaces with tabs. Help me check the indentation in my SAM template.
- Place your cursor after the MemorySize: 128 line.
- Press Enter twice.
- Add the function resource by pasting this block:
Resources:
IncidentFunction:
Type: AWS::Serverless::Function
Properties:
CodeUri: incident_api/
Handler: app.lambda_handler
Events:
SubmitIncident:
Type: Api
Properties:
Path: /incidents
Method: post
How does the route reach the handler?
- The IncidentFunction resource defines the Lambda function.
- The CodeUri value points SAM to the incident_api/ folder.
- The Handler value connects app.py to lambda_handler().
- The API event connects POST requests at /incidents to Amazon API Gateway.
- Press Ctrl+S to save template.yaml.
- Confirm the Resources section sits below Globals.
Is the function resource nested incorrectly?
Keep Resources at the far-left edge. Keep IncidentFunction indented beneath it.
Check that Path begins with a forward slash. Help me review my SAM function resource.
- Place your cursor after the Method: post line.
- Press Enter twice.
- Add the deployment outputs by pasting this block:
Outputs:
IncidentApiUrl:
Description: API Gateway URL for incident submissions
Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.${AWS::URLSuffix}/Prod/incidents"
IncidentFunctionArn:
Description: Lambda function ARN
Value: !GetAtt IncidentFunction.Arn
Why define outputs now?
The outputs expose the deployed API URL plus the function ARN after a cloud deployment. The local build ignores these values until the project reaches AWS.
Defining them in the same template keeps the local application aligned with its eventual cloud deployment.
- Press Ctrl+S to save template.yaml.
- Confirm Outputs appears at the far-left edge beneath Resources.
Do the outputs appear inside the function?
Move Outputs back to the far-left edge if it appears nested beneath IncidentFunction.
Keep the URL value inside double quotes. Help me fix the Outputs section in my SAM template.
Add the Lambda handler
A Lambda proxy response gives API Gateway an HTTP status code plus headers plus a string body. This first handler returns one fixed message so you can test the connection before adding business logic.
- Select incident-triage-api at the top of Explorer.
- Select New Folder....
- Type incident_api.
- Press Enter.
You will see the new incident_api folder beneath incident-triage-api in Explorer.
- Select the incident_api folder.
- Select New File....
- Type __init__.py.
- Press Enter.
- Mark the folder as the incident triage package by pasting this line:
"""Incident triage Lambda package."""
What does this package file do?
The module docstring identifies the purpose of the Python package. Keeping the file in incident_api also makes the package structure explicit.
- Press Ctrl+S to save __init__.py.
- Confirm __init__.py appears inside incident_api in Explorer.
- Select the incident_api folder again.
- Select New File....
- Type app.py.
- Press Enter.
- Add the smallest working handler by pasting this code:
import json
def lambda_handler(event, context):
return {
"statusCode": 200,
"headers": {"Content-Type": "application/json"},
"body": json.dumps({"message": "incident API running"}),
}
What does this handler do?
- The json module converts the response payload into a JSON string.
- The lambda_handler() function is the entry point configured by the SAM template.
- The statusCode value tells the client that the request succeeded.
- The Content-Type header identifies the body as JSON.
- The body supplies the first visible signal that the local route reached your code.
- Press Ctrl+S to save app.py.
- Confirm app.py appears beside __init__.py in Explorer.
Is the handler showing a syntax warning?
Check that each response key uses matching double quotes. Confirm that the closing brace aligns with the return statement.
Make sure app.py is inside incident_api. Help me check my minimal Lambda handler.
✔️ Awesome, I've got everything!
Your three files now match the smallest runnable incident API.
ⓧ I'd like to double check the full code
- Compare your complete template.yaml with this reference:
AWSTemplateFormatVersion: "2010-09-09"
Transform: AWS::Serverless-2016-10-31
Description: Docker-tested incident triage API
Globals:
Function:
Runtime: python3.13
Architectures:
- x86_64
Timeout: 3
MemorySize: 128
Resources:
IncidentFunction:
Type: AWS::Serverless::Function
Properties:
CodeUri: incident_api/
Handler: app.lambda_handler
Events:
SubmitIncident:
Type: Api
Properties:
Path: /incidents
Method: post
Outputs:
IncidentApiUrl:
Description: API Gateway URL for incident submissions
Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.${AWS::URLSuffix}/Prod/incidents"
IncidentFunctionArn:
Description: Lambda function ARN
Value: !GetAtt IncidentFunction.Arn
Template reference
This complete template defines the runtime settings plus the function resource plus the deployment outputs.
- Compare your complete incident_api/__init__.py with this reference:
"""Incident triage Lambda package."""
Package reference
This file provides the package docstring for the incident triage code.
- Compare your complete incident_api/app.py with this reference:
import json
def lambda_handler(event, context):
return {
"statusCode": 200,
"headers": {"Content-Type": "application/json"},
"body": json.dumps({"message": "incident API running"}),
}
Handler reference
This complete handler returns the fixed JSON response used for the first local test.
Run the API in Docker
SAM local emulation builds the function with the Lambda runtime inside a Docker container. The local server then maps /incidents to your handler.
- Select View from the Visual Studio Code menu bar.
- Select Terminal.
- Select PowerShell from the Launch Profile dropdown if another shell opens.
The first container build can take a few minutes while Docker downloads the matching runtime image. A longer first run is expected.
- Build the function inside its runtime container by running:
sam build --use-container
What does this build command do?
- The sam build command reads template.yaml plus the function source.
- The --use-container option builds with a Docker container that matches the Lambda runtime.
- Confirm the terminal reports that the build succeeded.
Did the container build fail?
Confirm Docker Desktop is still running. A stopped container engine prevents SAM from starting the build environment.
Check the indentation in template.yaml if SAM reports a template problem. Help me diagnose my SAM container build.
The local server keeps this terminal busy until you stop it. A second PowerShell terminal handles the test request.
- Start the Docker-backed local API by running:
sam local start-api
What does the local server do?
SAM starts an API Gateway emulator on your computer. Requests to the configured route invoke IncidentFunction inside Docker.
This gives you a local HTTP test without deploying cloud resources.
- Keep the first terminal running.
You will see the local server expose the route at http://127.0.0.1:3000/incidents.
Did the local server stop?
Confirm the build completed before starting the server. Keep Docker Desktop running throughout local emulation.
Check that no other program is already using the local server port. Help me start my SAM local API.
- Select Terminal from the Visual Studio Code menu bar.
- Select New Terminal.
- Select PowerShell from the Launch Profile dropdown if needed.
Before you send the request, do you expect the empty JSON object to reach the handler successfully?
- Send an empty JSON object to the local endpoint by running:
$body = @{} | ConvertTo-Json
Invoke-RestMethod -Method Post -Uri http://127.0.0.1:3000/incidents -ContentType "application/json" -Body $body
How does this request work?
- The first line converts an empty PowerShell hashtable into JSON.
- The second line sends an HTTP POST request to the local route.
- The content type tells the local API to treat the request body as JSON.
You made it through the whole local stack. The response contains incident API running from your Lambda handler.
Did the request fail to connect?
Confirm the first terminal is still running the local API. Confirm Docker Desktop is still active.
Check that the request URL ends with /incidents. Help me troubleshoot my local incident API request.
Your smallest API now runs through Docker-backed Lambda emulation. Next you will add incident classification before testing how the first-pass version handles incomplete input.
Make Triage Work, Then Break It
Your Docker-emulated endpoint already proves that the AWS SAM route reaches your AWS Lambda handler. Now the endpoint needs to turn incident details into an operational severity.
Perfect requests only show the happy path. This step lets a naive field lookup reveal its reliability limit when input is incomplete.
In this step, get ready to:
- Replace the fixed response with naive severity scoring.
- Confirm that a complete high-impact incident returns P1.
- Expose the missing-field reliability gap.
Add naive severity scoring
The current handler ignores the request body. The naive version trusts every incident field without checking it.
- In the Visual Studio Code Explorer sidebar, select incident_api/app.py.
- Find def lambda_handler(event, context):.
- Select the existing function through its closing }.
- Replace the selected function with the code below:
def lambda_handler(event, context):
body = json.loads(event["body"])
service = body["service"]
impact = body["impact"]
urgency = body["urgency"]
scores = {"low": 1, "medium": 2, "high": 3}
score = scores[impact] + scores[urgency]
severity = "P1" if score >= 6 else "P2" if score >= 4 else "P3"
return {
"statusCode": 201,
"headers": {"Content-Type": "application/json"},
"body": json.dumps({"service": service, "severity": severity}),
}
What does this code do?
- The json.loads(event["body"]) call turns the incoming JSON string into a Python dictionary.
- The direct lookups assume that every required field exists.
- The score combines both triage dimensions into one number.
- The severity expression maps that score to P1, P2, or P3.
- The return value creates a Lambda proxy response with status code 201.
- Save incident_api/app.py.
✔️ Awesome, I've got everything!
Your handler now matches the naive triage version.
ⓧ I'd like to double check the full code
import json
def lambda_handler(event, context):
body = json.loads(event["body"])
service = body["service"]
impact = body["impact"]
urgency = body["urgency"]
scores = {"low": 1, "medium": 2, "high": 3}
score = scores[impact] + scores[urgency]
severity = "P1" if score >= 6 else "P2" if score >= 4 else "P3"
return {
"statusCode": 201,
"headers": {"Content-Type": "application/json"},
"body": json.dumps({"service": service, "severity": severity}),
}
How is this file organised?
The import json line keeps Python's JSON tools available.
The lambda_handler function reads the incident before returning its calculated severity.
Does your handler look different?
- Keep import json as the first line of incident_api/app.py.
- Remove the previous response that returned incident API running.
Help me match my handler to the naive triage code.
Rebuild the local API
The local server uses the handler from the most recent build. Rebuilding makes your new scoring logic available to the emulated runtime.
- Stop the running local API in its PowerShell terminal.
- Rebuild the API with the updated handler by running this command:
sam build --use-container
What does this build do?
The container build copies the current handler into runtime-matched build output.
Starting the local API after this build serves your updated code.
Build did not complete?
- Confirm that Docker Desktop is still running.
- Confirm that PowerShell is inside the incident-triage-api folder.
Help me diagnose my container build failure.
- Start the rebuilt local API by running this command:
sam local start-api
What does this command do?
The sam local start-api command starts the Docker-backed local HTTP server.
The command keeps this terminal occupied while the server is running.
You'll see the local server listening on the same incidents endpoint.
- Switch back to the second PowerShell terminal from the previous step.
- Create a complete payments incident by running this command:
$body = @{ service = "payments"; impact = "high"; urgency = "high" } | ConvertTo-Json
What does this request body contain?
The PowerShell hashtable becomes the JSON body for a complete incident.
Before you resend it, consider which severity two high inputs should produce.
- Resend the POST request with the command from the previous step.
You'll see payments under service, with P1 under severity. The naive classifier now handles its ideal request.
Don't see a P1 response?
- Check that impact is high.
- Check that urgency is high.
- Confirm that the SAM terminal started after the latest build.
Help me trace why the local request does not return P1.
Break the handler with incomplete input
A reliable incident endpoint must handle the requests people actually send. This shorter payload tests the current contract with one field omitted.
- Create an incomplete payments incident by running this command:
$body = @{ service = "payments"; impact = "high" } | ConvertTo-Json
What changed in this payload?
This request still includes the service value. It deliberately leaves out urgency.
Before you resend it, consider whether the handler can process this shorter payload.
- Resend the POST request with the command from the previous step.
The request terminal shows a non-success response.
- Switch back to the PowerShell terminal running the local API.
You'll see KeyError: 'urgency' in the SAM terminal.
Why did the handler fail?
The handler uses body["urgency"] as a direct dictionary lookup. That lookup requires the key to exist.
The missing key raises an error before the handler can create an HTTP response. This is the reliability gap the naive version was meant to expose.
Don't see the missing-key error?
- Confirm that the shorter request body omits urgency.
- Resend the earlier POST command after setting the shorter body.
- Return to the PowerShell terminal running the local API.
Help me reproduce the missing urgency error.
That failure did its job: it exposed the handler's unsafe assumption. Next, you'll add request validation that keeps incomplete input inside a structured response.
Add Validation, Tests, and Logs
Your local AWS Lambda API now classifies complete incidents. The missing urgency request exposed the reliability gap at the request boundary.
This step replaces that crash with structured validation responses. You will also add automated tests plus traceable logs.
In this step, get ready to:
- Validate incomplete or unsupported incident requests.
- Prove five behaviors with automated tests.
- Rebuild the local API with structured incident logs.
Replace the naive handler with validation
Request validation checks incoming data before the scoring logic uses it. Invalid input can then produce a structured response with status code 400.
- Return to incident_api/app.py in Visual Studio Code.
- Select all of the naive handler code.
- Delete the selected code.
- Add the response foundation by pasting this code:
import json
import logging
logger = logging.getLogger(__name__)
logger.setLevel(logging.INFO)
IMPACT_SCORES = {"low": 1, "medium": 2, "high": 3}
URGENCY_SCORES = {"low": 1, "medium": 2, "high": 3}
REQUIRED_FIELDS = ("service", "impact", "urgency")
def build_response(status_code, payload):
return {
"statusCode": status_code,
"headers": {"Content-Type": "application/json"},
"body": json.dumps(payload),
}
What Does This Foundation Do?
- The logger records messages at the INFO threshold.
- The two score maps define the supported impact values plus urgency values.
- The REQUIRED_FIELDS tuple lists the values every incident needs.
- The build_response() helper gives every response the same proxy-compatible shape.
- Save incident_api/app.py.
- Confirm the file now contains build_response() below the three validation constants.
Seeing a Syntax Warning?
- Check that each response dictionary line remains inside build_response().
- Check that the closing braces match the reference above.
Help me check the response foundation.
- Add the reusable severity logic below build_response() by pasting this code:
def classify_severity(impact, urgency):
score = IMPACT_SCORES[impact] + URGENCY_SCORES[urgency]
if score >= 6:
return "P1"
if score >= 4:
return "P2"
return "P3"
How Is Severity Classified?
- The helper adds the impact score to the urgency score.
- A total score of 6 produces P1.
- A total score of at least 4 produces P2.
- Every lower supported score produces P3.
- Save incident_api/app.py.
- Confirm classify_severity() appears as a separate function below build_response().
Is the Helper Nested?
- Align def classify_severity with def build_response.
- Keep each return statement indented inside its matching condition.
Help me fix the severity helper indentation.
- Add the request-boundary checks below classify_severity() by pasting this code:
def lambda_handler(event, context):
body = event.get("body")
if not body:
return build_response(400, {"error": "Request body is required"})
try:
payload = json.loads(body)
except (json.JSONDecodeError, TypeError):
return build_response(400, {"error": "Request body must be valid JSON"})
if not isinstance(payload, dict):
return build_response(400, {"error": "Request body must be a JSON object"})
missing_fields = [field for field in REQUIRED_FIELDS if not payload.get(field)]
if missing_fields:
return build_response(
400,
{"error": "Missing required fields", "fields": missing_fields},
)
How Does the Boundary Stay Safe?
- The first check catches requests without a body.
- The exception handler catches malformed JSON input.
- The type check requires the decoded value to be an object.
- The missing_fields list reports every required field without a usable value.
- Save incident_api/app.py.
- Confirm the handler now contains four structured 400 response paths.
Are the Boundary Checks Misaligned?
- Keep every boundary check inside lambda_handler().
- Keep the final 400 response inside the if missing_fields block.
Help me inspect the request-boundary checks.
- Continue lambda_handler() by pasting this code directly below the missing-field response:
service = str(payload["service"]).strip()
impact = str(payload["impact"]).strip().lower()
urgency = str(payload["urgency"]).strip().lower()
if impact not in IMPACT_SCORES:
return build_response(
400,
{"error": "impact must be low, medium, or high"},
)
if urgency not in URGENCY_SCORES:
return build_response(
400,
{"error": "urgency must be low, medium, or high"},
)
severity = classify_severity(impact, urgency)
request_id = getattr(context, "aws_request_id", "local-test")
What Gets Normalized?
- The service value loses surrounding whitespace.
- The impact value becomes lowercase before validation.
- The urgency value becomes lowercase before validation.
- Unsupported values receive structured 400 responses.
- The request_id comes from the invocation context.
- Save incident_api/app.py.
- Confirm the normalized values appear before the severity calculation.
Are Valid Values Being Rejected?
- Check that impact uses IMPACT_SCORES.
- Check that urgency uses URGENCY_SCORES.
- Keep the calls to strip() plus lower() in the order shown.
Help me compare the normalization checks.
- Finish lambda_handler() by pasting this code below the request ID assignment:
logger.info(
json.dumps(
{
"event": "incident_triaged",
"request_id": request_id,
"service": service,
"impact": impact,
"urgency": urgency,
"severity": severity,
},
sort_keys=True,
)
)
return build_response(
201,
{
"request_id": request_id,
"service": service,
"severity": severity,
},
)
What Gets Recorded?
- The incident_triaged event identifies a completed classification.
- The log records the request ID plus normalized incident values.
- The log records the assigned severity.
- The success response returns status code 201.
- The response includes the same request ID used by the log.
- Save incident_api/app.py.
- Confirm the final return block contains request_id plus severity.
Is the Final Block Outside the Handler?
- Keep the logging call indented inside lambda_handler().
- Keep the final return block aligned with logger.info().
Help me check the structured log plus final response.
✔️ Awesome, I've got everything!
Your validated handler is assembled. Make sure incident_api/app.py is saved.
ⓧ I'd like to double check the full code
Compare incident_api/app.py with this complete reference.
import json
import logging
logger = logging.getLogger(__name__)
logger.setLevel(logging.INFO)
IMPACT_SCORES = {"low": 1, "medium": 2, "high": 3}
URGENCY_SCORES = {"low": 1, "medium": 2, "high": 3}
REQUIRED_FIELDS = ("service", "impact", "urgency")
def build_response(status_code, payload):
return {
"statusCode": status_code,
"headers": {"Content-Type": "application/json"},
"body": json.dumps(payload),
}
def classify_severity(impact, urgency):
score = IMPACT_SCORES[impact] + URGENCY_SCORES[urgency]
if score >= 6:
return "P1"
if score >= 4:
return "P2"
return "P3"
def lambda_handler(event, context):
body = event.get("body")
if not body:
return build_response(400, {"error": "Request body is required"})
try:
payload = json.loads(body)
except (json.JSONDecodeError, TypeError):
return build_response(400, {"error": "Request body must be valid JSON"})
if not isinstance(payload, dict):
return build_response(400, {"error": "Request body must be a JSON object"})
missing_fields = [field for field in REQUIRED_FIELDS if not payload.get(field)]
if missing_fields:
return build_response(
400,
{"error": "Missing required fields", "fields": missing_fields},
)
service = str(payload["service"]).strip()
impact = str(payload["impact"]).strip().lower()
urgency = str(payload["urgency"]).strip().lower()
if impact not in IMPACT_SCORES:
return build_response(
400,
{"error": "impact must be low, medium, or high"},
)
if urgency not in URGENCY_SCORES:
return build_response(
400,
{"error": "urgency must be low, medium, or high"},
)
severity = classify_severity(impact, urgency)
request_id = getattr(context, "aws_request_id", "local-test")
logger.info(
json.dumps(
{
"event": "incident_triaged",
"request_id": request_id,
"service": service,
"impact": impact,
"urgency": urgency,
"severity": severity,
},
sort_keys=True,
)
)
return build_response(
201,
{
"request_id": request_id,
"service": service,
"severity": severity,
},
)
What Should Match?
This reference contains the complete validation path. It also contains the structured success log plus response.
Add five unit tests
Unit tests call the handler with controlled inputs. They prove the validation behavior without sending an HTTP request.
- Create a tests folder inside incident-triage-api using the Visual Studio Code file sidebar.
You should now see tests beside incident_api.
- Create tests/__init__.py inside the tests folder.
- Add the test package description by pasting this code:
"""Tests for the incident triage API."""
Why Add This File?
The docstring identifies tests as the test package for this project.
- Save tests/__init__.py.
- Confirm the new file appears inside tests.
Is the Package File Missing?
- Check that the filename begins with two underscore characters.
- Check that the filename ends with two underscore characters before .py.
Help me check the test package file.
- Create tests/test_app.py inside the tests folder.
- Add the test harness plus severity test by pasting this code:
import json
import unittest
from incident_api.app import classify_severity, lambda_handler
class FakeContext:
aws_request_id = "test-request-id"
class IncidentApiTests(unittest.TestCase):
def invoke(self, payload):
event = {"body": json.dumps(payload)}
return lambda_handler(event, FakeContext())
def test_classify_medium_incident_as_p2(self):
self.assertEqual(classify_severity("medium", "medium"), "P2")
How Does the Test Harness Work?
- The imports load Python's standard JSON plus testing modules.
- The FakeContext class supplies a predictable request ID.
- The invoke() helper packages each payload like an API request body.
- The first test proves that medium impact plus medium urgency produces P2.
- Save tests/test_app.py.
- Confirm FakeContext appears above IncidentApiTests.
Are the Tests Outside the Class?
- Keep invoke() indented inside IncidentApiTests.
- Keep the first test aligned with invoke().
Help me check the test harness structure.
- Add the valid-request test plus missing-field test below the first test by pasting this code:
def test_valid_high_incident_returns_p1(self):
response = self.invoke(
{"service": "payments", "impact": "high", "urgency": "high"}
)
body = json.loads(response["body"])
self.assertEqual(response["statusCode"], 201)
self.assertEqual(body["severity"], "P1")
self.assertEqual(body["request_id"], "test-request-id")
def test_missing_field_returns_400(self):
response = self.invoke({"service": "payments", "impact": "high"})
body = json.loads(response["body"])
self.assertEqual(response["statusCode"], 400)
self.assertEqual(body["fields"], ["urgency"])
What Do These Tests Prove?
- A complete high-impact incident returns status code 201.
- The complete incident receives severity P1.
- The response carries the request ID from FakeContext.
- A missing urgency field returns a structured 400 response.
- Save tests/test_app.py.
- Confirm the file now contains three methods whose names start with test_.
Is One Test Missing?
- Keep both new methods inside IncidentApiTests.
- Keep each assertion inside its matching test method.
Help me compare the valid plus missing-field tests.
- Finish the suite with the invalid-impact plus malformed-JSON tests by pasting this code:
def test_invalid_impact_returns_400(self):
response = self.invoke(
{"service": "payments", "impact": "extreme", "urgency": "high"}
)
body = json.loads(response["body"])
self.assertEqual(response["statusCode"], 400)
self.assertIn("impact", body["error"])
def test_malformed_json_returns_400(self):
response = lambda_handler({"body": "{"}, FakeContext())
body = json.loads(response["body"])
self.assertEqual(response["statusCode"], 400)
self.assertEqual(body["error"], "Request body must be valid JSON")
if __name__ == "__main__":
unittest.main()
Which Failures Are Covered?
- The invalid-impact test submits a value outside the supported score map.
- The malformed-input test sends incomplete JSON syntax.
- Both tests require status code 400.
- The final block lets Python run the file as a test program.
- Save tests/test_app.py.
- Confirm the file now contains five methods whose names start with test_.
Is Test Discovery Skipping Methods?
- Check that every test method starts with test_.
- Check that all five test methods remain inside IncidentApiTests.
Help me inspect the final two tests.
✔️ Awesome, I've got everything!
Your test package is complete. Make sure both files inside tests are saved.
ⓧ I'd like to double check the full code
Compare tests/__init__.py with this reference.
"""Tests for the incident triage API."""
What Should Match?
The package file contains one descriptive docstring.
Compare tests/test_app.py with this complete reference.
import json
import unittest
from incident_api.app import classify_severity, lambda_handler
class FakeContext:
aws_request_id = "test-request-id"
class IncidentApiTests(unittest.TestCase):
def invoke(self, payload):
event = {"body": json.dumps(payload)}
return lambda_handler(event, FakeContext())
def test_classify_medium_incident_as_p2(self):
self.assertEqual(classify_severity("medium", "medium"), "P2")
def test_valid_high_incident_returns_p1(self):
response = self.invoke(
{"service": "payments", "impact": "high", "urgency": "high"}
)
body = json.loads(response["body"])
self.assertEqual(response["statusCode"], 201)
self.assertEqual(body["severity"], "P1")
self.assertEqual(body["request_id"], "test-request-id")
def test_missing_field_returns_400(self):
response = self.invoke({"service": "payments", "impact": "high"})
body = json.loads(response["body"])
self.assertEqual(response["statusCode"], 400)
self.assertEqual(body["fields"], ["urgency"])
def test_invalid_impact_returns_400(self):
response = self.invoke(
{"service": "payments", "impact": "extreme", "urgency": "high"}
)
body = json.loads(response["body"])
self.assertEqual(response["statusCode"], 400)
self.assertIn("impact", body["error"])
def test_malformed_json_returns_400(self):
response = lambda_handler({"body": "{"}, FakeContext())
body = json.loads(response["body"])
self.assertEqual(response["statusCode"], 400)
self.assertEqual(body["error"], "Request body must be valid JSON")
if __name__ == "__main__":
unittest.main()
What Should Match?
This reference contains the helper plus all five test methods.
Before you run the suite, which result do you expect from the five cases?
- Return to the PowerShell terminal inside incident-triage-api.
- Run the five tests with this command:
python -m unittest -v
What Should I See?
You should see five passing tests. The output ends with OK.
The suite now protects the missing-field behavior that crashed earlier.
Tests Not Passing?
- Check that the terminal is inside incident-triage-api.
- Check that the test file is named tests/test_app.py.
- Compare the first failing test with the complete references above.
Help me diagnose my failing incident API tests.
That reliability gap is now covered. Five tests prove that valid input succeeds while unsafe input receives structured responses.
Rebuild and verify the local API
The tests prove the Python behavior directly. AWS SAM now rebuilds the same handler inside the configured runtime container.
- Stop the earlier local API process in its terminal.
- Switch back to the PowerShell terminal inside incident-triage-api.
- Rebuild the validated application with Docker by running this command:
sam build --use-container
What Does This Build Prove?
The command builds the function inside a container containing its configured runtime. A successful build confirms that SAM can package the final handler.
The first container build can take a little longer while Docker prepares the runtime image.
- Confirm the terminal reports a successful build.
Build Not Completing?
- Confirm Docker Desktop is still running.
- Confirm template.yaml remains inside incident-triage-api.
- Confirm incident_api/app.py is saved.
Help me troubleshoot the container build.
- Start the rebuilt local API by running this command:
sam local start-api
What Is Running Now?
SAM starts the API route on its local Docker-backed server. Keep this terminal running while you send the verification request.
- Switch back to the second PowerShell terminal from earlier.
Before you send the request, which response fields should prove that classification plus request tracing work?
- Submit a complete payments incident to the local endpoint by running this code:
$body = @{ service = "payments"; impact = "high"; urgency = "high" } | ConvertTo-Json
Invoke-RestMethod -Method Post -Uri http://127.0.0.1:3000/incidents -ContentType "application/json" -Body $body
What Should I See?
You should see severity set to P1. You should also see a request_id in the response.
The SAM terminal should show the matching incident_triaged log. The request ID connects the response to that invocation.
Missing the Request ID?
- Confirm the latest SAM build completed after you saved incident_api/app.py.
- Confirm the local API restarted after the build.
- Compare the final response block with the full handler reference.
Help me trace the missing local response fields.
Your API now handles incomplete requests safely. Next, you will deploy this tested version to AWS plus observe a cloud invocation.
Deploy and Observe the AWS API
Your local API now validates requests. It also classifies a complete incident as P1 inside Docker.
A local container cannot prove that cloud permissions work. It also cannot prove that managed services connect correctly.
In this step, AWS SAM deploys the tested handler to AWS Lambda through AWS CloudFormation.
Amazon API Gateway exposes the live route. Amazon CloudWatch Logs gives you evidence that each request reached the handler.
In this step, get ready to:
- Deploy the tested API with AWS SAM.
- Send a valid incident to the public endpoint.
- Correlate the response with its structured CloudWatch log event.
Deploy the tested stack
The guided deployment uploads your built artifacts to Amazon S3. AWS CloudFormation uses those artifacts to create one managed stack.
Deploying cloud resources can feel like a billing risk. This short exercise is expected to stay within the credits and service allowances of your AWS Free Plan.
The deployed endpoint is public. Keep using fabricated incident data throughout the demonstration.
- Return to the PowerShell terminal running your local API.
- Press Ctrl+C to stop the local API.
- Start the guided cloud deployment by running this command:
sam deploy --guided
What does this command do?
- The --guided option starts the first-deployment questionnaire.
- AWS SAM uploads the application artifacts before asking AWS CloudFormation to deploy them.
- Saving the answers writes future deployment settings to samconfig.toml.
- Enter incident-triage-api as the stack name.
- Enter us-east-1 as the Region.
- Allow AWS SAM to create AWS Identity and Access Management roles.
- Keep rollback enabled.
- Acknowledge that the endpoint is public.
- Save the deployment settings.
The first cloud deployment can take several minutes while AWS creates each resource. Progress events in PowerShell mean the deployment is still working.
- Wait for PowerShell to return to its prompt after the deployment completes.
- Find IncidentApiUrl in the deployment outputs.
- Record the endpoint here: https://your-api-id.execute-api.us-east-1.amazonaws.com/Prod/incidents.
That is the cloud foundation in place. Your Lambda function now has a public API route backed by a managed stack.
Deployment stopped?
- Repeat the temporary browser sign-in from Step 1 if your AWS session has expired.
- Rerun the guided deployment if you did not allow AWS SAM to create IAM roles.
- Confirm that Docker Desktop is still running if the deployment rebuilds your application.
Help me troubleshoot the AWS SAM deployment.
Call the deployed endpoint
The live endpoint uses the same Lambda proxy contract you tested locally. A successful cloud request proves that API Gateway can invoke your deployed handler.
- Return to the request PowerShell terminal from the local test.
- Prepare the deployed request variables by running these commands:
$apiUrl = "[[INCIDENT_API_URL=\"https://your-api-id.execute-api.us-east-1.amazonaws.com/Prod/incidents\"]]"
$body = @{ service = "payments"; impact = "high"; urgency = "high" } | ConvertTo-Json
What do these variables hold?
- The $apiUrl variable holds the public incident route from the deployment output.
- The $body variable converts your fabricated payments incident into JSON.
Before you send the request, do you expect its severity to match the local P1 result?
- Submit the incident to the deployed API by running this command:
Invoke-RestMethod -Method Post -Uri $apiUrl -ContentType "application/json" -Body $body
What does this request prove?
PowerShell sends the JSON body through API Gateway to Lambda. The handler returns its successful 201 response contract.
You should see payments as the service. You should also see P1 as the severity.
The response also contains a cloud-generated request_id. That identifier gives you a precise thread to follow into the logs.
Live request failed?
- Confirm that the copied endpoint ends with /Prod/incidents.
- Rerun the request-variable commands if your current PowerShell terminal does not contain $apiUrl.
- Check the deployment output again if the URL was copied without its full path.
Help me troubleshoot the deployed API request.
Trace the request through CloudWatch Logs
Lambda sends your structured logger output to CloudWatch Logs. Matching the response ID with the log ID proves that both records describe the same invocation.
- Return to the PowerShell terminal used for deployment.
- Start tailing the deployed function logs by running this command:
sam logs -n IncidentFunction --stack-name incident-triage-api --tail
How does log tailing work?
- The -n IncidentFunction option selects the Lambda function by its logical ID.
- The --stack-name incident-triage-api option scopes the search to your deployed stack.
- The --tail option keeps the stream attached so new events can appear.
- Leave the log terminal running.
- Switch back to the request PowerShell terminal from earlier.
Before you submit the incident again, do you expect the cloud log's request ID to match the new response?
- Create one more cloud invocation by running this command:
Invoke-RestMethod -Method Post -Uri $apiUrl -ContentType "application/json" -Body $body
Why send another request?
This request creates a fresh invocation while the log stream is active. The new response gives you one specific event to trace.
- Record the new request_id here: your latest request ID.
- Return to the PowerShell terminal tailing the logs.
- Find the newest incident_triaged event.
- Compare the logged request_id with your latest request ID.
You will see the same request ID in both places. The structured event also contains "severity": "P1".
- Press Ctrl+C in the log terminal to stop tailing.
Log event missing?
- Leave the log tail running for a few seconds because log delivery can lag briefly.
- Confirm that the command uses IncidentFunction as the function logical ID.
- Submit the deployed request again if the tail started after the previous invocation.
Help me find the matching CloudWatch log event.
You have completed the cloud feedback loop. Your tested API is live. Its P1 response can now be traced to one structured application event.
Secret mission
Run a P1 Observability Drill
Generate a stream of deployed incidents with P1, P2, and P3 responses. Filter the cloud logs to isolate the critical event. Then correlate that event with its API response.
Clean Up Your Resources
Clean Up Your Resources
Choose whether to keep the deployment available or remove it when you finish. This lab is expected to remain at $0 on a new AWS Free Plan while credits and service allowances remain.
Cost warning
The endpoint remains public while the stack exists. Keep using fabricated incident data.
Paid-only features or usage beyond available credits can create charges. Pause your local tools if you plan to return soon. Delete the cloud stack if you are finished.
Resources you used:
- An AWS CloudFormation stack named incident-triage-api.
- An AWS Lambda function identified by the logical ID IncidentFunction.
- An IAM execution role generated for the Lambda function.
- An Amazon API Gateway REST API identified by the logical ID ServerlessRestApi.
- An Amazon CloudWatch Logs log group containing the incident drill events.
- Deployment artifacts uploaded by AWS SAM to Amazon S3.
- A temporary browser-authenticated AWS CLI session.
- A Docker Desktop process capable of running the local API.
Keep everything running
No action is needed. Choose this if you want to keep demonstrating or testing the incident-triage API.
- Leave the incident-triage-api stack deployed.
- Keep the local incident-triage-api source folder on your computer.
- Monitor your remaining Free Plan credits in the AWS console.
- Use fabricated incident data for every request.
- Keep the public endpoint stored in $apiUrl private.
Pause - I'll come back to this later
Shut down the local processes to free system memory. Keep the cloud deployment available for your next session.
- Stop the local API by pressing Ctrl+C in its PowerShell terminal if it is still running.
- Exit Docker Desktop from its taskbar menu.
- Avoid sending more requests to the public endpoint stored in $apiUrl.
- Continue monitoring your remaining Free Plan credits in the AWS console.
The cloud stack remains deployed. The existing log data also remains in AWS.
Delete - I don't want to use this again
Deleting the cloud resources is permanent. Your tested source files in incident-triage-api stay on your computer.
- Stop the local API by pressing Ctrl+C in its PowerShell terminal if it is still running.
- Switch back to PowerShell in the incident-triage-api project root.
- Delete the deployed stack and its SAM artifacts by running this command:
sam delete
What does this remove?
The command deletes the CloudFormation stack. This removes the Lambda function, its IAM execution role, and the API Gateway REST API.
The command also deletes the SAM deployment artifacts from Amazon S3.
- Approve each deletion confirmation in PowerShell.
- Wait for the deletion process to finish.
You'll see PowerShell confirm that the stack was deleted. That's the cloud deployment cleared from your account.
Stack deletion not starting?
- Confirm that PowerShell is still inside the incident-triage-api project root.
- Refresh the temporary browser login from Step 1 if your AWS session has expired.
- Check that the AWS console is using us-east-1.
- Get help diagnosing the deletion problem.
Why are logs still present?
Lambda log groups are not automatically deleted when a function is deleted. The incident drill logs therefore need a separate cleanup action.
- Return to the AWS console from earlier.
- Set the console Region to us-east-1.
- Go to Amazon CloudWatch Logs.
- Filter the log-group list with incident-triage-api.
- Open the log group containing the incident_triaged events from your drill.
- Delete the selected log group.
Your P1, P2, and P3 drill events are now removed from CloudWatch Logs.
Your browser-authenticated CLI session can remain cached after resource deletion. Signing out removes those temporary credentials from the AWS CLI.
- Remove the cached temporary session by running this command:
aws logout
What does logout remove?
This removes the cached temporary credentials from the AWS CLI. Your AWS account still exists.
- Wait for PowerShell to return to its prompt without an error.
- Exit Docker Desktop from its taskbar menu.
Your temporary CLI session is closed. Your final source files and five unit tests remain in the local incident-triage-api folder.
Nice Work!
Nice Work!
You did it! You built a tested Python incident-triage API with local emulation powered by Docker Desktop. You also deployed the same API to AWS Lambda behind Amazon API Gateway.
You've learned how to:
- Build a validated Lambda proxy handler that classifies incidents as P1, P2, or P3. It returns structured errors for incomplete input or malformed input.
- Use Docker Desktop with AWS SAM to emulate the Lambda runtime locally. Five automated tests prove the handler's expected behavior.
- Deploy infrastructure as code through AWS CloudFormation. Match API request IDs to structured events in Amazon CloudWatch Logs.
- Secret Mission: Generated a mixed P1, P2, and P3 incident stream. Isolated the P1 incident_triaged event in CloudWatch Logs. Used its request_id to correlate the API response with the cloud log event.
Ready to quiz yourself?