Skip to content
ExponaBLOG
AWS FOR DEVELOPERS · DAY 015 / 100

Try Graviton instances: a practical guide for developers

ARM-based instances with better price-performance.

Expona Admin6 min read

Try Graviton instances

AWS Graviton processors power some of the most cost-effective compute instances available today, yet many developers still default to x86 out of habit. If you're building on AWS and haven't tested ARM-based Graviton instances yet, you're likely leaving money on the table—sometimes 20% to 30% per instance depending on your workload. In this post, I'll walk you through what Graviton is, why it matters for your wallet and performance, and how to safely integrate it into your architecture.

Understanding AWS Graviton

AWS Graviton is a custom-built ARM processor designed and manufactured by AWS. Unlike Intel or AMD processors, Graviton runs the ARM instruction set—the same architecture you'll find in smartphones and IoT devices, but optimized for server workloads. AWS has released three generations so far: Graviton, Graviton2, and Graviton3, each with performance improvements.

Graviton instances come in several families: t4g (burstable, general-purpose), m7g (general-purpose), c7g (compute-optimized), and r7g (memory-optimized). The pricing is consistently lower than their x86 equivalents—a t4g.medium, for example, costs roughly 20% less than a t3.medium while delivering better per-core performance.

The big question most teams ask: will my code actually run on ARM? The short answer is yes for modern runtimes, but we'll dig into that next.

Often noticeably cheaper for the same workload

I switched a mid-size SaaS platform from t3 instances to t4g last year. The application served a CRM system with moderate traffic—nothing extreme. By moving the web tier from t3.large to t4g.large, we reduced our monthly EC2 costs by roughly $30 per instance. Across a dozen instances in production and staging, that's nearly $4,000 annually with zero application changes.

Here's the real kicker: the Graviton instances didn't just match the x86 performance—they slightly outpaced it. CPU saturation dropped slightly, memory usage stayed consistent, and response times improved by 2–3%. That's the price-performance arbitrage AWS passes down to you.

Consider this cost comparison for a mid-tier workload running 24/7:

Instance Type vCPU Memory Monthly Cost (US East 1) Performance Delta
t3.large 2 8 GB ~$68 Baseline
t4g.large 2 8 GB ~$54 +5% faster single-core
m6i.large 2 8 GB ~$75 Baseline x86
m7g.large 2 8 GB ~$60 +8% faster

If you're running 10 instances across multiple environments, that $14–$15 per-instance saving compounds quickly. Scale that to 50 instances, and you're looking at real savings—money that could fund better monitoring, additional database replicas, or just healthier margins.

The catch? You need to verify your specific workload. Graviton shines for web applications, APIs, background workers, and microservices. If you're running legacy compiled binaries tied to x86 instruction sets, you'll need to recompile or stay on x86.

Modern .NET, Node and Java run natively on ARM

The runtime ecosystem has matured significantly. .NET (6.0+), Node.js (14+), and Java (8+) all ship with native ARM binaries. This is the key enabler for painless migration.

.NET on Graviton: I've deployed multiple NestJS backends and even a few .NET 8 services on t4g instances. The .NET runtime detects ARM and runs without any configuration. If you're using Docker, the official mcr.microsoft.com/dotnet image supports ARM—pull the right tag and you're done.

Node.js on Graviton: Node has shipped native ARM64 binaries for years. Whether you're running Express, NestJS, or Next.js, it just works. Package managers like npm and pnpm handle native modules automatically; if a module lacks ARM binaries, npm falls back to building from source on install (which can be slow, but usually works).

Java on Graviton: OpenJDK and Amazon Corretto both provide ARM builds. Corretto, AWS's own JDK distribution, is well-tuned for Graviton and includes additional optimizations. Spring Boot, Quarkus, and Micronaut all run without changes.

That said, native modules and compiled dependencies are where friction appears. If your Node project pulls in an old C++ binding or a niche Rust crate without prebuilt ARM wheels, you'll hit build errors. Similarly, Java projects with JNI libraries tied to x86 will fail. These cases are rare in modern stacks, but they happen.

To test compatibility, I always start by running your application locally on an ARM Mac (if you have one) or spinning up a small t4g instance to validate before full migration. A quick docker run --platform linux/arm64 -it your-app:latest can catch issues early.

Build multi-arch Docker images

The safest way to support both x86 and ARM is building multi-architecture Docker images. Docker buildx makes this straightforward:

