Deploying Node.js apps to Fly.io
This post covers the main notes from setting up the Fly.io CLI tool to CI/CD pipeline with Github actions.
Prerequisites
- Run the following command to install the CLI tool and follow the instructions to add the necessary commands to the appropriate shell configuration file
curl -L https://fly.io/install.sh | sh
- Create an account or log in if you already have an account
fly auth signupfly auth login
Node server
- Run the following commands for packages setup
npm init -ynpm i express
- Write basic server
// server.jsconst express = require('express');const app = express();const PORT = process.env.PORT || 8080;app.get('/', (_, res) => res.send('Hello world'));app.listen(PORT, () => {console.log(`Server is running on port ${PORT}`);});
- Configure
start
script
// package.json{"scripts": {"start": "node server.js"}}
Project setup
Choose the project name and region for deployment. The following command will create a project and generate a configuration file. Project URL will be <PROJECT_NAME>.fly.dev
fly launch
Environment variables
Configuration variables
Generated fly.toml
file contains env
section for environment variables that can be publicly available, like URLs, HTTP timeout values, etc.
// ...[env]PORT = "8080"NEW_VARIABLE = "value"// ...
Secrets
The following commands are used for setting up the secrets like API keys, credentials, etc.
fly secrets set KEY=valuefly secrets listfly secrets unset KEY
Databases
Redis
Run the following commands to provision the Redis database and connect with it. Eviction is disabled by default. URL can be found with the status command. Set it as a secret.
fly redis createfly redis connectfly redis listfly redis status <database-name>
Postgres
Run the following command to bootstrap a Postgres database cluster.
fly postgres create
The previous command will prompt the connection string only once. Set it as a secret.
// knexfile.jsmodule.exports = {// ...production: {client: 'postgresql',connection: process.env.DATABASE_URL// ...}};
Release command can run migrations.
# fly.toml[deploy]release_command = "npm run migrate"
Test the database connection with the following command.
fly postgres connect -a <postgres-app-name>
Backup
Fly.io performs daily snapshots of the provisioned volumes. Snapshots are volume-specific. Postgres database can be restored from the provided snapshot.
fly volumes list -a <postgres-app-name>fly volumes snapshots list <volume-id>fly postgres create --snapshot-id <snapshot-id>
Scaling
Every app runs inside a virtual machine, and the following command shows the VM size and how many app instances are running.
fly scale show
Vertical scaling
- The following command shows the list of VM types and their memory allocations
fly platform vm-sizes
- Run the following command to upgrade the VM to one of the types from the previous command, memory allocation in MBs can be specified as well
fly scale vm <type> --memory 1024
- Run the following command to increase VM memory, 1024MB in this case
fly scale memory 1024
Horizontal scaling
Run the following command to increase the number of app instances, 3 in this case
fly scale count 3
Autoscaling is disabled by default. Run the following commands to configure it.
fly autoscale showfly autoscale set min=3 max=6
Debugging
- Get the real-time logs
fly logs
- Get the info about the app, services, and IP addresses
fly info
- Get the state of the project
fly status
- Get the app IP addresses
fly ips list
- Run bash on the app
fly ssh console
- Restart the app
fly apps restart <app-name>
Deployment
Manual deployment
The following commands are used to deploy and open the deployed version.
fly deploy --no-cachefly open
Continuous deployment with Github actions
A project auth token is needed. Run the following command to generate it. Add the generated key as a Github action secret in the Settings → Secrets → Actions page
fly auth token
Add .github/workflows/config.yml
file with the following configuration.
name: CI/CD pipelineon:push:branches:- mainenv:FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }}jobs:build-and-deploy:runs-on: ubuntu-latestcontainer: node:20.9.0-alpine3.17steps:- name: Github checkoutuses: actions/checkout@v4- uses: actions/setup-node@v4with:node-version: 20- run: npm ci- run: npm run lint- run: npm test- run: npm audit- name: Setup Fly.io configuses: superfly/flyctl-actions/setup-flyctl@master- name: Deploy to Fly.iorun: flyctl deploy --remote-only --no-cache
Boilerplate
Here is the link to the boilerplate I use for the development.