Skip to main content
emnode / learn
Cost

Migrate EC2 to Graviton (ARM)

Up to 40% better price-performance on ARM — surfaced by Cost Optimization Hub as a recommendation. Validate compatibility, then ride the migration loop.

18 min·10 sections·AWS

Last reviewed

Graviton: the basics

What is Graviton and why does AWS keep nagging you to migrate?

Graviton is AWS's family of custom 64-bit Arm processors, built on the Neoverse core and designed specifically for cloud workloads. The current generations — Graviton3 (the 7g instance families like m7g, c7g, r7g) and Graviton4 (the newer 8g line) — sit alongside the older Graviton2 (6g) which still ships in many regions. Every Graviton instance is ARM64; every x86 instance is, well, x86. There is no overlap in CPU architecture, which is the entire reason this lesson exists.

The pitch is straightforward and unusually honest for cloud marketing: Graviton instances list at roughly 20% lower per-hour than the equivalent x86 generation, and AWS measures another 25-40% better performance on common workloads — microservices, JVM apps, NGINX, Redis, Postgres, Go and Node services on Graviton-tuned AMIs. Stack those two effects and the price-performance gap is real, not a rounding-error discount.

Cost Optimization Hub flags Graviton migration as a recommendation type whenever it sees a workload running on an x86 instance family that has a published Graviton equivalent (m5 → m7g, c5 → c7g, r5 → r7g, t3 → t4g) and judges the workload portable. The action is Migrate to Graviton (ARM) EC2 instance, scoped to a single arn:aws:ec2:...:instance/..., with a recommendedInstanceType and monthly savings projection. The recommendation is correct often enough to act on by default — but only after you've cleared the architecture gate.

In this lesson you'll learn what makes Graviton a genuine price-performance win rather than a marketing discount, how to read the Cost Optimization Hub recommendation, the compatibility loop that gates every migration (multi-arch builds, base-image pinning, canary on a real Graviton node), and the mechanics of rolling a fleet from x86 to ARM without taking downtime. You'll also see how Graviton interacts with your existing Savings Plans and Spot strategy — because the discount stack matters more than the sticker price.

Fun fact

AWS runs on Graviton too

By 2024 AWS reported that more than 50% of all new EC2 CPU capacity sold to customers was Graviton, and internally a majority of their managed services — RDS, ElastiCache, OpenSearch, Lambda, Fargate, even parts of the EC2 control plane itself — run on Graviton in production. When AWS picks ARM for their own most-critical services, the price-perf story is not aspirational; the dogfooding has already happened. The slowest mover in the migration is usually the customer fleet, not the platform.

Migrating to Graviton in action

Nina runs the SRE team at a fintech that ships a Go-based pricing service on a fleet of forty c5.2xlarge instances behind an ALB. Cost Optimization Hub has been quietly flagging the same recommendation across the fleet for three weeks: migrate to c7g.2xlarge, projected monthly savings $4.8k.

She knows the service is pure Go with no CGO dependencies and no x86-only third-party binaries. The container base is alpine:3.19 which has supported arm64 since the early 3.x line. On paper it should Just Work — but the team has been bitten before by an x86-only Datadog sidecar, so the migration has been sitting in the backlog waiting for someone to actually run the validation loop.

She pulls the Hub recommendation, confirms the savings math, builds a multi-arch image with docker buildx, deploys a single c7g.2xlarge canary alongside the existing fleet, and watches latency for 72 hours. p99 actually improves by 11%. She updates the launch template and lets the ASG drain x86 nodes overnight.

First, pull the Cost Optimization Hub recommendation to confirm the recommended type and the savings projection.

$ aws cost-optimization-hub get-recommendation --recommendation-id rec-0abc123def456 --query 'currentResourceDetails.ec2Instance,recommendedResourceDetails.ec2Instance,estimatedMonthlySavings'
[
{
"instance": { "instanceType": "c5.2xlarge", "platform": "Linux/UNIX" }
},
{
"instance": { "instanceType": "c7g.2xlarge", "platform": "Linux/UNIX" }
},
{ "value": 119.84, "currency": "USD" }
]
# c5.2xlarge -> c7g.2xlarge, ~$120/mo per instance, $4.8k/mo across the fleet.

