Skip to content

ARM64 (Graviton) Migration Assessment

This note captures what it would take to move the stack from amd64 to arm64 (AWS Graviton), and where the real risk is. Everything here is current-state research, not a plan that's been started.

Current State

Nothing in this repo pins amd64 explicitly except one place - most of the stack is on amd64 today simply because that's Fargate's default and nobody has set it otherwise:

  • infra/modules/ecs/main.tf's aws_ecs_task_definition resources set no runtime_platform block at all, so Fargate defaults to X86_64.
  • docker-bake.hcl sets no platforms for any target, so docker buildx bake just builds for the host builder's native architecture.
  • infra/modules/tracker_restore_host/main.tf's AMI lookup explicitly filters name = ["debian-13-amd64-*"] - this one is hardcoded.
  • infra/modules/database (the self-managed Postgres/TimescaleDB/PostGIS EC2 host - not RDS) takes an explicit ami_id with no default; whatever's configured today is presumably an amd64 image, but the module itself doesn't pin an architecture.

Straightforward Part: Fargate Services and Jobs

Every application Dockerfile is already on an official, multi-arch base image, so there's no base-image blocker:

File Base image
build/api/Dockerfile, services/docker/Dockerfile, build/dev/Dockerfile python:3.13-slim
build/db/Dockerfile postgres:16-bookworm
tracker-frontend-svelte/docker/*.Dockerfile, tracker-admin-svelte/docker/*.Dockerfile node:20-alpine, nginx:alpine

pyproject.toml's dependencies with native extensions (psycopg, shapely/ geoalchemy2, pillow, orjson, argon2-cffi) all publish arm64 wheels on PyPI - no expected native-build blockers there. Dragonfly's image is confirmed multi-arch (Graviton is one of their own marketed use cases).

To move the Fargate side to arm64:

  1. Add runtime_platform { cpu_architecture = "ARM64", operating_system_family = "LINUX" } to both aws_ecs_task_definition resources in infra/modules/ecs/main.tf.
  2. Add platforms = ["linux/arm64"] to each target in docker-bake.hcl (or multi-platform ["linux/amd64", "linux/arm64"] if local dev machines and CI/deploy need to stay on different architectures for a transition period).
  3. Rebuild and push images, then apply - this is a normal image-tag rollout like any other deploy, fully reversible via a task-definition revision.

Graviton Fargate is typically ~20% cheaper than x86 at comparable or better performance, so this part is a fairly clean win on its own.

Needs Verification Before Trusting It

  • build/db/Dockerfile installs TimescaleDB and PostGIS from packagecloud and Debian apt repos for bookworm. Both generally publish arm64 packages, but this should be confirmed with an actual arm64 build of this image, not assumed.
  • dadoum/anisette-v3-server (used directly in compose.yml for local dev, and mirrored into ECR for staging/prod per infra/envs/*/main.tf's anisette service) is a small third-party image. Whether it publishes an arm64 variant couldn't be checked from this environment - worth a docker manifest inspect dadoum/anisette-v3-server before committing to this migration. If it's amd64-only, that one service needs either QEMU emulation (acceptable for a low-traffic auth helper) or a rebuilt/forked image.

The Real Risk: the Self-Managed Database Host

This is not Fargate, so it doesn't get the "just add a runtime_platform block" treatment:

  • infra/modules/tracker_restore_host explicitly filters its AMI to debian-13-amd64-* and defaults to instance_type = "t3.medium" (an x86-only family).
  • infra/modules/database provisions the actual production/staging Postgres+TimescaleDB+PostGIS host the same way - a hand-bootstrapped EC2 instance (via the tracker-ops Ansible repo, not this one), not RDS.

Moving this to Graviton means: a new AMI, switching to a Graviton-compatible instance family (e.g. t4g/m7g instead of t3/m5), and re-verifying TimescaleDB, PostGIS, and pgBackRest all have working arm64 builds for that exact OS release - on the actual database host, not a disposable container. This is stateful infrastructure where a bad AMI or missing package isn't fixed by rolling back a task definition revision.

Recommendation

Don't couple these two migrations. Do the Fargate/container side first - it's cheap, low-risk, and fully reversible. Treat the EC2 database host as a separate, later decision, tested in isolation (e.g. against a throwaway Graviton instance running the same bootstrap playbook) before ever pointing it at production data.

Suggested Evaluation Checklist

  1. Confirm dadoum/anisette-v3-server publishes an arm64 image.
  2. Build build/db/Dockerfile for linux/arm64 and confirm TimescaleDB + PostGIS install cleanly.
  3. Add runtime_platform/platforms for one low-traffic Fargate service first (e.g. mcp-diagnostics, since it's already an on-demand job, not a long-running service) and confirm it runs correctly.
  4. Roll out to the rest of the Fargate services/jobs once (3) is proven.
  5. Only then evaluate the EC2 database host migration as its own project, tested against a disposable instance first.