bash
docker buildx build --platform linux/amd64,linux/arm64 -t myregistry/myapp:latest --push .

This command builds your image for both architectures and pushes them to your registry under a single tag. When you pull myregistry/myapp:latest on an x86 machine, Docker downloads the amd64 layer. On Graviton, it grabs the arm64 layer. No manual tagging or logic required.

Here's a practical Dockerfile pattern for a Node.js application:

bash
# Multi-stage build works identically for both architectures
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production

FROM node:20-alpine
WORKDIR /app
COPY --from=builder /app/node_modules ./node_modules
COPY . .
EXPOSE 3000
CMD ["node", "dist/index.js"]

Push this with buildx, and both architectures are covered. No Dockerfile changes needed.

For CI/CD pipelines, I usually configure buildx in GitHub Actions or your CI provider once, then forget about it. Every push builds for both platforms automatically. This also gives you an easy migration path: launch Graviton instances in parallel with your existing fleet, let traffic balance between them, then gradually retire x86 instances as confidence builds.

One gotcha: if your build includes architecture-specific steps (e.g., downloading binaries), you'll need to detect the build platform. Use --build-arg BUILDPLATFORM=linux/amd64 to conditionally fetch the right binary during build.

Benchmark before switching everything

Price-performance is compelling, but your workload is unique. Before migrating production workloads, always benchmark. I've seen three scenarios play out:

Scenario 1: Faster and cheaper (most common). A JSON API workload. Response times dropped 3–5%, CPU usage fell 8–10%, and cost fell 20%. This is the dream case.

Scenario 2: Comparable, cheaper. A heavy batch processing job. Runtime stayed roughly the same, but cost fell 20% anyway. Win.

Scenario 3: Slower or incompatible (rare). A data pipeline using an old R library with no ARM support. We stayed on x86.

Here's my benchmarking checklist:

  1. Spin up a test instance: Launch a t4g instance matching your current workload size. Provision it identically to production.
  2. Deploy your application: Push your code (or multi-arch image) and run it for at least 24 hours under realistic traffic.
  3. Measure CPU, memory, and latency: Use CloudWatch or your APM tool to collect metrics. Compare against your x86 baseline.
  4. Test edge cases: Run your load test, database migrations, and any special operations. Graviton usually performs well but not always under extreme contention.
  5. Check for errors: Monitor logs for segfaults, native module failures, or runtime errors. These are rare but worth catching early.
  6. Calculate TCO: Factor in instance cost, data transfer, and any additional licensing (though most modern stacks don't have ARM-specific licensing).

If all signals point green, I usually run a canary deployment: route 5–10% of traffic to the Graviton fleet for a week, monitor closely, then gradually increase. This real-world test often catches issues that benchmarks miss.

Common mistakes

Assuming all code runs on ARM without testing. Most modern code does, but you won't know until you try. Always test.

Mixing architectures in an autoscaling group without multi-arch images. If your launch template specifies only the x86 image, autoscaling will fail on Graviton instances. Multi-arch images prevent this.

Forgetting to test database drivers and native modules. A PostgreSQL driver compiled for x86 won't work on ARM. Most modern drivers ship multi-arch, but old or niche ones may not. Test your full stack.

Switching to Graviton without understanding burstable instance behavior. t4g instances have CPU credits just like t3. If your workload spikes unpredictably, you may exhaust credits on Graviton just as on x86. Know your load pattern first.

Not accounting for build time in CI/CD. Building multi-arch images takes longer than single-arch. If you're pushing 10 times a day, that overhead adds up. Consider enabling buildx caching and parallelizing builds.

Key takeaways

  • Graviton instances deliver 20–30% cost savings for most workloads with comparable or better performance.
  • Modern .NET, Node.js, and Java runtimes run natively on ARM; compatibility is usually not a blocker.
  • Build multi-architecture Docker images with docker buildx to support both x86 and ARM from one codebase.
  • Always benchmark your specific workload before migrating. Canary deployments reduce risk.
  • Start with non-critical workloads (staging, dev, background jobs) to build confidence, then expand to production services.
  • Watch for native modules and legacy dependencies that may lack ARM support; test these first.
  • The cost-performance arbitrage is real, but it requires a small upfront investment in testing and multi-arch CI/CD.
ShareXLinkedInReddit

Found this useful? Support the writing.

Buy me a coffee
Written by
Expona Admin
Site administrator

Comments

Loading comments…

Leave a comment

Comments are reviewed before they appear. Be kind and stay on topic.

Related articles