Migrating Node.js apps from Heroku to Cloud Run

Last reviewed 2023-10-01 UTC

This tutorial describes how to migrate Node.js web apps that are running on Heroku to Cloud Run on Google Cloud. This tutorial is intended for architects and product owners who want to migrate their apps from Heroku to managed services on Google Cloud.

Cloud Run is a managed compute platform that lets you run stateless containers that are invocable through HTTP requests. It is built on open source Knative, which enables portability across platforms and supports container workflows and standards for continuous delivery. The Cloud Run platform is well integrated with the Google Cloud product suite and makes it easier for you to design and develop apps that are portable, scalable, and resilient.

In this tutorial, you learn how to migrate an app to Google Cloud that's written in Node.js and uses Heroku Postgres as a backing service on Heroku. The web app is containerized and hosted in Cloud Run and uses Cloud SQL for PostgreSQL as its persistence layer.

In the tutorial, you use a simple app called Tasks that lets you view and create tasks. These tasks are stored in Heroku Postgres in the current deployment of the app on Heroku.

This tutorial assumes that you are familiar with the basic functionality of Heroku and that you have a Heroku account (or access to one). It also assumes you are familiar with Cloud Run, Cloud SQL, Docker, and Node.js.

Objectives

  • Build a Docker image to deploy the app to Cloud Run.
  • Create a Cloud SQL for PostgreSQL instance to serve as the backend after migration to Google Cloud.
  • Review the Node.js code to understand how Cloud Run connects to Cloud SQL and to see the code changes (if any) that are required in order to migrate to Cloud Run from Heroku.
  • Migrate data from Heroku Postgres to Cloud SQL for PostgreSQL.
  • Deploy the app to Cloud Run.
  • Test the deployed app.

Costs

In this document, you use the following billable components of Google Cloud:

To generate a cost estimate based on your projected usage, use the pricing calculator. New Google Cloud users might be eligible for a free trial.

You might also be charged for the resources you use on Heroku.

Before you begin

  1. Sign in to your Google Cloud account. If you're new to Google Cloud, create an account to evaluate how our products perform in real-world scenarios. New customers also get $300 in free credits to run, test, and deploy workloads.
  2. In the Google Cloud console, on the project selector page, select or create a Google Cloud project.

    Go to project selector

  3. Make sure that billing is enabled for your Google Cloud project.

  4. Enable the Cloud SQL, Cloud Build, Cloud Run, Container Registry, Service Networking, Serverless VPC Access APIs.

    Enable the APIs

  5. Make sure that you have the following role or roles on the project: Cloud Run > Cloud Run Admin, Cloud Storage > Storage Admin, Cloud SQL > Cloud SQL Admin, Compute Engine > Compute Network Admin, Resource Manager > Project IAM Admin, Cloud Build > Cloud Build Editor, Serverless VPC Access > Serverless VPC Access Admin, Logging > Logs Viewer, Service Accounts > Service Account Admin, Service Accounts > Service Account User, and Service Usage > Service Usage Consumer

    Check for the roles

    1. In the Google Cloud console, go to the IAM page.

      Go to IAM
    2. Select the project.
    3. In the Principal column, find the row that has your email address.

      If your email address isn't in that column, then you do not have any roles.

    4. In the Role column for the row with your email address, check whether the list of roles includes the required roles.

    Grant the roles

    1. In the Google Cloud console, go to the IAM page.

      Go to IAM
    2. Select the project.
    3. Click Grant access.
    4. In the New principals field, enter your email address.
    5. In the Select a role list, select a role.
    6. To grant additional roles, click Add another role and add each additional role.
    7. Click Save.
  6. In the Google Cloud console, on the project selector page, select or create a Google Cloud project.

    Go to project selector

  7. Make sure that billing is enabled for your Google Cloud project.

  8. Enable the Cloud SQL, Cloud Build, Cloud Run, Container Registry, Service Networking, Serverless VPC Access APIs.

    Enable the APIs

  9. Make sure that you have the following role or roles on the project: Cloud Run > Cloud Run Admin, Cloud Storage > Storage Admin, Cloud SQL > Cloud SQL Admin, Compute Engine > Compute Network Admin, Resource Manager > Project IAM Admin, Cloud Build > Cloud Build Editor, Serverless VPC Access > Serverless VPC Access Admin, Logging > Logs Viewer, Service Accounts > Service Account Admin, Service Accounts > Service Account User, and Service Usage > Service Usage Consumer

    Check for the roles

    1. In the Google Cloud console, go to the IAM page.

      Go to IAM
    2. Select the project.
    3. In the Principal column, find the row that has your email address.

      If your email address isn't in that column, then you do not have any roles.

    4. In the Role column for the row with your email address, check whether the list of roles includes the required roles.

    Grant the roles

    1. In the Google Cloud console, go to the IAM page.

      Go to IAM
    2. Select the project.
    3. Click Grant access.
    4. In the New principals field, enter your email address.
    5. In the Select a role list, select a role.
    6. To grant additional roles, click Add another role and add each additional role.
    7. Click Save.

