Infrastructure as Code for an AI App with the AWS CDK
Learn how to describe an AI app as code with the AWS CDK in Python. Build a Lambda and API Gateway stack that calls Amazon Bedrock, preview changes with cdk diff, deploy it repeatably, and follow safe habits for infrastructure as code.
Describe your AI app's cloud resources in Python, preview every change, and deploy the same stack again and again, in any account, without clicking through a console.
In the last few articles we built resources by hand: buckets, tables, functions, an API. That's fine for learning. It stops working the moment you need to do it twice. A second environment, a teammate's copy, a rebuild after a mistake: each one means remembering every setting you clicked.
Infrastructure as code fixes that. You describe the resources in a file, keep the file in version control, and let a tool create them. The AWS Cloud Development Kit (CDK) lets you write that description in a normal programming language. In this article we'll rebuild the API from the previous article, a Lambda function that calls Amazon Bedrock behind API Gateway, as a CDK app in Python.
What you need
- An AWS account, with the AWS CLI configured
- Node.js, which the CDK command-line tool needs, and Python 3
- The Lambda function code from the previous article, saved as
app.py
- A Bedrock model ID from the model's documentation
- About an hour
Deploying creates real resources and an endpoint that anyone with the key can call. Set a budget alert first, and finish with the clean-up step.
How CDK works
The idea is simple:
- You write constructs in Python. A construct is a building block, such as a function, a bucket or an API.
- CDK synthesises them into a CloudFormation template, which is AWS's own format for describing resources.
- CloudFormation deploys that template, creating or changing resources to match.
So CDK isn't a separate deployment system. It's a friendlier way to write CloudFormation, with the benefit that you get loops, functions, types and reuse.
It sits alongside other options you may meet: writing CloudFormation directly, using the AWS SAM shorthand from the last article, or using tools from other vendors. CDK tends to appeal to developers who'd rather write code than YAML.
Step 1: Install and set up
Bash
npm install -g aws-cdkcdk --version
Create a project:
Bash
mkdir ai-api && cd ai-apicdk init app --language pythonpython3 -m venv .venvsource .venv/bin/activatepip install -r requirements.txt
This generates a starter project. The parts we care about are:
Bash
ai-api/app.py the entry pointai_api/ai_api_stack.py where our resources will gorequirements.txtcdk.json
Now add a folder called
lambda and put the function code from the previous article in it as lambda/app.py .Step 2: Bootstrap your account (once)
The first time you use CDK in an account and Region, run:
Bash
cdk bootstrap
This creates a few supporting resources, including a storage bucket for deployment files and some IAM roles. It's a one-off setup per account and Region, and it's why CDK can upload your function code for you.
Step 3: Describe the stack
Open
ai_api/ai_api_stack.py and replace its contents with this:Python
from aws_cdk import (CfnOutput,Duration,RemovalPolicy,Stack,Tags,aws_apigateway as apigw,aws_iam as iam,aws_lambda as _lambda,aws_logs as logs,)from constructs import Constructclass AiApiStack(Stack):def __init__(self, scope: Construct, construct_id: str, **kwargs) -> None:super().__init__(scope, construct_id, **kwargs)# The model is passed in at deploy time, not hard-coded.model_id = self.node.try_get_context("model_id")if not model_id:raise ValueError("Pass the model with: cdk deploy -c model_id=YOUR_MODEL_ID")# A log group with a set retention, so logs don't pile up forever.log_group = logs.LogGroup(self,"AskLogs",retention=logs.RetentionDays.ONE_MONTH,removal_policy=RemovalPolicy.DESTROY,)fn = _lambda.Function(self,"AskFunction",runtime=_lambda.Runtime.PYTHON_3_12,handler="app.handler",code=_lambda.Code.from_asset("lambda"),timeout=Duration.seconds(25),memory_size=256,environment={"MODEL_ID": model_id},log_group=log_group,)# One action, on one model. Nothing else.fn.add_to_role_policy(iam.PolicyStatement(actions=["bedrock:InvokeModel"],resources=[f"arn:aws:bedrock:{self.region}::foundation-model/{model_id}"],))api = apigw.RestApi(self,"AskApi",rest_api_name="ask-api",deploy_options=apigw.StageOptions(stage_name="prod"),)ask = api.root.add_resource("ask")ask.add_method("POST", apigw.LambdaIntegration(fn), api_key_required=True)# Throttling and a daily quota, tied to an API key.plan = api.add_usage_plan("AskPlan",name="ask-api-plan",throttle=apigw.ThrottleSettings(rate_limit=2, burst_limit=5),quota=apigw.QuotaSettings(limit=1000, period=apigw.Period.DAY),)key = api.add_api_key("AskKey")plan.add_api_key(key)plan.add_api_stage(stage=api.deployment_stage)# Label everything so costs can be tracked.Tags.of(self).add("Application", "AskApi")CfnOutput(self, "Endpoint", value=api.url_for_path("/ask"))CfnOutput(self, "ApiKeyId", value=key.key_id)
Compare this with the SAM template from the last article. It's the same stack, but now in a language with a type checker and editor completion. The things to notice:
- The model ID is a deployment setting, passed on the command line. The same code can deploy a different model to a test or production account.
- The permission is built from the same variable, so the policy and the function can't drift apart.
- Log retention is set in code, so no one has to remember to do it in the console.
- Tags are applied to the whole stack in one line.
- A usage plan and API key do the throttling, exactly as before.
A note on versions: CDK changes often, and some arguments get renamed. This example uses a
log_group argument on the function, which recent CDK versions support. If your version complains, check the current CDK reference for the equivalent.The starter project's
app.py already creates the stack. Check that it looks like this, and remove any env lines you don't want:Python
import aws_cdk as cdkfrom ai_api.ai_api_stack import AiApiStackapp = cdk.App()AiApiStack(app, "AiApiStack")app.synth()
Step 4: Preview before you deploy
This is one of the best habits CDK gives you. First, check it builds:
Bash
cdk synth -c model_id=YOUR_MODEL_ID
Then see exactly what would change in your account:
Bash
cdk diff -c model_id=YOUR_MODEL_ID
The diff lists resources that would be added, changed or removed. For anything touching permissions, it also shows a table of IAM changes. Read that table every time. It's the quickest way to catch a policy that's broader than you intended.
Step 5: Deploy
Bash
cdk deploy -c model_id=YOUR_MODEL_ID
CDK shows the security-related changes and asks you to confirm. Say yes, and after a few minutes it prints the outputs: the endpoint URL and the API key's ID.
To get the key's value:
Bash
aws apigateway get-api-key --api-key YOUR_KEY_ID --include-value
Then test it with the same curl command from the previous article.
Step 6: Change something, and see the diff
Here's where it pays off. Say you want to tighten the rate limit. Change
rate_limit=2 to rate_limit=1 in the stack file, then run:Bash
cdk diff -c model_id=YOUR_MODEL_ID
You'll see a single, small change to the usage plan. Deploy it, and that's it. No clicking, and a record in your version history of what changed and why.
Try something bigger too: add a second environment by deploying the stack under a different name or in another account. Because it's code, the second copy is the same as the first.
Good habits for CDK projects
Keep it in version control. The stack file is your documentation of what exists. Commit it, and review changes like any other code.
Don't hard-code account details or secrets. Pass settings in as context values or read them from the environment. Never put keys in the stack file.
Read every diff. Especially the IAM changes and anything marked as a replacement or deletion.
Avoid changing deployed resources by hand. A manual change in the console makes your code and the real world disagree, which is called drift. If you must change something quickly, copy the change back into the code.
Protect data resources. For anything that holds data you can't recreate, such as a database or bucket, set a retention policy so it isn't deleted when you destroy the stack. Our log group above is set to be deleted, which suits a test stack.
Check for common security mistakes automatically. Tools such as cdk-nag can scan your stack against a set of best-practice rules and flag problems before you deploy. It's worth adding once your stacks grow.
Pin your versions. Keep the CDK library version in
requirements.txt , and upgrade deliberately, because new versions occasionally change behaviour.Split big stacks sensibly. Put things that change together in the same stack, and keep long-lived resources, such as storage, separate from things you redeploy often.
What it costs
CDK itself is free, and so is CloudFormation for normal AWS resources. You pay for the resources it creates and for the small bootstrap bucket. The same cost controls from the earlier articles apply: the usage plan, the token cap, budget alerts and tags, all of which are now in code.
Common mistakes
- Forgetting to bootstrap. The first deploy fails with a message about missing resources.
- Skipping
cdk diff. Surprises tend to appear in IAM.
- Editing resources in the console and then wondering why the next deploy changes them back.
- Hard-coding the model ID or account number. Use context values.
- Deleting a stack that holds data you needed, because the resource wasn't protected.
- Mixing CDK versions between the CLI and the library.
- Committing secrets.
Clean up
When you're done, remove everything the stack created:
Bash
cdk destroy -c model_id=YOUR_MODEL_ID
Then check your account for anything left behind. The bootstrap resources stay, since other stacks may use them, and they cost very little. Look at your bill after a day or two.
A checklist
- Is the stack in version control?
- Does the model ID come from deployment settings, not the code?
- Did you read the
cdk diffoutput, including the IAM table?
- Are permissions limited to what each resource needs?
- Are log retention and tags set in code?
- Do data resources have a retention policy?
- Do you have a budget alert and a way to destroy the stack?
Where we go next
With the stack described as code, the next question is cost: how to keep an eye on spending and spot surprises. In the next article we'll look at AWS Cost Explorer and Cost Anomaly Detection in more depth.
CDK versions, construct options, bootstrap behaviour and pricing change often. Check the current AWS CDK documentation before you follow any step here, and test everything in a non-production account first.
Enjoyed reading this content? Let the author know!
Your likes, comments, shares, and saves help creators reach more builders.
Loading recommendations
Loading article