Deploying Node-RED to Google App Engine

Header image for Node-RED GCP deployment post

Node-RED is often deployed to self-hosted environments like a Raspberry PI where all the editing and configuration is done on that machine. This works fine for smaller individual projects, but leaves out the potential for using it as a general purpose workflow automation tool. By deploying our flows on Google App Engine, we can have an extensible automation platform running for dirt cheap.

This project builds on the previous work we did to setup Node-RED in a version-controlled project as a part of our series on using Node-RED as an open-source alternative to Zapier. If you just want to see an example of the final result, checkout the project on GitHub.

Locking-down the configuration

When we deploy Node-RED to our production server we want to completely disable editing functionality. There are a couple benefits to this: all changes will go through version-control, and we don’t need to worry about securing the editor. Plus since we want to use the cheaper App Engine standard environment instead of the flexible environment, the service will be deployed on a read-only filesystem.

The two settings we want to change are httpAdminRoot which when set to false will disable the editor, and readOnly which will prevent Node-RED from trying to create any config files on the host. These are configured in the settings.js file.

// ...
module.exports = {
    // ...
    /** By default, the Node-RED UI is available at http://localhost:1880/
     * The following property can be used to specify a different root path.
     * If set to false, this is disabled.
     */
    httpAdminRoot: false,
    // ...
    /** Prevent the storage module from trying to write files */
    readOnly: true,
    // ...
}

Since our default settings.js file will lock-down the editor, we’re going to need to add another npm script to override those settings for runbning the editor locally to make changes to the flows. The node-red command accepts setting overrides with the -D flag which we’ll use to revert the changes we just made.

{
  // ...
  "scripts": {
    // Override the locked-down settings for local editing
    "editor": "node-red --settings=./settings.js -D httpAdminRoot=/ -D readOnly=false",
    "start": "node-red --settings=./settings.js"
  },
  // ...
}

Now when you want to change any flows you’ll use the npm run editor command to launch the editing UI locally. After making changes and saving them with the “Deploy” button you’ll commit your changed flows.json file to Git.

Setting-up Google App Engine

Our Node-RED production service is going to run on Google App Engine which is a managed environment for running your Node.js application. The main benefits are that we don’t need to worry about any boilerplate configuration of the hosts, and with the right configuration we can actually keep everything within the forever free-tier. Simple and cheap.

For the following steps I just used the Google Cloud web console, but these steps can also be accomplished with the gcloud CLI tool if you’re more comfortable with that. First create a new project and then create an App Engine application in that project.

Now we need to setup a service account that we’ll use later to automatically update our service with GitHub Actions. Go to “IAM & Admin” then “Service Accounts”. Click on the “Create Service Account” button at the top and name it “GitHub Actions”. Next go to the “Manage Permissions” page for the service account you just created. We need to grant the account some permissions to allow it to deploy to App Engine.

  • App Engine Admin (roles/appengine.appAdmin): Can manage all App Engine resources
  • Service Account User (roles/iam.serviceAccountUser): To deploy as the service account
  • Storage Admin (roles/compute.storageAdmin): To upload files
  • Cloud Build Editor (roles/cloudbuild.builds.editor): To build the application

Service account details on GCP

In the same area as the link to “Manage Permissions” you’ll see a link to “Manage Keys”. On this page you’ll need to click “Add Key” then select the JSON option and click “Create”. This will download a JSON file to your computer; the contents of this file are a key that we’ll use later in configuring GitHub Actions.

Finally you’ll need to enable the App Engine Admin API and Cloud Build Admin API for the project. This will require attaching a billing account to the project so go ahead and set that up if you need to.

Automatic deployment with GitHub Actions

Using GitHub Actions we can automatically update our App Engine service everytime a new flow revision is merged into the main branch. Before we get to the workflow itself though we need to setup a couple files that the gcloud CLI will use to deploy our project.

First is the app.yaml file where we’ll define some basic metadata about our service. We’ll configure our service to use the Node.js v16 runtime and some scaling parameters to keep a single instance running. The F1 instance class can run a single instance continuously through the month on the free-tier where the B1 instance class configured for a single instance with manual_scaling will cost about $23/month.

Keeping the service running a single instance at a time helps if you use polling-based triggers like incoming email, which need to have an instance running to check for new email periodically. If you only define HTTP-based incoming triggers, then you can allow the service to autoscale normally.

runtime: nodejs16
instance_class: F1
inbound_services:
  - warmup
automatic_scaling:
  min_instances: 1
  max_instances: 1

We need a way to pass in the secret key we setup using dotenv previously. Google advises against using environment variables for secrets and instead suggests using their hosted secrets service. This is a good idea for many use-cases, but for our purposes environment variables are fine.

The strategy we’re going to use to get the secret credential key deployed with our service is to store the value in a GitHub secret then write it to a .env file at deploy time and let the gcloud CLI bundle it with the application. The problem is that by default the CLI will use your .gitignore file exclusions while packaging. To get around this we’re going to create a .gcloudignore file in our project that will exclude everything in our .gitignore except for the .env file that will get written in our workflow.

#!include:.gitignore
!.env

You’ll need to setup three secrets in your GitHub repository settings for our Actions workflow to use.

  • NODE_RED_CREDENTIAL_FILE_KEY: The secret key from your .env file.
  • GCP_PROJECT: The project Id of your Google Cloud Platform project.
  • GCP_CREDENTIALS: The service account key generated earlier.

With those secrets in place you should be able to add this workflow to your project. This runs a workflow when changes get merged into your main branch that deploys the changes right to App Engine.

name: Deploy Node-RED to Google App Engine
on:
  push:
    branches:
      - main
jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - name: Create .env file for deployment
        run: |
                    echo "CREDENTIAL_FILE_KEY=${{ secrets.NODE_RED_CREDENTIAL_FILE_KEY }}" >> .env
      - name: Authenticate to Google Cloud Platform
        uses: google-github-actions/auth@v0.7.1
        with:
          credentials_json: ${{ secrets.GCP_CREDENTIALS }}
      - name: Deploy to Google App Engine
        uses: google-github-actions/deploy-appengine@v0.8.0
        with:
          deliverables: app.yaml
          project_id: ${{ secrets.GCP_PROJECT }}

Once you have this workflow merged into your main branch you should see it run under your repository’s “Actions” tab. After successfully completing you should see the dashboard on the App Engine console update with information about the service, including the public URL where it’s accessible.