Run a Meteor App
Getting an application running on Fly.io is essentially working out how to package it as a deployable image. Once packaged it can be deployed to the Fly.io platform.
In this guide we’ll learn how to deploy a Meteor application on Fly.io.
This guide is intended for users of Meteor 3. If you’re using Meteor 2 and want to migrate, please see the Meteor 3.0 migration guide.
Meteor is a full-stack JavaScript framework for building modern web and mobile applications. You get real-time features for free, built-in accounts, TypeScript support, and a powerful CLI tool for creating and managing your app.
We’ll start off by installing Meteor:
npx meteor
=> Arch: os.osx.arm64
=> Meteor Release: 3.0.0
Downloading |█████████████████████████████████░░░░░░░| 82%
Generate the Meteor app
We’ll assume you have NodeJS installed already. Meteor 3 requires NodeJS v20 or higher. We’ll be using a skeleton web application generated by Meteor. This is a bare-bones app that does not need a MongoDB database.
Let’s create the new project by running meteor create
. We’ll choose a “minimal” skeleton, which is a good choice for a new project and to get up and running quickly.
meteor create hello-meteor --minimal --release 3.0.0
Created a new Meteor app in 'hello-meteor'.
To run your new app:
cd hello-meteor
meteor
If you are new to Meteor, try some of the learning resources here:
https://www.meteor.com/tutorials
When you’re ready to deploy and host your new Meteor application, check out
Cloud:
https://www.meteor.com/cloud
Preview your Application
You can preview your production build locally, simply by executing the command meteor
.
meteor
[[[[[ ~/hello-meteor ]]]]]
=> Started proxy.
=> Started HMR server.
=> Started your app.
=> App running at: http://localhost:3000/
Install flyctl and Login
We are ready to start working with Fly.io, and that means we need flyctl
, our CLI app for managing apps on Fly.io. If you’ve already installed it, carry on. If not, hop over to our installation guide. Once that’s installed you’ll want to log in to Fly.io.
Deploy the app on Fly.io
Each Fly App needs a fly.toml
file to tell the system how we’d like to deploy it.
That file can be automatically generated with fly launch
. This command will also generate a Dockerfile for deployment.
fly launch
Scanning source code
Detected a Meteor app
Creating app in ~/Demo/hello-meteor
We're about to launch your Meteor app on Fly.io. Here's what you're getting:
Organization: Demo (fly launch defaults to the personal org)
Name: hello-meteor (generated)
Region: Johannesburg, South Africa (this is the fastest region for you)
App Machines: shared-cpu-1x, 1GB RAM (most apps need about 1GB of RAM)
Postgres: <none> (not requested)
Redis: <none> (not requested)
Tigris: <none> (not requested)
? Do you want to tweak these settings before proceeding? No
Created app 'hello-meteor' in organization 'demo'
Admin URL: https://fly.io/apps/hello-meteor
Hostname: hello-meteor.fly.dev
installing: npm install @flydotio/dockerfile@latest --save-dev
added 33 packages, and audited 143 packages in 4s
32 packages are looking for funding
run `npm fund` for details
found 0 vulnerabilities
create Dockerfile
Wrote config file fly.toml
Validating ~/Demo/hello-meteor/fly.toml
✓ Configuration is valid
==> Building image
Remote builder fly-builder-frosty-night-3947 ready
==> Building image with Docker
...
Watch your deployment at https://fly.io/apps/hello-meteor/monitoring
Provisioning ips for hello-meteor
Dedicated ipv6: 2a09:8280:1::3c:c2d4:0
Shared ipv4: 66.241.124.224
Add a dedicated ipv4 with: fly ips allocate-v4
This deployment will:
* create 2 "app" machines
No machines in group app, launching a new machine
Creating a second machine to increase service availability
Finished launching new machines
-------
NOTE: The machines for [app] have services with 'auto_stop_machines = true' that will be stopped when idling
-------
Checking DNS configuration for hello-meteor.fly.dev
Visit your newly deployed app at https://hello-meteor.fly.dev/
...
Inside fly.toml
The fly.toml
file now contains a default configuration for deploying your app. In the process of creating that file, flyctl
has also created a Fly.io application slot of the same name, “hello-meteor”. If we look at the fly.toml
configuration file we can see the name in there:
app = "hello-meteor"
primary_region = "jnb"
[env]
PORT = "3000"
ROOT_URL = "https://hello-meteor.fly.dev/"
[http_service]
internal_port = 3000
force_https = true
auto_stop_machines = true
auto_start_machines = true
min_machines_running = 0
processes = ["app"]
...
We make sure to set the ROOT_URL
and PORT
environment variables. These are important to Meteor. When you eventually add a custom domain to your app, you’ll want to make sure your ROOT_URL
is set to the domain name.
The flyctl
command will always refer to this file in the current directory if it exists, specifically for the app
name/value at the start. That name will be used to identify the application to the Fly.io platform.
The rest of the file contains settings to be applied to the application when it deploys.
Viewing the Deployed App
If you want to find out more about the deployment. The command fly status
will give you all the essential details.
fly status
App
Name = hello-meteor
Owner = personal
Hostname = hello-meteor.fly.dev
Image = hello-meteor:deployment-01J2S3S8A9TDR1D2AYZSYJWTHH
Machines
PROCESS ID VERSION REGION STATE ROLE CHECKS LAST UPDATED
app 3287154db00658 1 jnb started 2024-07-14T17:26:16Z
app 4d890d17a6d768 1 jnb stopped 2024-07-14T17:23:03Z
Connecting to the App
The quickest way to browse your newly deployed application is with the fly apps open
command.
fly apps open
Opening https://hello-meteor.fly.dev/
Your browser will be sent to the displayed URL.
Session Affinity (aka ‘Sticky Sessions’)
Sticky sessions are a feature of a load balancer that ensures that all requests from a client are routed to the same server. This can be important for Meteor applications depending on the client/server architecture of your app, as it ensures that the client is always connected to the same server, and that any changes made to the server are reflected in the client.
The easiest way to achieve this on Fly.io is to run your app with a maximum of one Machine per region. The Fly Proxy will take care of load balancing clients — sending requests to the Machine closest to them.
A better option, is to use dynamic request routing with the fly-replay
response header. You’ll need to implement some additional code (roughly 18 lines) that uses the meteor/webapp
package and plugs into your app’s server-side entry point. This example uses middleware to create a client-side cooking containing the Machine ID to “pin” requests to. The process is explained in greater detail here.
First, install cookie-parser
:
npm install cookie-parser
Now create a new file called sticky.js
wherever you keep your server server-side code:
import { WebApp } from 'meteor/webapp';
import cookieParser from "cookie-parser";
WebApp.connectHandlers.use(cookieParser());
WebApp.connectHandlers.use('/',(request, response, next) => {
if (!process.env.FLY_MACHINE_ID) {
next();
} else if (!request.cookies["fly-machine-id"]) {
const maxAge = 24 * 60 * 60 * 1000; // 24 hours
response.cookie("fly-machine-id", process.env.FLY_MACHINE_ID, { maxAge });
next();
} else if (request.cookies["fly-machine-id"] !== process.env.FLY_MACHINE_ID) {
response.set('Fly-Replay', `instance=${request.cookies["fly-machine-id"]}`)
response.status(307)
response.send()
} else {
next();
}
});
Make sure to import that module when initializing your Meteor server, then you should see clients routed to the same Fly Machine for the duration of the session.
Bonus Points
Let’s take a look at a slightly more advanced app that uses a MongoDB database for storing account details and user data — but also for real-time UI updates.
First, we’ll need a MongoDB database to connect to. If you have one, great! If not, it’s easy to deploy a development MongoDB database on Fly.io. If you’re deploying a production app, we recommend that you look at a managed service like MongoDB Atlas.
Save the below as your mongo.fly.toml
, then run fly launch -c mongo.fly.toml --no-deploy
to create the database app without deploying it.
app = "hello-mongo"
primary_region = "jnb"
[build]
image = 'mongodb/mongodb-community-server'
[experimental]
cmd = ["mongod", "--bind_ip_all", "--ipv6"]
[[services]]
internal_port = 27017
protocol = "tcp"
[[services.ports]]
handlers = ["tls"]
port = 27017
force_https = false
[[vm]]
memory = '1gb'
cpu_kind = 'shared'
cpus = 1
[[mounts]]
source = "mongo_data"
destination = "/data/db"
Now, set the default username and password for the database:
fly secrets set MONGODB_INITDB_ROOT_USERNAME=admin MONGODB_INITDB_ROOT_PASSWORD=password
And finally, deploy it:
fly deploy --no-public-ips
Ok, now we can deploy our Meteor app. We’ll use the Simple Tasks example app:
fly launch --from https://github.com/fredmaiaarantes/simpletasks --no-deploy
This simultaneously clones the GitHub repo and launches the new app on Fly.io
Set the MONGO_URL
secret so Meteor knows where to connect to:
fly secrets set MONGO_URL=mongodb://admin:password@hello-mongo.internal:27017/meteor
And deploy the app:
fly deploy
And finally, open the app. It’ll take a second longer on the first run while database migrations are performed:
fly apps open
That’s it! You should see the app running:
Arrived at Destination
You have successfully built, deployed, and connected to your first Meteor application on Fly.io.