AWS Builder Center
AIdeas: SafeMigration for AWS Graviton

AIdeas: SafeMigration for AWS Graviton

A serverless tool that uses Amazon Bedrock to automatically migrate CI/CD pipelines to ARM/Graviton, forking your repo into a sandbox, stubbing out secrets and databases, and opening a reviewable PR with zero risk to your code. Built with Kiro's spec-driven workflow, AWS Lambda, and Step Functions.

This article was co-written with Kiro itself.

App Category

Workplace Efficiency

My Vision

Graviton instances are cheaper and faster. Everyone knows this. But migrating CI/CD pipelines from x86 to ARM? That's where teams freeze up. Nobody wants to be the person who breaks the build. The gap between "we should migrate" and "we actually migrated" is filled with fear, not technical difficulty.
SafeMigration closes that gap. It's a serverless application that takes a GitHub repository, forks it into a sandbox, uses Amazon Bedrock to analyze pipeline files for architecture-specific dependencies, generates arm64-compatible changes, stubs out secrets and databases so tests can actually run, and opens a pull request you can review. Every change happens in a fork. Zero risk to your code.
The idea came from a simple observation: most Graviton migrations follow the same pattern. Swap the runner label, update a base image, check dependencies, check intrinsics. It's repetitive work that an LLM can handle well — especially when paired with structured knowledge from the Arm MCP Server . The hard part isn't the analysis; it's the confidence to actually push the button. SafeMigration gives you that confidence by doing everything in a sandbox first.

Why This Matters

Cloud cost optimization isn't optional anymore. AWS Graviton processors deliver up to 40% better price-performance than comparable x86 instances, and that advantage extends to every Lambda invocation, every container task, and every CI/CD runner minute. For teams running hundreds of pipeline executions per day, the savings compound fast.
But migration friction is real. A typical GitHub Actions workflow might reference ubuntu-latest (x86), pull a Docker base image compiled for amd64, install native Python packages that don't have ARM wheels, and depend on secrets and database connections that can't be replicated in a test environment. Changing any of these by hand means understanding the full dependency chain, testing in isolation, and hoping nothing breaks downstream.
This is a workplace efficiency problem at scale. Every engineering team that delays Graviton migration is leaving money on the table — not because they don't know it's better, but because the migration process is tedious and risky. SafeMigration automates the tedious parts and eliminates the risk by sandboxing everything. One click, one PR, one review. That's the workflow.
The stubbing system is what makes this practical. Real CI/CD pipelines depend on production secrets, database connections, and external services. You can't just swap a runner label and expect tests to pass. SafeMigration's Stubbing Engine performs static analysis to detect these dependencies and replaces them with safe mocks — annotated so you know exactly what was changed and why. This means the migrated pipeline can actually execute in the fork without needing production credentials or infrastructure access.

How I Built This

The Stack

SafeMigration is a fully serverless Python application on AWS:
  • 9 Lambda functions (Python 3.12, running on arm64)
  • AWS Step Functions orchestrating a 5-stage migration pipeline
  • DynamoDB for session and job state (two tables, pay-per-request)
  • Amazon Bedrock (Claude) for pipeline analysis and change generation
  • GitHub App OAuth for authentication
  • React SPA dashboard (TypeScript + Vite) with a retro console UI
  • AWS CLI bash scripts for deployment (no CloudFormation — fast iteration)

Architecture

The pipeline flows through five stages, each its own Lambda:
1
Fork → Analyze → Generate → Stub → PR
Step Functions handles sequencing and retries. When a user clicks "Start Migration," the Job Lambda creates a record in DynamoDB and kicks off a Step Functions execution. Each stage Lambda updates the job record with its progress, so the dashboard can show real-time stage tracking.
The Analyze stage is where Bedrock comes in. It receives the contents of every file in the repository, along with context from the Arm MCP Server's migration knowledge base. Bedrock identifies architecture-specific dependencies — things like x86 runner labels, amd64 base images, and packages without ARM wheels — and returns a structured migration report with line-level precision.
The Generate stage applies those changes. The Stub stage detects external dependencies (secrets, databases, service URLs) using Bedrock and generates safe mocks. The PR stage commits everything to a migration branch in the fork and opens a pull request with a detailed body: per-file change summaries, rationale for each modification, and flagged items for manual review.

Spec-Driven Development with Kiro

This project was built using Kiro's spec-driven development workflow. Before writing any code, I authored three documents: requirements (11 user stories with formal acceptance criteria), design (component interfaces, data models, 26 correctness properties), and tasks (implementation units).
This process caught three significant design issues before a single line of code was written:
  1. The original design used Amazon Cognito for auth. During requirements review, I realized this added unnecessary complexity for a GitHub-native audience. Rewritten to use GitHub App OAuth directly.
  2. The original design passed GitHub access tokens through Step Functions execution state. During design review, I realized this would expose tokens in the Step Functions execution history (visible in the AWS console). Redesigned: tokens are stored in DynamoDB, and each Lambda fetches its token via get_github_token(job_id).
  3. The original design had the app writing changes directly to the user's repository. Changed to a fork-based sandbox model during requirements, ensuring zero risk to user code.

