+1 (478) 654-9062

Where to Run a Meteor App: Galaxy, Your Own Containers, or Somewhere in Between

Hosting rarely makes it into the upgrade conversation. Teams ask about Fibers, about packages, about whether to stay on Meteor at all — and the answer to all three is shaped by where the app runs and who can change it. In an assessment, hosting posture is one of the four things we look at, because it is usually the cheapest thing to fix and the one most likely to be blocking something else.

This is not a recommendation for one host. It is a description of what a Meteor app actually needs at runtime, so you can judge your own situation.

A Meteor bundle is a plain Node app, with three conditions

meteor build produces a tarball. Inside it is a bundle/ directory with a main.js, a programs/ tree, and a package.json of native npm dependencies you rebuild on the target platform. Starting it is:

cd bundle/programs/server && npm install
cd ../../ && node main.js

So far, unremarkable. The conditions are what matter:

  1. The Node version is not yours to pick. The bundle expects the Node major that its Meteor release targets — roughly Node 14 for the Meteor 2.x line, Node 20 and later for 3.x. Running a bundle on a different major is not supported and fails in ways that look like application bugs.
  2. Environment variables are the entire configuration surface. MONGO_URL, ROOT_URL, PORT, and on Meteor 2, usually MONGO_OPLOG_URL. ROOT_URL is the one that quietly breaks things: it must be the public URL users actually hit, because it feeds absolute URLs, OAuth callbacks, and email links.
  3. Client traffic is websockets, not just HTTP. DDP runs over a persistent connection with a SockJS-style fallback. Every proxy, load balancer, and CDN between the browser and the app has to pass that through, with an idle timeout longer than your heartbeat interval.

If a hosting option satisfies those three, it can run your app. Most modern platforms can. The differences are in the operational details below.

Sticky sessions, and why they are not optional the way people hope

On more than one instance, DDP connections want to stay pinned to the instance that created them. Without session affinity you get reconnect storms: a client's websocket lands on a new instance, re-subscribes, re-sends its method queue, and the merge box rebuilds from scratch. On a small app this is invisible. On an app with heavy publications it shows up as CPU spikes on every deploy and as intermittent "the page went blank for a second" reports that nobody can reproduce.

Galaxy handles affinity for you. Self-hosted, you configure it — ip_hash or a cookie-based sticky policy in nginx, session affinity in an ingress controller, or a load balancer with sticky target groups. It is a fifteen-minute configuration that gets forgotten for years, so it is worth checking even if you have no plans to change hosts.

Related, and worth measuring once: your heartbeat settings. Meteor sends DDP heartbeats to detect dead connections. If an intermediate proxy has a 60-second idle timeout and your heartbeat is longer, connections die and reconnect on a loop. Symptoms look like a flaky network; the cause is a timeout mismatch.

What Galaxy actually buys, in plain terms

Galaxy is Meteor Software's own platform, and what it sells is Meteor-shaped operations knowledge you do not have to hold yourself: it knows how to run a bundle, it handles affinity, it does rolling deploys with a health check, and its APM shows you method and publication timings with Meteor's own instrumentation rather than generic Node metrics.

That APM view is the underrated part. Per-method and per-publication response times, observer counts, and the breakdown of where a publication spends its time are hard to reconstruct from generic tooling. When we are asked to diagnose a slow app, Galaxy APM data cuts the investigation roughly in half.

The trade is the usual one for managed platforms: less control, a cost that scales with containers rather than with your own infrastructure commitments, and a dependency on a single vendor for the runtime of a revenue-carrying app. Those are real considerations, not disqualifiers. Plenty of teams should stay exactly where they are.

What self-hosting actually costs

Self-hosting a Meteor bundle is not exotic. A Dockerfile that builds the bundle in one stage and runs it on the matching Node base image in another is maybe thirty lines, and from there it is a normal container: ECS, Kubernetes, Fly, a pair of VMs behind nginx, whatever your organization already operates.

The costs are the parts Galaxy was doing quietly:

  • Session affinity, as above.
  • Zero-downtime deploys. You need a readiness check that does not report healthy until the app is actually serving, and a drain period so existing websockets close politely rather than being severed mid-subscription.
  • Node version pinning. Your base image is now coupled to your Meteor release. When you upgrade Meteor, the image changes with it — write that down in the upgrade plan or discover it on deploy day.
  • Native dependency rebuilds. npm install inside programs/server must run on the same OS and architecture as production. Build on ARM, deploy on x86, and you get a bcrypt error at boot. Build inside the target image and this disappears.
  • Your own observability. Meteor-specific metrics are not free anymore. At minimum, instrument method durations and publication counts; a self-hosted APM package or an OpenTelemetry setup around the DDP layer both work, but somebody has to own it.

None of these are hard. All of them are somebody's job, ongoing. That is the honest framing: self-hosting is not cheaper, it is differently expensive, and the deciding factor is usually whether you already have a platform team running containers for other services. If you do, a Meteor app is one more container. If you do not, you are standing up a platform to host one app.

MongoDB posture is a separate decision — treat it that way

Where the app runs and where the database runs are independent choices, and conflating them causes bad outcomes. Two things to check regardless of host:

  • Is it a replica set? Meteor 2 needs one for oplog tailing; Meteor 3 needs one for change streams. A standalone mongod on the same box as the app is the single most common configuration we are asked to fix, and converting to a single-node replica set is the first step out.
  • Is there network distance between app and database? Every publication observer and method round-trips to Mongo. Cross-region latency turns a 5 ms method into a 60 ms method, and users feel that on every keystroke in a reactive UI. Same region, ideally same availability zone.

The Meteor 3 move from oplog tailing to change streams removes the one Meteor-specific hosting constraint that used to matter here: no MONGO_OPLOG_URL, no support ticket to get oplog read access on a managed cluster. If oplog access has been the reason you could not use a particular managed Mongo provider, that reason expires with the upgrade.

How this fits the bigger decision

Hosting changes are attractive because they are reversible and they do not touch application code. They are also easy to over-prioritize. A useful ordering:

  1. Fix anything that is a live risk — an unsupported database version, no tested restore, a missing replica set.
  2. Fix anything blocking an upgrade — an oplog constraint, a Node version pinned by a base image nobody wants to change.
  3. Fix anything costing real money or real incident time — no affinity, deploys that drop connections, no visibility into method timings.
  4. Then, if a platform change still looks worth it, do it as its own project with a rollback path, not bundled into the Meteor 3 upgrade.

That last point is the one we argue for most often. Changing the platform and changing the framework version in the same window means that when latency moves, you cannot say which change moved it. Do one, measure, then do the other.

And sometimes the right answer is to change nothing. An app on Galaxy, with a supported managed MongoDB, deploying cleanly, is in a fine place — whether it stays on Meteor for another five years or gets strangled route by route into something else. Hosting is not where that decision gets made. It just determines how much friction the decision meets when you finally make it.