以 CDK Pipelines 零手動步驟部署 Claude AI 編碼助手
CDK Pipelines for AI Agents: Deploying a Claude‑Powered Coding Assistant with Zero Manual Steps
CDK Pipelines 可將 Claude 編碼助手作為基礎設施即程式碼管理,自動完成 Docker 映像建置、測試及部署。流程由 CodePipeline 協調,並包括 ECS/Fargate 執行資源及存放 Claude API key 的 Secrets Manager,令每次執行可重複、受版本控制並可審核。
Deploying AI coding assistants usually means juggling Docker builds, secret management, and ad‑hoc scripts. With CDK Pipelines you can treat the entire Claude‑based agent as infrastructure code and let AWS handle the build, test, and deploy phases automatically. This post walks you through a fully automated, reproducible workflow that any team can adopt.
Why CDK Pipelines Matter for AI Agents
When you ship a traditional web service, you already know the value of Infrastructure as Code (IaC) – a practice where you describe cloud resources in a programming language instead of clicking around the console.
CDK Pipelines is an IaC pattern built on top of the AWS Cloud Development Kit (CDK). It gives you a ready‑made CodePipeline (a series of automated steps) that knows how to:
- Pull source code from a repository.
- Build artifacts (like Docker images).
- Run tests.
- Deploy to the target environment.
Why does this matter for AI agents?
An AI coding assistant is more than a piece of Python or Node code. It usually consists of:
- A Docker container that bundles the Claude SDK and a small CLI.
- Secrets (API keys) that must stay hidden.
- Runtime resources (ECS/Fargate) that host the container.
If you manage each of those pieces by hand, you end up with:
- Inconsistent builds – one developer may forget to update the secret, another may use a stale Docker cache.
- Hard‑to‑audit changes – you cannot trace which version of the Claude model was used for a given deployment.
- Longer onboarding – new teammates copy scripts and hope they work.
Treating the whole assistant as a single pipeline eliminates these problems. The pipeline becomes the single source of truth, and every run is repeatable, version‑controlled, and auditable.
Key takeaway: CDK Pipelines turn the “run‑once” steps of building and deploying an AI agent into a reliable, version‑controlled process that anyone can trigger.
A simple analogy
Think of a pipeline like a bakery assembly line. The source stage is the delivery truck bringing flour, the build stage is the mixer turning flour into dough, the test stage is the quality inspector, and the deploy stage is the oven that bakes the bread and puts it on the shelf. If you skip any station or let a worker improvise, the loaf may be undercooked or miss a crucial ingredient. CDK Pipelines make sure every loaf follows the same recipe, every time.
Setting Up the Claude Coding Assistant Project
Before we write any CDK code, we need a local project that represents the assistant. The goal is to keep it tiny so the example stays approachable.
1. Create a new folder
mkdir claude-coding-assistant
cd claude-coding-assistant
2. Initialize a TypeScript CDK app
npm init -y # creates package.json
npm install aws-cdk-lib constructs # CDK core libraries
npm install -D typescript ts-node # development tools
npx cdk init app --language=typescript
The cdk init command scaffolds a CDK app – a Node project where each file is a construct (a reusable piece of cloud infrastructure).
3. Add the Claude CLI wrapper
Create a folder cli/ and a tiny Node script that calls Claude:
mkdir cli
cat > cli/index.ts <<'EOF'
#!/usr/bin/env node
import { ClaudeClient } from "@anthropic-ai/sdk"; // pretend SDK
import * as readline from "readline";
const client = new ClaudeClient({ apiKey: process.env.CLAUDE_API_KEY! });
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
});
rl.question("Give me a function description: ", async (desc) => {
const response = await client.complete({
prompt: `Write a JavaScript function that ${desc}`,
max_tokens: 200,
});
console.log("\n--- Generated code ---\n");
console.log(response.completion);
rl.close();
});
EOF
chmod +x cli/index.ts
Tip: The script reads a description from the terminal, asks Claude for code, and prints the result. In a real product you would wrap this in a proper CLI library, but the above is enough for the demo.
4. Dockerfile
The Docker image will contain Node, the Claude SDK, and our script.
# Dockerfile
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build # compile TypeScript
FROM node:20-alpine
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
ENTRYPOINT ["node", "dist/cli/index.js"]
In plain English: We first compile the TypeScript code (
npm run build) in a temporary image, then copy the compiled output into a small runtime image. This two‑stage build keeps the final image lightweight.
5. Commit everything
git init
git add .
git commit -m "Initial Claude assistant skeleton"
Now the repository is ready to be connected to a GitHub source stage.
Defining the CDK Pipeline Stack
With the project source ready, we can describe the whole pipeline in CDK. The stack will contain:
- CodePipeline – the orchestrator.
- CodeBuild – builds the Docker image and runs a quick Claude prompt test.
- ECS Fargate Service – runs the container in production.
- Secrets Manager – stores the Claude API key safely.
1. Install additional CDK libraries
npm install @aws-cdk/aws-codepipeline @aws-cdk/aws-codepipeline-actions \
@aws-cdk/aws-codebuild @aws-cdk/aws-ecs @aws-cdk/aws-ecs-patterns \
@aws-cdk/aws-secretsmanager
2. Create pipeline-stack.ts
// lib/pipeline-stack.ts
import * as cdk from 'aws-cdk-lib';
import { Construct } from 'constructs';
import * as codepipeline from 'aws-cdk-lib/aws-codepipeline';
import * as cpactions from 'aws-cdk-lib/aws-codepipeline-actions';
import * as codebuild from 'aws-cdk-lib/aws-codebuild';
import * as ecs from 'aws-cdk-lib/aws-ecs';
import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns';
import * as secretsmanager from 'aws-cdk-lib/aws-secretsmanager';
// A PipelineStack defines the CI/CD flow for our Claude assistant.
export class PipelineStack extends cdk.Stack {
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
// --------------------------------------------------------------------
// 1️⃣ Source stage – pull code from GitHub
// --------------------------------------------------------------------
const sourceOutput = new codepipeline.Artifact();
const sourceAction = new cpactions.GitHubSourceAction({
actionName: 'GitHub_Source',
owner: 'my-github-org', // replace with your org/user
repo: 'claude-coding-assistant', // repo name
branch: 'main',
oauthToken: cdk.SecretValue.secretsManager('github-token'), // stored secret
output: sourceOutput,
});
// --------------------------------------------------------------------
// 2️⃣ Build stage – Docker image + Claude sanity test
// --------------------------------------------------------------------
// Secret that holds Claude API key (must be created in Secrets Manager)
const claudeSecret = secretsmanager.Secret.fromSecretNameV2(
this,
'ClaudeApiKey',
'claude/api-key'
);
const buildProject = new codebuild.PipelineProject(this, 'BuildProject', {
environment: {
buildImage: codebuild.LinuxBuildImage.STANDARD_7_0,
privileged: true, // required for Docker-in-Docker
// Pass the secret as an environment variable that CodeBuild will mask in logs
environmentVariables: {
CLAUDE_API_KEY: {
type: codebuild.BuildEnvironmentVariableType.SECRETS_MANAGER,
value: claudeSecret.secretArn,
},
},
},
// Grant the build role permission to read the secret
role: new iam.Role(this, 'BuildRole', {
assumedBy: new iam.ServicePrincipal('codebuild.amazonaws.com'),
managedPolicies: [
iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonEC2ContainerRegistryPowerUser'),
],
}).addToPolicy(
new iam.PolicyStatement({
actions: ['secretsmanager:GetSecretValue'],
resources: [claudeSecret.secretArn],
})
),
// The buildspec tells CodeBuild exactly what to do.
// Note the explicit export of the secret – otherwise it never reaches the Docker build.
buildSpec: codebuild.BuildSpec.fromObject({
version: '0.2',
phases: {
pre_build: {
commands: [
'echo Logging in to Amazon ECR...',
'aws ecr get-login-password --region $AWS_DEFAULT_REGION | docker login --username AWS --password-stdin $ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com',
],
},
build: {
commands: [
// Export secret into the Docker build context (gotcha #1)
'export CLAUDE_API_KEY=$CLAUDE_API_KEY',
'docker build -t claude-assistant .',
// Tag with the commit SHA from the source artifact
'docker tag claude-assistant:latest $ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/claude-assistant:$CODEBUILD_RESOLVED_SOURCE_VERSION',
// Push the image for later deployment
'docker push $ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/claude-assistant:$CODEBUILD_RESOLVED_SOURCE_VERSION',
// Run a quick sanity test – invoke the CLI with a sample prompt
'docker run --rm -e CLAUDE_API_KEY=$CLAUDE_API_KEY $ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/claude-assistant:$CODEBUILD_RESOLVED_SOURCE_VERSION node -e "require(\'./dist/cli/index\')" <<< "write a function that adds two numbers"',
],
},
},
artifacts: {
files: ['**/*'],
},
}),
});
const buildAction = new cpactions.CodeBuildAction({
actionName: 'Build_and_Test',
project: buildProject,
input: sourceOutput,
outputs: [new codepipeline.Artifact()], // dummy artifact for downstream
});
// --------------------------------------------------------------------
// 3️⃣ Deploy stage – ECS Fargate service
// --------------------------------------------------------------------
const cluster = new ecs.Cluster(this, 'AssistantCluster', {
vpc: new ec2.Vpc(this, 'VPC'), // creates a new VPC (simplified)
});
const fargateService = new ecs_patterns.ApplicationLoadBalancedFargateService(
this,
'AssistantService',
{
cluster,
taskImageOptions: {
image: ecs.ContainerImage.fromRegistry(
`${cdk.Aws.ACCOUNT_ID}.dkr.ecr.${cdk.Aws.REGION}.amazonaws.com/claude-assistant:${cdk.Fn.getArtifactMetadata(sourceOutput, 'CommitId')}`
),
// Pass the API key at runtime (environment variable)
environment: {
CLAUDE_API_KEY: claudeSecret.secretValue.toString(),
},
secrets: {
CLAUDE_API_KEY: ecs.Secret.fromSecretsManager(claudeSecret),
},
},
desiredCount: 1,
publicLoadBalancer: true,
}
);
// --------------------------------------------------------------------
// Assemble the pipeline
// --------------------------------------------------------------------
new codepipeline.Pipeline(this, 'ClaudeAssistantPipeline', {
pipelineName: 'ClaudeAssistantPipeline',
stages: [
{
stageName: 'Source',
actions: [sourceAction],
},
{
stageName: 'Build',
actions: [buildAction],
},
{
stageName: 'Deploy',
actions: [
new cpactions.EcsDeployAction({
actionName: 'DeployToFargate',
service: fargateService.service,
input: buildAction.actionProperties.outputs![0],
}),
],
},
],
});
}
}
What the code does, step by step
| Step | Explanation |
|---|---|
| Source | Pulls the latest commit from GitHub using a personal‑access token stored in Secrets Manager. |
| Build | Spins up a CodeBuild container, logs in to Amazon ECR (Elastic Container Registry), builds the Docker image, pushes it, and runs a tiny Claude request to confirm the key works. The line export CLAUDE_API_KEY=$CLAUDE_API_KEY is the crucial fix that forwards the secret into the Docker build context. |
| Deploy | Creates an ECS Fargate service (serverless containers) behind an Application Load Balancer. The container receives the Claude API key at runtime via the secrets field, which keeps the value encrypted in transit and out of logs. |
| Pipeline | Chains the three stages together so that a new commit automatically triggers the whole flow. |
Helpful tip: After you write the stack, run
cdk bootstraponce per account/region before anycdk deploy. This creates the CDK‑specific S3 bucket and IAM roles needed for the pipeline. Forgetting this step is a common source of “bootstrap missing” errors.
3️⃣ Bootstrap the environment (the hidden step)
npx cdk bootstrap aws://123456789012/us-east-1
Replace the account ID and region with yours. The command creates a bucket named cdk-bootstrap-<account>-<region> and a few roles that the pipeline later assumes.
Integrating Claude Calls in the Build Phase
The most fragile part of an AI‑driven pipeline is secret propagation. CodeBuild can read a secret, but Docker builds run in a separate process that does not inherit environment variables automatically. If you try to docker build without explicitly exporting the secret, the container’s build‑time script will see an empty CLAUDE_API_KEY and any API call will fail.
Two ways to get the secret into Docker
-
Export before
docker build– we usedexport CLAUDE_API_KEY=$CLAUDE_API_KEY. This copies the variable from the CodeBuild environment into the shell that runsdocker build. Inside the Dockerfile you can reference it withARG CLAUDE_API_KEYand thenENV CLAUDE_API_KEY=$CLAUDE_API_KEYif you need it at runtime. -
secretsManagerfield in CodeBuild – newer CodeBuild versions let you declare asecretsManagermapping, which injects the secret as a file inside the build container. This approach avoids exposing the key in plain‑text logs.
Below is a minimal Dockerfile snippet that accepts the secret as a build‑time argument:
# Inside Dockerfile (add near the top)
ARG CLAUDE_API_KEY
ENV CLAUDE_API_KEY=${CLAUDE_API_KEY}
Now, when the build command runs, the secret is baked into the image’s environment (still encrypted at rest because the image is stored in ECR). For extra safety you can avoid persisting the key by only passing it at runtime (as we do for the ECS task).
In plain English: Think of the secret as a password you need to write on a sticky note for the baker (Docker) to see. If you forget to put the note on the counter, the baker can’t add the secret ingredient, and the cake (your image) will be flavorless.
Running a sanity check
The buildspec includes a one‑line sanity test:
docker run --rm -e CLAUDE_API_KEY=$CLAUDE_API_KEY \
$ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/claude-assistant:$CODEBUILD_RESOLVED_SOURCE_VERSION \
node -e "require('./dist/cli/index')" <<< "write a function that adds two numbers"
What happens?
-
docker runstarts a container from the image we just built. -
-e CLAUDE_API_KEY=$CLAUDE_API_KEYinjects the key as an environment variable. - The tiny Node script reads a prompt from STDIN, sends it to Claude, and prints the generated function.
If the key is missing, the CLI will exit with an authentication error, and the entire pipeline will fail—exactly what you want for early detection.
Testing and Observability
Automation is only as good as the feedback you get when something goes wrong. With CDK Pipelines you have three built‑in places to observe the health of your AI assistant:
| Observation point | What to look for | Why it matters |
|---|---|---|
| CodeBuild logs | Errors like “Secret not found” or “Docker login failed” | Immediate insight into build‑time failures, especially secret propagation. |
| ECS task logs | Runtime exceptions, missing environment variables | Detects issues that only appear after deployment (e.g., wrong IAM permissions). |
| CloudWatch Metrics | CPU/Memory usage of the Fargate task | Helps you size the service appropriately; AI inference can be CPU‑heavy. |
Adding a simple test stage
We can add an extra CodeBuild action that runs a post‑deploy test against the live service. The test will call the public load balancer endpoint with a sample prompt and verify that the response contains valid JavaScript.
// Inside PipelineStack constructor, after the Deploy stage definition
const testProject = new codebuild.PipelineProject(this, 'PostDeployTest', {
environment: {
buildImage: codebuild.LinuxBuildImage.STANDARD_7_0,
},
buildSpec: codebuild.BuildSpec.fromObject({
version: '0.2',
phases: {
build: {
commands: [
// Grab the load balancer DNS name from CloudFormation output
`LB_DNS=$(aws cloudformation describe-stacks --stack-name ${cdk.Stack.of(this).stackName} --query "Stacks[0].Outputs[?OutputKey==\'LoadBalancerDNS\'].OutputValue" --output text)`,
'echo Testing endpoint $LB_DNS',
// Send a sample request
`RESPONSE=$(curl -s -X POST -H "Content-Type: application/json" -d \'{"prompt":"write a function that multiplies two numbers"}\' http://$LB_DNS)`,
// Very naive validation
'if echo $RESPONSE | grep -q "function"; then echo "✅ Test passed"; else echo "❌ Test failed"; exit 1; fi',
],
},
},
}),
});
const testAction = new cpactions.CodeBuildAction({
actionName: 'PostDeployTest',
project: testProject,
input: buildAction.actionProperties.outputs![0],
});
pipeline.addStage({
stageName: 'Test',
actions: [testAction],
});
The snippet does three things:
- Looks up the load balancer DNS name from the CloudFormation stack (the pipeline’s own stack).
- Sends a POST request containing a prompt.
- Checks that the response includes the word
function, a simple sanity check.
Key tip: CloudWatch Logs automatically capture
curloutput and anyechostatements. If your test fails, the pipeline will stop and you’ll see the exact HTTP payload that caused the problem.
Observability best practices for AI services
-
Mask secrets in logs – CodeBuild environment variables of type
SECRETS_MANAGERare automatically hidden, but anyecho $CLAUDE_API_KEYwill expose them. Avoid printing the variable. - Enable VPC flow logs – If you attach CodeBuild to a VPC (common for private ECR access), make sure the VPC has a NAT Gateway; otherwise the build container cannot reach the Claude API endpoint on the internet.
-
Set a build cache – Docker layer caching speeds up subsequent builds, but remember that the cache is not invalidated when you rotate the Claude API key. Add a dummy
ARG CACHE_BUSTER=$(date +%s)line to the Dockerfile to force a fresh build after secret rotation.
The Takeaway
In plain English: By describing your Claude‑powered coding assistant as code, you let AWS do the heavy lifting of building, testing, and deploying. No manual Docker commands, no hidden credentials, and no “it works on my machine” surprises.
- CDK Pipelines turn a series of manual steps into a single, version‑controlled pipeline that can be triggered by any git push.
- Secrets must be exported explicitly into Docker builds; otherwise the Claude SDK cannot authenticate.
- CodeBuild logs expose unmasked environment variables, so always use the
SECRETS_MANAGERtype and avoid printing the values. - A VPC‑attached CodeBuild needs a NAT Gateway to reach external APIs like Claude; otherwise builds will fail at the network level.
- Adding a lightweight post‑deploy test gives fast feedback that the live service can actually talk to Claude.
- Remember to run
cdk bootstraponce per account/region; without it the pipeline cannot create the required S3 bucket and IAM roles.
With these pieces in place, any team can spin up a reproducible, auditable, and secure Claude‑based coding assistant in minutes—no manual Docker pushes, no secret leaks, and no hidden steps. Happy building!
Transparency notice
This article was written with the help of an AI system — Groq (GPT OSS 120B).
Published: 2026-10-20 · Primary focus: CDK
All code blocks are intended to be correct and runnable, but please verify them
against the official docs for the tools mentioned before using in production.Find an error? Drop a comment — corrections are always welcome.
來源:DEV Community · dev.to