Setting up your environment

  1. Open Cloud Shell.

    OPEN Cloud Shell

  2. In Cloud Shell, set environment variables and default values for the Google Cloud CLI used in this tutorial.

    gcloud config set project PROJECT_ID
    gcloud config set run/region us-central1
    

    Replace PROJECT_ID with your project ID.

Architecture

The following figures outline the web app's architecture on Heroku (as is) and its architectural layout on Google Cloud (that you will build).

As-is architecture on Heroku.
Figure 1. As-is architecture on Heroku

The Tasks app that's currently deployed in Heroku consists of one or more web dynos. Web dynos are able to receive and respond to HTTP traffic, unlike worker dynos, which are better suited for background jobs and timed tasks. The app serves an index page that displays tasks stored in a Postgres database, using the Mustache templating library for Node.js.

You can access the app at an HTTPS URL. A /tasks route at that URL lets you create new tasks.

As-is architecture on Heroku.
Figure 2. Architecture that you build on Google Cloud

On Google Cloud, Cloud Run is used as the serverless platform to deploy the Tasks app. Cloud Run is designed to run stateless, request-driven containers. It is well suited for when you need your managed service to support containerized apps that autoscale and also scale to zero when they're not serving traffic.

Mapping components used in Heroku to Google Cloud

The following table maps components in the Heroku platform to Google Cloud. This mapping helps you translate the architecture outlined in this tutorial from Heroku to Google Cloud.

Component Heroku platform Google Cloud
Containers Dynos: Heroku uses the container model to build and scale Heroku apps. These Linux containers are called dynos and can scale to a number that you specify to support resource demands for your Heroku app. You can select from a range of dyno types based on your app's memory and CPU requirements. Cloud Run containers: Google Cloud supports running containerized workloads in stateless containers that can be run in a fully managed environment or in Google Kubernetes Engine (GKE) clusters.
Web app Heroku app: Dynos are the building blocks of Heroku apps. Apps usually consist of one or more dyno types, usually a combination of web and worker dynos. Cloud Run service: A web app can be modeled as a Cloud Run service. Each service gets its own HTTPS endpoint and can automatically scale up or down from 0 to N based on the traffic to your service endpoint.
Database Heroku Postgres is Heroku's database as a service (DaaS) based on PostgreSQL. Cloud SQL is a managed database service for relational databases on Google Cloud.

Deploying the sample Tasks web app to Heroku

The next sections show how to set up the command-line interface (CLI) for Heroku, clone the GitHub source repository, and deploy the app to Heroku.

Set up the command-line interface for Heroku

This tutorial runs the Heroku CLI in Cloud Shell and must authenticate using a Heroku API key. When running in Cloud Shell, the Heroku CLI cannot authenticate using a password or web-based authentication.

Alternatively, if you do run the sample on a local terminal, you can use any Heroku CLI authentication method. When running the tutorial on a local terminal, you must also install Google Cloud CLI, git, and Docker.

  1. Log in to the web console for Heroku, and then from your account settings page, copy the value of your API key.

  2. In Cloud Shell, install the Heroku CLI

  3. In Cloud Shell, authenticate the Heroku CLI. When prompted for your password, enter the value of your API key that you copied from the Heroku console, not the password you use to sign in to the console.

    heroku login --interactive
    

