Bug: medusa db:migrate deadlocks (hangs forever) on aarch64/ARM64 Linux; works on x86_64
Summary
npx medusa db:migrate hangs indefinitely at Running migrations... on ARM64 Linux hosts, but the identical project + database completes migrations normally on x86_64 (both x86_64 Linux and x86_64 macOS). The hang is an unresolved promise — the Node event loop goes fully idle with no pending I/O — inside the per-module migration transaction. It reproduces with a fresh create-medusa-app project (no custom modules), across Medusa 2.16.0 and 2.17.2, Node 20 and 22, and multiple base images.
Environment where it hangs
- Arch: aarch64 (ARM64) — Oracle Cloud Ampere A1, Ubuntu 24.04, kernel
6.8.0-1027-oracle
- Node: tested 20.18 / 20.20 / 22 (all hang)
- Medusa: 2.17.2 and 2.16.0 (all hang)
- Postgres 16, Redis 7 (also reproduces with
REDIS_URL unset → "fake redis")
- Reproduces both in Docker and on the bare host (so not a container/sandbox issue)
Environment where it works
- x86_64 macOS (local) and x86_64 GitHub Actions
ubuntu-latest — same repo, same DB, migrations complete in seconds.
Steps to reproduce
create-medusa-app app --version 2.17.2 --skip-db --no-browser
cd app/apps/backend
DATABASE_URL=postgres://user:pass@host:5432/db REDIS_URL= NODE_ENV=production \
JWT_SECRET=x COOKIE_SECRET=x STORE_CORS=http://x ADMIN_CORS=http://x AUTH_CORS=http://x \
npx medusa db:migrate
On ARM64: prints Running migrations... (and, on a fresh DB, Migrations table created successfully) then hangs forever. On x86_64: completes.
Diagnosis
- A minimal bare knex transaction using the exact same pattern Medusa uses —
createPgConnection({ pool: { min:1, max:1 } }) + pg_advisory_xact_lock(hashtext(...)) inside knex.transaction() — works fine on the same ARM64 host. So knex/tarn/pg/advisory-locks are not the cause.
- An
async_hooks trace of the hung process shows the never-resolving promise chain is:
@medusajs/modules-sdk .../medusa-app.js runMigrations
→ applyMigration → @medusajs/utils executeWithConcurrency → executeNext (Promise.allSettled)
→ run (medusa-app.js) → lockKnex.transaction()
→ knex .../execution/transaction.js:79/85 (new Transaction, _evaluateContainer) → Promise.then ← never resolves
- A Node diagnostic report at hang time: event loop idle (
loopIdleTimeSeconds climbing), the only referenced active handle is one idle Postgres TCP socket (readable, no data pending), no timers. On the Postgres side that connection is state=idle, wait_event=ClientRead (server already answered, waiting for the client) — i.e. the client is awaiting a completion that will never come.
What was ruled out (all still hang on ARM64)
base image node:20-slim / node:22-slim / node:20 / node:20-alpine; seccomp=unconfined; UV_USE_IO_URING=0; Docker embedded DNS (tested via direct IP); Redis on/off; MTU; telemetry; --skip-links / --skip-scripts / --concurrency 1; Medusa 2.16.0 and 2.17.2.
Impact
Medusa cannot run its own migrations on ARM64 Linux hosts (e.g. Oracle Ampere, AWS Graviton) — a common/cheap deployment target. Runtime (medusa start) works fine on ARM64; only the migrator deadlocks. Current workaround: run medusa db:migrate from an x86_64 machine against the same DB.
Happy to provide the full async_hooks dump / diagnostic report.
Bug:
medusa db:migratedeadlocks (hangs forever) on aarch64/ARM64 Linux; works on x86_64Summary
npx medusa db:migratehangs indefinitely atRunning migrations...on ARM64 Linux hosts, but the identical project + database completes migrations normally on x86_64 (both x86_64 Linux and x86_64 macOS). The hang is an unresolved promise — the Node event loop goes fully idle with no pending I/O — inside the per-module migration transaction. It reproduces with a freshcreate-medusa-appproject (no custom modules), across Medusa 2.16.0 and 2.17.2, Node 20 and 22, and multiple base images.Environment where it hangs
6.8.0-1027-oracleREDIS_URLunset → "fake redis")Environment where it works
ubuntu-latest— same repo, same DB, migrations complete in seconds.Steps to reproduce
On ARM64: prints
Running migrations...(and, on a fresh DB,Migrations table created successfully) then hangs forever. On x86_64: completes.Diagnosis
createPgConnection({ pool: { min:1, max:1 } })+pg_advisory_xact_lock(hashtext(...))insideknex.transaction()— works fine on the same ARM64 host. So knex/tarn/pg/advisory-locks are not the cause.async_hookstrace of the hung process shows the never-resolving promise chain is:loopIdleTimeSecondsclimbing), the only referenced active handle is one idle Postgres TCP socket (readable, no data pending), no timers. On the Postgres side that connection isstate=idle, wait_event=ClientRead(server already answered, waiting for the client) — i.e. the client is awaiting a completion that will never come.What was ruled out (all still hang on ARM64)
base image
node:20-slim/node:22-slim/node:20/node:20-alpine;seccomp=unconfined;UV_USE_IO_URING=0; Docker embedded DNS (tested via direct IP); Redis on/off; MTU; telemetry;--skip-links/--skip-scripts/--concurrency 1; Medusa 2.16.0 and 2.17.2.Impact
Medusa cannot run its own migrations on ARM64 Linux hosts (e.g. Oracle Ampere, AWS Graviton) — a common/cheap deployment target. Runtime (
medusa start) works fine on ARM64; only the migrator deadlocks. Current workaround: runmedusa db:migratefrom an x86_64 machine against the same DB.Happy to provide the full async_hooks dump / diagnostic report.