Many Node.js projects don’t require the complexity of "infrastructure as code" based tooling, such as CloudFormation or Terraform. Cloud application platforms can help provide a low friction deployment process that’s more "here’s my app, please get it in production for me!". One newer platform offering this style of deployment is Fly.io. I recently deployed a Node.js application and Postgres database to Fly while creating my upcoming egghead course.
Deploying to Fly was mostly a smooth process thanks to their Build, Deploy & Run a Node Application guide, but I discovered a few extra things I needed to take care of along the way. I wrote them up as I figured they might be helpful for other folks looking to deploy Node.js applications on Fly. This isn’t an exhaustive Fly deployment guide, but it should give you a solid base for getting things up and running.
The Node.js application I was deploying is built with the Fastify framework, but the steps in this guide should generally apply to any Node.js application you want to deploy to Fly.
Jump links
Prepare the application for deployment
Configure the Node.js server to listen on all network interfaces
We need to configure our server to listen on IP address 0.0.0.0
. This means that it will then bind to all available network interfaces. If we don’t set this then our server will typically only bind to local network IP addresses (127.0.0.1
for IPv4 and ::1
for IPv6) — this is fine in development, but no good in production.
With the application I’d built with Fastify, it meant configuring it to listen like this:
app.listen({ port: process.env.PORT, host: "0.0.0.0" });
The documentation for your Node.js framework of choice should provide details on how to configure the host name.
I received an error when after I’d first deployed my application to Fly telling me that v1 failed - Failed due to unhealthy allocations
. The Host checking section of the Fly troubleshooting guide helped get me back on track.
Set a Node.js version in package.json
When deploying my application to Fly I noticed in the build output in my terminal that it was using the latest version of Node.js (v18.16.0 at the time). I prefer to run a Long-Term Support (LTS) release of Node.js in production, as they’re considered stable and will typically receive critical bug fixes for 2.5 years. It’s also what the Node.js project recommends.
To specify the version of Node.js that Fly will use for building and running our Node.js applications we need to add an engines
object in package.json
, for example:
"engines": {
"node": "16.16.0"
}
That’s it — the Node.js version specified in engines.node
will be automatically detected by Fly. You can check the latest LTS version of Node.js on the Node.js Downloads page.
Set up the Fly CLI
We’ll use the Fly CLI for creating, configuring and deploying our application to Fly. We can follow the Installing flyctl documentation to install the Fly CLI.
Once the CLI has been installed, there’s a message telling us that we need to add the directory path where the CLI has been installed to our shell PATH
. This will allow us to run the fly
CLI program in our terminal. Typically we should be able to add the export
statements to the bottom of our shell’s rc file:
- bash —
~/.bashrc
or~/.bash_profile
- zsh —
~/.zshrc
- fish —
~/.config/fish/config.fish
. Instead of the export statements, we need to add something along the lines of:set -x PATH $PATH /path/to/the/.fly/bin
Now we need to restart our terminal or open a new terminal tab. We should be able to run the fly
command in our terminal if the Fly CLI has been correctly included in our PATH
. This command outputs detailed usage instructions for the Fly CLI.
Create a Fly account
To create an account on Fly, we can run:
fly auth signup
Then we’ll go through the signup process in our web browser (keep an eye out for the email verification email they send through). If we don’t set a payment method Fly will provide us with a free allowance under their Trial Plan. The Fly pricing page has full details on the resources that are included for free.
Once we’ve finished creating an account in our browser, we should automatically be logged in to our account on the Fly CLI.
Heroku recently announced the removal of their free plans. The good news is that Fly offer a generous free allowance, and the Heroku "buildpack" approach is supported by Fly through the standardised Cloud Native Buildpacks ecosystem. Fly can also deploy existing Docker images, or build a Docker image for you. Their Builders and Fly docs explain how this works.
Create a Fly application
To create our Fly application, we run:
fly launch
This command runs an interactive process that allows us to specify the settings we want for our Fly application. It asks us:
- The name we want for our new app.
- What region we want to deploy the app to — if we don’t have a specific requirement, we can just select the region nearest to us.
- If we’d like to set up a PostgreSQL database app too — yes please!
- The configuration for our PostgreSQL database — ‘Development’ is fine for testing things out.
- If we want to deploy now — we’ll say no as there are a couple of other things we need to configure before we can deploy.
What does Fly create for us?
Once fly launch
is finished, it will have:
- Created a Fly app for us to deploy our Node.js application to.
- Created a Fly Postgres app — see Postgres on Fly for details on how this works.
- Attached the Postgres app to the Node.js app.
- Created a
fly.toml
configuration file in our local project directory.
When Fly attaches the Postgres app to our Node.js app, it sets a DATABASE_URL
environment variable on the Node.js app (docs). This environment variable contains a Postgres connection string URI. We can access it via process.env.DATABASE_URL
in our Node.js application, and pass it in wherever we need to configure our database library connection.
We can connect to the Postgres console for our Postgres app with:
fly pg connect --app <POSTGRES_APP_NAME>
Now we need to configure a few things before we can deploy our Node.js application to Fly.
Configure the Fly application
Configure pre-deployment tasks with a release command
If we’re using a database library such as Knex.js or Prisma, we’ll probably need to run database migrations after our application has been built, but before it’s deployed. We can specify a command for Fly to run by adding a deploy
block to our fly.toml
:
# fly.toml
[deploy]
release_command = "npx knex migrate:latest"
In the code block above we’re running the migration command for Knex.js, but we can specify any command that we want here. For example, for Prisma it would be npx prisma migrate deploy
.
See the fly.toml deploy docs for more details.
Set environment variables
As I mentioned earlier, the DATABASE_URL
environment variable will be automatically made available to our Node.js application when it’s deployed by Fly. For any environment variables that don’t contain secrets (passwords, API keys etc.), we can set them under an env
block in our fly.toml
. Here’s an example from the application I deployed:
# fly.toml
[env]
PORT = "8080"
DATABASE_ENVIRONMENT = "production"
The PORT
environment is already there in the default configuration that’s generated by the Fly CLI, but we can change it if we want to. See the Fly configuration reference for more details on setting environment variables via fly.toml
.
If we need to set environment variables for our application that contain secrets, we can set them using the fly secrets
command. The Fly Secrets documentation explains how to set and unset secrets.
Create a .dockerignore
When Fly builds our application for deployment it creates a Docker image. The node_modules
directory will be regenerated every time Fly builds our application, so let’s create a .dockerignore
file that excludes it from the Docker build:
# .dockerignore
node_modules
Deploy the application
To deploy our Node.js application to Fly we’ll run:
fly deploy
The first deploy will take a little while, but subsequent deploys are typically a lot faster as Fly caches our dependencies and the layers of the Docker image that it builds for deployment.
Here are a few handy commands that we can use once our application has been deployed:
- Check it’s status with
fly status
- Open it up in our web browser with
fly open
- SSH in to a running instance of the application with
flyctl ssh console
— helpful for debugging our application in production.
Ready to Fly?
Do you see yourself giving Fly a try for hosting your next Node.js project? Or are you up and running with it already? Either way, I hope this blog post helps!