Clone the source repository

  1. In Cloud Shell, clone the sample Tasks app GitHub repository:

    git clone https://github.com/GoogleCloudPlatform/migrate-webapp-heroku-to-cloudrun-node.git
    
  2. Change directories to the directory created by cloning the repository:

    cd migrate-webapp-heroku-to-cloudrun-node
    

    The directory contains the following files:

    • A Node.js script called index.js with the code for the routes served by the web app.
    • package.json and package-lock.json files that outline the web app's dependencies. You must install these dependencies in order for the app to run.
    • A Procfile file that specifies the command that the app executes on startup. You create a Procfile file to deploy your app to Heroku.
    • A views directory, with the HTML content served by the web app on the "/" route.
    • A .gitignore file.

Deploy an app to Heroku

  1. In Cloud Shell, create a Heroku app:

    heroku create
    

    Make note of the name created for the app. You need this value in the next step.

  2. Create an environment variable for the Heroku app name:

    export APP_NAME=APP_NAME
    

    Replace APP_NAME with the app name returned by the heroku create command.

  3. Add the Heroku Postgres add-on to provision a PostgreSQL database:

    heroku addons:create heroku-postgresql:mini
    
  4. Make sure the add-on was successfully added:

    heroku addons
    

    If the Postgres add-on was successfully added, you see a message similar to the following:

    Add-on               Plan     Price       State
    -----------------    -----    --------    -----
    heroku-postgresql    mini     5$/month    created
    
  5. Deploy the app to Heroku:

    git push heroku master
    
  6. Run the following command to confirm the value of DATABASE_URL.

    heroku config
    

    Make note of the retrieved value for DATABASE_URL. You need this value in the next step.

  7. Run a Docker container.

    docker run -it --rm postgres psql "DATABASE_URL"
    

    Replace DATABASE_URL with the Heroku Postgres URL that you noted in the previous step.

  8. In the Docker container, create the TASKS table by using the following command:

    CREATE TABLE TASKS
    (DESCRIPTION TEXT NOT NULL);
    
  9. Exit the container:

    exit
    
  10. In Cloud Shell, get the Web URL for your Heroku app by running the following command:

    heroku info
    
  11. Open the web URL in a browser window. The app looks the following screenshot (although your version won't have the tasks listed):

    To-do app in the web browser.

  12. Create sample tasks in your app from the browser. Make sure the tasks are retrieved from the database and visible in the UI.

Preparing the web app code for migration to Cloud Run

This section details the steps that you need to complete to prep your web app for deployment to Cloud Run.

Build and publish your Docker container to Container Registry

You need a Docker image to build the app container so it can run in Cloud Run. You can build the container manually or by using Buildpacks.

Build container manually

  1. In Cloud Shell, create a Dockerfile in the directory created by cloning the repository for this tutorial:

    cat <<"EOF" > Dockerfile
    # Use the official Node image.
    # https://hub.docker.com/_/node
    FROM node:10-alpine
    
    # Create and change to the app directory.
    WORKDIR /app
    
    # Copying this separately prevents re-running npm install on every code change.
    COPY package*.json ./
    RUN npm install
    
    # Copy local code to the container image.
    COPY . /app
    
    # Configure and document the service HTTP port.
    ENV PORT 8080
    EXPOSE $PORT
    
    # Run the web service on container startup.
    CMD ["npm", "start"]
    EOF
    
  2. Build your container with Cloud Build and publish the image to Container Registry:

    gcloud builds submit --tag gcr.io/PROJECT_ID/APP_NAME:1 \
      --gcs-log-dir=gs://PROJECT_ID_cloudbuild
    
  3. Create an environment variable to hold the name of the Docker image that you created:

    export IMAGE_NAME="gcr.io/PROJECT_ID/APP_NAME:1"
    

Build container with Buildpacks

  1. In Cloud Shell, install the pack CLI.

  2. Set the pack CLI to use the Heroku builder by default:

    pack config default-builder heroku/buildpacks:22
    
  3. Create an environment variable to hold the Docker image name:

    export IMAGE_NAME=gcr.io/PROJECT_ID/APP_NAME:1
    
  4. Build the image using the pack command and push or publish the image to Container Registry:

    pack build --publish $IMAGE_NAME
    

Create a Cloud SQL for PostgreSQL instance

You create a Cloud