Categories
Node.js

Deploying a Node app and Postgres database to Fly.io

6 min read

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.

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!