Key Development Milestones

The build happened in three phases:
Phase 1: Backend and deployment. All Lambda functions, DynamoDB tables, Step Functions state machine, and API Gateway — deployed and tested via bash scripts. This phase surfaced the most instructive bugs: a PATH variable collision that clobbered the system path, missing shell utilities in the deployment environment, API Gateway permission issues with parameterized routes, and the infamous ELF header mismatch where macOS arm64 binaries were packaged instead of Linux arm64 wheels.
Phase 2: Dashboard and OAuth integration. Connecting the React SPA to the live API produced a cascade of frontend-backend integration bugs. React Strict Mode double-rendered the OAuth callback (consuming the authorization code twice). Silent error swallowing in the catch handler made failures invisible. GitHub's token endpoint returned non-JSON responses that crashed the Lambda. Each bug was invisible until the previous one was fixed.
Phase 3: Pipeline status tracking. The dashboard showed jobs stuck in "pending" forever. The root cause: none of the pipeline Lambdas were calling update_job_stage() or complete_job() — functions that existed in the data layer but were never wired into the handlers. I added stage tracking to all pipeline Lambdas, created a new completion Lambda for terminal states, and updated the state machine to pass job_id to every stage and call the completion Lambda at terminal transitions.

Token Security

Worth calling out: GitHub access tokens never flow through Step Functions state. They're stored in DynamoDB at job creation time, and each pipeline Lambda fetches its token via a get_github_token(job_id) call. This prevents token exposure in Step Functions execution history, which is visible in the AWS console and via the API to anyone with states:DescribeExecution permission.
Demo

What I Learned

Spec-driven development pays for itself

Writing requirements and design documents before code felt slow at first. It wasn't. The three design issues caught during spec review (Cognito vs. GitHub OAuth, token exposure in Step Functions, fork-based sandbox) would have been expensive architectural rework if discovered during implementation. The spec also served as a living reference throughout the build — when I wasn't sure how a component should behave, the acceptance criteria had the answer.

The bugs that matter most are the ones between systems

Unit tests caught logic errors. But the hardest bugs lived in the seams between systems: React Strict Mode double-rendering an OAuth callback, macOS binaries packaged for a Linux Lambda runtime, a state machine that didn't pass job_id to two of its stages, pipeline Lambdas that never wrote status updates back to DynamoDB. These integration-layer bugs don't show up in isolated tests. They only surface when you connect real systems together and push real data through them.

Stubbing is what makes sandbox migration practical

The technical insight that made SafeMigration viable: you can't just swap a runner label and expect a pipeline to pass. Real pipelines depend on secrets, databases, and external services. The Stubbing Engine — which detects these dependencies via static analysis and replaces them with annotated mocks — is what turns a fork from "a copy of the repo" into "a copy that can actually run." Without it, every sandbox migration would fail on the first step that needs a production credential.

AI works best with structured context

Bedrock's analysis quality improved dramatically when we paired it with the Arm MCP Server's curated migration knowledge base. A raw prompt like "find ARM compatibility issues" produces vague results. A prompt that includes the actual file contents, structured as workflow/Dockerfile/manifest categories, with explicit instructions to return line-level dependency data in a specific JSON schema — that produces actionable output. The lesson: LLMs are powerful, but they need structured context and constrained output formats to be reliable in automated pipelines.

Ship the simplest thing that works, then iterate

The deployment uses bash scripts, not CloudFormation. The dashboard uses inline styles, not a CSS framework. The state machine is a JSON file with placeholder substitution, not a CDK construct. Every one of these choices was deliberate: they kept the iteration cycle fast. When I found the pipeline status tracking bug, I could fix seven Lambda handlers, add a new Lambda, update the state machine, and redeploy in minutes. Infrastructure-as-code is great for production; for building and learning, speed wins.

Future Improvements

  • Smarter stubbing: The current stubbing engine identifies external dependencies but needs deeper integration with the generated changes to produce stubs that are contextually aware of the full migration.
  • Full PR creation: The PR stage currently generates migration changes but needs improved GitHub API integration to reliably commit all file types (including workflow files) and open pull requests in forked repositories.
  • Pipeline validation loop: A future iteration will add automated pipeline run checking and a Bedrock-powered feedback loop that captures failure logs and applies corrective fixes — up to three cycles before escalating to manual review.
Built with Kiro , Amazon Bedrock, AWS Lambda, Step Functions, and the Arm MCP Server.
Any opinions in this article are those of the individual author and may not reflect the opinions of AWS.
Enjoyed reading this content? Let the author know!

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

Loading recommendations

Loading article