Cost Optimization Hub's per-instance migration recommendation.

Now build a multi-arch image so the same tag works on both x86 and arm64 nodes during the migration window.

$ docker buildx build --platform linux/amd64,linux/arm64 -t 123456789012.dkr.ecr.eu-west-1.amazonaws.com/pricing:v1.42 --push .
[+] Building 84.3s (18/18) FINISHED
=> [linux/amd64 builder 1/4] FROM golang:1.22-alpine
=> [linux/arm64 builder 1/4] FROM golang:1.22-alpine
=> [linux/amd64 builder 4/4] RUN go build -o /pricing ./cmd/server
=> [linux/arm64 builder 4/4] RUN go build -o /pricing ./cmd/server
=> exporting manifest list sha256:7c2a91...
=> pushing pricing:v1.42 to ECR (multi-arch manifest)
# Same tag, two architectures. Kubernetes/ECS pulls the right one per node.

A multi-arch image lets you canary on Graviton without forking the deployment pipeline.

Graviton under the hooddeep dive

Graviton is ARM64 (aarch64). Every byte of compiled code on the instance has to target that architecture — the kernel, the userland, your application binary, every shared library it dlopens, every C extension your interpreter imports. Managed runtimes (JVM, .NET 6+, Go, Python, Node, Ruby) handle this transparently because they ship arm64 builds; the breakage usually lives one layer down, in a CGO module, a pre-compiled wheel, an x86-only sidecar, or a kernel module shipped as a .ko blob in your AMI.

Cost Optimization Hub's recommendation engine reads the same CloudWatch and Compute Optimizer signals it uses for right-sizing, then applies a portability heuristic — instance generation, AMI metadata, attached EBS volumes, known sidecar inventory — and emits the migration recommendation when the workload looks portable enough to be worth the human review. The recommendedInstanceType is always the matching Graviton SKU for the same vCPU/RAM tier within the family. Savings come from two sources: the ~20% list-price gap, and the fact that better performance per vCPU often lets you also drop a size (a c5.4xlarge sometimes maps to c7g.2xlarge, not c7g.4xlarge).

The migration mechanics are identical to right-sizing because under the hood you're just changing InstanceType on a launch template — except you also need to swap the AMI for an ARM64 build. Amazon Linux 2023, Ubuntu 22.04, and most modern distros publish both architectures under the same SSM parameter prefix (e.g. /aws/service/ami-amazon-linux-latest/al2023-ami-kernel-default-arm64). The ASG/EKS managed node group handles the rest: drain, terminate, replace with the new launch template, repeat.

# Resolve the latest Amazon Linux 2023 ARM64 AMI via SSM.
AMI_ID=$(aws ssm get-parameter \
  --name /aws/service/ami-amazon-linux-latest/al2023-ami-kernel-default-arm64 \
  --query 'Parameter.Value' --output text)

# Update the launch template with the new AMI and Graviton instance type.
aws ec2 create-launch-template-version \
  --launch-template-id lt-0abc123def456 \
  --source-version '$Latest' \
  --launch-template-data "{\"ImageId\":\"$AMI_ID\",\"InstanceType\":\"c7g.2xlarge\"}"

aws ec2 modify-launch-template \
  --launch-template-id lt-0abc123def456 \
  --default-version '$Latest'

# Roll the fleet at the autoscaling layer.
aws autoscaling start-instance-refresh \
  --auto-scaling-group-name pricing-asg \
  --preferences '{"MinHealthyPercentage":90,"InstanceWarmup":120}'

What is the impact of staying on x86 when Graviton fits?

The direct impact is the bill. A fleet that could be running on c7g.2xlarge but isn't is paying roughly 20% more per hour for compute that, on most workloads, would also perform 25-40% better. For a 40-node c5.2xlarge fleet that's around $4.8k/month, or ~$58k/year, going to AWS for no reason other than "we haven't gotten around to it." Across a larger estate the number reaches genuine seven-figure territory before anyone bothers to total it.

The second-order effect is that you're sitting on a recommendation Cost Optimization Hub flagged weeks or months ago, which means the same finding is being re-surfaced in every finance review, every quarterly business review, every FinOps standup. Each iteration costs human attention without producing the migration — the worst kind of organisational tax.

