How to Deploy a Node.js app on OpenShift

I write often about Java, but I don’t feel like I’ve given enough love to the JavaScript crew lately.

Are you a back-end JavaScript developer? This article is for you!

Do you want to know how to build a Docker image for your Node API, and then deploy it in OpenShift? Then read on.

In this tutorial I’ll go through deploying a Node.js hello world application onto OpenShift.

By the way, you’ll need the oc tool installed for this.

What we’re going to build

In this article we’re going to deploy a JavaScript application to OpenShift.

When you deploy an app into OpenShift or Kubernetes, you’re basically creating a bunch of objects, that usually look like JSON or YAML files.

A typical application in OpenShift consists of a BuildConfig, a Deployment, a Service and a Route.

Object What it does
BuildConfig Builds a Docker image inside OpenShift from our source code, and push it to OpenShift’s internal Docker registry. In this tutorial, we’re going to use the Source-to-Image (S2I) method for building an image.
Deployment Deploys the image. It creates a set of Pods to run the image. (see some Deployment examples)
Service This load-balances traffic across the Pods.
Route Exposes the app outside the OpenShift cluster.

The BuildConfig will build our Docker image, and then the other objects are responsible for deploying it, and making it available to the outside world.

First, we’ll need an app to deploy

First, we need an application to deploy.

If you just want to skip creating an app, and use one I made earlier, then use this one. This is using Node.js/Express:

https://github.com/monodot/container-up/tree/master/node-hello-world

If you’ve already got your own app, make sure you’ve configured Node to listen on all interfaces (0.0.0.0) and listen on port 8080.

But what about if you want to create an app, from scratch? I’ll show you how I created one:

I’m no JavaScript coder, by any stretch of the imagination. So, to keep me on the safe side, I’m going to keep things as basic as possible.

I decided to use Express JS, and started out by creating a new Express application, using the Express Generator:

Screenshot of Express application generator documentation

Use the Express Generator to create a new ExpressJS app

If you use this generator to create your Express app, then make sure you add the --no-view option. This is because by default, the generator sets up Jade as your HTML rendering engine, and it’s apparently already deprecated and full of critical bugs (!!!). So either swap to the pug engine, or add --no-view.

Here’s the command that I used, in case you want to do the same as me:

npx express-generator node-hello-world --no-view

Now you should have a basic Express app.

cd into the directory, run npm install and npm start and you should get an API.

Got your Node.js application ready to deploy? Read on, in the next section we’ll look at building an image from your code.

Build and deploy with oc new-app

So now we’ve got our Node.js application ready.

The next thing to do is create the necessary objects in OpenShift: a BuildConfig, a Deployment, a Service and a Route.

The oc new-app command will create all of those objects for you. Just pass it your Git repo URL, and optionally a context-dir (if your code is in a subfolder), and a name:

oc new-app https://github.com/monodot/container-up
  --context-dir=node-hello-world 
  --name=node-app 
  --strategy=source

(My example repo contains a Dockerfile, so I’m using --strategy=source to force OpenShift to use an S2I build, instead of a Docker build.)

OpenShift will detect the language of the application (Node), and try to pick an appropriate S2I builder image, from the image streams that are installed in your cluster.

If your cluster is missing the node S2I builder images, or you just want to use a different S2I builder image, like the community CentOS NodeJS image, you can give the full image URL with a tilde, like this:

oc new-app quay.io/centos7/nodejs-12-centos7~https://github.com/monodot/container-up \
  --name=node-app
  --context-dir=node-hello-world
  --strategy=source

And you’ll get output like this:

$ oc new-app https://github.com/monodot/container-up --name=node-app --context-dir=node-hello-world
--> Found container image 995ff80 (7 days old) from Docker Hub for "node:14"

    * An image stream tag will be created as "node:14" that will track the source image
    * A Docker build using source code from https://github.com/monodot/container-up will be created
      * The resulting image will be pushed to image stream tag "node-app:latest"
      * Every time "node:14" changes a new build will be triggered
    * This image will be deployed in deployment config "node-app"
    * Port 8080/tcp will be load balanced by service "node-app"
      * Other containers can access this service through the hostname "node-app"
    * WARNING: Image "node:14" runs as the 'root' user which may not be permitted by your cluster administrator

--> Creating resources ...
    imagestream.image.openshift.io "node" created
    imagestream.image.openshift.io "node-app" created
    buildconfig.build.openshift.io "node-app" created
    deploymentconfig.apps.openshift.io "node-app" created
    service "node-app" created
--> Success
    Build scheduled, use 'oc logs -f bc/node-app' to track its progress.
    Application is not exposed. You can expose services to the outside world by executing one or more of the commands below:
     'oc expose svc/node-app' 
    Run 'oc status' to view your app.

Which port and host?

It’s really important that your Node app is serving on the correct host and port. Otherwise, OpenShift will not be able to route requests to it.

For oc new-app to work, your app should run on the same port that is exposed by the S2I image. (A Docker image usually has an EXPOSE instruction, which says which ports the container should expose)

The Red Hat and CentOS S2I images generally expose port 8080. This means that your Node app also needs to run on port 8080.

You also need to make sure that your app is listening on all interfaces. Set your server listen address to 0.0.0.0, not “localhost” or “127.0.0.1”.

Behind the scenes, OpenShift will start a Build to build your app and create a Docker image. You can follow the build logs:

oc logs -f bc/node-app

Finally, the last step is to expose your app by creating a Route (or you could create an Ingress object on OpenShift if you prefer):

oc expose svc/node-app

And now our app should be up and available! Super.

Accessing the app

We can test it out:

Get the URL to the app by using oc get route. The Route is the thing you created at the end of the last section:

$ oc get route
NAME           HOST/PORT                                                             PATH   SERVICES       PORT       TERMINATION   WILDCARD
node-app   node-app-toms-project.apps.example.com          node-app   8080-tcp                 None

If you’ve deployed my example app, then you can hit the endpoint with curl, adding /greeting to the end:

curl http://node-app-toms-project.apps.example.com/greeting

which will give you:

{"greeting":"Hello, world!"}

Success

So there you have it. A Node JS Express application built and deployed onto OpenShift.

We used oc new-app to detect the language of our app, and create a BuildConfig. This ran a Source-to-Image (S2I) build, to create a Docker image.

oc new-app also created a Deployment and a Service to run the container in Pods. Finally, we created a Route so we could access the API outside the cluster.

And a reminder of the main gotchas:

  • Make sure your app is running on the same port as the S2I builder image you use. e.g. port 8080.

  • Make sure your app is listening on all addresses (0.0.0.0)

  • If oc new-app can’t find any suitable S2I builder images in the cluster, you can always specify the image you want to use, with oc new-app <image url>~<git url>

Next steps….

  • Try updating your code in Git and run a new build, from the web console.

  • See how to run Docker builds in OpenShift, if you prefer that instead of S2I.

Thanks for reading!

Comments

Got any thoughts on what you've just read? Anything wrong, or no longer correct? Sign in with your GitHub account to leave a comment.

(All comments get added as Issues in our GitHub repo here, using the comments tool Utterances)