There's a Savings Plan trap on the other side: if your existing commitment is a Compute Savings Plan it covers Graviton transparently and you get the migration savings on top of the commitment discount. If it's an EC2 Instance Savings Plan locked to the c5 family, however, those hours do not transfer across to c7g — the SP keeps charging you for c5 hours you're no longer running, and the migration savings get partly eaten until the commitment expires. This is the single most common reason a Graviton migration's actual savings undershoot the projection.

Finally, Spot availability and pricing on Graviton instance pools is generally as good or better than the equivalent x86 pool, particularly in newer regions where capacity has been built out aggressively. A fleet that's already on Spot and migrates to Graviton typically sees both lower interrupt rates and lower hourly prices — a second discount layer the on-paper projection doesn't capture.

How do you migrate to Graviton safely?

Graviton migration is a four-step compatibility-then-rollout loop. Skip the validation half and you'll find the one binary that's x86-only the hard way, in production, at 3am.

1. Inventory the architecture surface

Walk the dependency tree before you touch a launch template. List every container image, every sidecar, every pre-compiled binary or kernel module shipped in the AMI, every native dependency your runtime pulls in (Python wheels, Node native modules, Ruby gems with C extensions). For each one, confirm an arm64 build exists. JVM/Go/Python/Node services on standard base images almost always pass; .NET Framework (not .NET 6+), x86-only third-party agents (some older APM/security vendors), and homegrown C/C++ tools are the common blockers.

2. Build multi-arch and pin Graviton-friendly base images

Use docker buildx build --platform linux/amd64,linux/arm64 so the same tag works on both architectures during the rollout. Pin base images that publish arm64 (Amazon Linux 2023, debian:bookworm, alpine:3.19+, ubuntu:22.04+, the official language images for Go/Python/Node/JDK). Avoid :latest — you want the arm64 build to be deterministic, not a moving target. For Lambda, ECS, and EKS the manifest list handles per-node pulling automatically.

3. Canary on a real Graviton instance for 24-72 hours

Spin up one Graviton node alongside the existing x86 fleet, route a small slice of traffic to it (1-5% via target group weights or service mesh), and watch p99 latency, error rate, GC behaviour (JVM JIT warm-up looks different on ARM — give it longer than you think before judging steady-state), and any vendor-specific health metrics. 24 hours catches the obvious breakage; 72 catches the JIT and cache-warmup edge cases. Promote only after the canary holds steady through at least one full traffic cycle.

4. Roll the fleet via launch template + instance refresh

Update the launch template with the ARM64 AMI and the recommended Graviton instance type, then trigger an ASG instance refresh (or eks update-nodegroup-version --force-update for managed node groups). Set MinHealthyPercentage to 90 so you drain gradually. After the rollout completes, verify your Savings Plan coverage in Cost Explorer — if you were on an EC2 Instance SP locked to the old family, plan the SP rotation or wait out the term, and price that gap into the migration's expected ROI.

# Bulk-export the open Graviton recommendations across the account.
aws cost-optimization-hub list-recommendations \
  --filter 'actionTypes=MigrateToGraviton' \
  --query 'items[?implementationEffort==`Low`].[recommendationId,currentResourceArn,estimatedMonthlySavings.value]' \
  --output table > graviton-candidates.txt

# Feed into your change-management workflow.
wc -l graviton-candidates.txt

Quick quiz

Question 1 of 5

Cost Optimization Hub recommends migrating a c5.2xlarge running a Go service in an alpine:3.19 container to c7g.2xlarge with a $120/mo savings projection. Your existing commitment is an EC2 Instance Savings Plan locked to the c5 family. What's the right next move?

You've completed Migrate EC2 to Graviton (ARM). You now know what Graviton actually is, why the price-performance gap is real and not marketing, how to clear the architecture gate before you migrate, and how to run the four-step compatibility-then-rollout loop — inventory, multi-arch build, canary, roll — without breaking production or stranding your Savings Plan. The next Cost Optimization Hub recommendation that lands in your inbox is ready to be acted on, not deferred.

Back to the library