Skip to main content
Redhat Developers  Logo
  • Products

    Featured

    • Red Hat Enterprise Linux
      Red Hat Enterprise Linux Icon
    • Red Hat OpenShift AI
      Red Hat OpenShift AI
    • Red Hat Enterprise Linux AI
      Linux icon inside of a brain
    • Image mode for Red Hat Enterprise Linux
      RHEL image mode
    • Red Hat OpenShift
      Openshift icon
    • Red Hat Ansible Automation Platform
      Ansible icon
    • Red Hat Developer Hub
      Developer Hub
    • View All Red Hat Products
    • Linux

      • Red Hat Enterprise Linux
      • Image mode for Red Hat Enterprise Linux
      • Red Hat Universal Base Images (UBI)
    • Java runtimes & frameworks

      • JBoss Enterprise Application Platform
      • Red Hat build of OpenJDK
    • Kubernetes

      • Red Hat OpenShift
      • Microsoft Azure Red Hat OpenShift
      • Red Hat OpenShift Virtualization
      • Red Hat OpenShift Lightspeed
    • Integration & App Connectivity

      • Red Hat Build of Apache Camel
      • Red Hat Service Interconnect
      • Red Hat Connectivity Link
    • AI/ML

      • Red Hat OpenShift AI
      • Red Hat Enterprise Linux AI
    • Automation

      • Red Hat Ansible Automation Platform
      • Red Hat Ansible Lightspeed
    • Developer tools

      • Red Hat Trusted Software Supply Chain
      • Podman Desktop
      • Red Hat OpenShift Dev Spaces
    • Developer Sandbox

      Developer Sandbox
      Try Red Hat products and technologies without setup or configuration fees for 30 days with this shared Openshift and Kubernetes cluster.
    • Try at no cost
  • Technologies

    Featured

    • AI/ML
      AI/ML Icon
    • Linux
      Linux Icon
    • Kubernetes
      Cloud icon
    • Automation
      Automation Icon showing arrows moving in a circle around a gear
    • View All Technologies
    • Programming Languages & Frameworks

      • Java
      • Python
      • JavaScript
    • System Design & Architecture

      • Red Hat architecture and design patterns
      • Microservices
      • Event-Driven Architecture
      • Databases
    • Developer Productivity

      • Developer productivity
      • Developer Tools
      • GitOps
    • Secure Development & Architectures

      • Security
      • Secure coding
    • Platform Engineering

      • DevOps
      • DevSecOps
      • Ansible automation for applications and services
    • Automated Data Processing

      • AI/ML
      • Data Science
      • Apache Kafka on Kubernetes
      • View All Technologies
    • Start exploring in the Developer Sandbox for free

      sandbox graphic
      Try Red Hat's products and technologies without setup or configuration.
    • Try at no cost
  • Learn

    Featured

    • Kubernetes & Cloud Native
      Openshift icon
    • Linux
      Rhel icon
    • Automation
      Ansible cloud icon
    • Java
      Java icon
    • AI/ML
      AI/ML Icon
    • View All Learning Resources

    E-Books

    • GitOps Cookbook
    • Podman in Action
    • Kubernetes Operators
    • The Path to GitOps
    • View All E-books

    Cheat Sheets

    • Linux Commands
    • Bash Commands
    • Git
    • systemd Commands
    • View All Cheat Sheets

    Documentation

    • API Catalog
    • Product Documentation
    • Legacy Documentation
    • Red Hat Learning

      Learning image
      Boost your technical skills to expert-level with the help of interactive lessons offered by various Red Hat Learning programs.
    • Explore Red Hat Learning
  • Developer Sandbox

    Developer Sandbox

    • Access Red Hat’s products and technologies without setup or configuration, and start developing quicker than ever before with our new, no-cost sandbox environments.
    • Explore Developer Sandbox

    Featured Developer Sandbox activities

    • Get started with your Developer Sandbox
    • OpenShift virtualization and application modernization using the Developer Sandbox
    • Explore all Developer Sandbox activities

    Ready to start developing apps?

    • Try at no cost
  • Blog
  • Events
  • Videos

Modern web applications on OpenShift: Part 2 -- Using chained builds

October 23, 2018
Lucas Holmquist
Related topics:
KubernetesNode.js
Related products:
Red Hat OpenShift

Share:

    In the previous article, we took a quick look at a new source-to-image (S2I) builder image designed for building and deploying modern web applications on Red Hat OpenShift. While the last article was focused on getting your application deployed quickly, this article will look at how to use the S2I image as a "pure" builder image and combine it with an OpenShift chained build.

    Read the series:

    • Part 1: How to deploy modern web apps using the fewest steps

    • Part 2: Combine a Node.js Builder image with a current HTTP server image

    • Part 3: Run your application's development server on OpenShift while syncing with your local file system

    Pure builder image

    As mentioned in the previous article, most modern web applications now have a build step. Common workflows done in the build step are things like transpiling your code, concatenating multiple files, and minifying. Once these workflows are done, the resulting files, which are static HTML, JavaScript, and CSS, are put into an output folder. The location of the folder usually depends on the build tools you are using, but for something like React, the location is ./build (more on this location in a minute).

    Source-to-Image (S2I)

    We won't go into the "what and how" of S2I, but we should understand two of the phases that happen in order to better understand what the Node.js Builder image is doing.

    Assemble phase

    The assemble phase is very similar to what happens when running docker build. The result of this phase will be a new Docker image. This phase also happens when a build is run on OpenShift.

    For the Web App Builder image, the assemble script is responsible for installing your application's dependencies and running your build. By default, the builder image will use npm run build.

    As I said before, the location of your "built" app depends on the build tools you are using. For example, React uses ./build, but an Angular app uses project_name/dist. And, as you saw in Part 1, this output directory, which defaults to build, can be overridden using the OUTPUT_DIR environment variable.

    Run phase

    This phase is run when docker run is called on the newly created image from the assemble phase. This is also what is run during an OpenShift deployment. By default, the run script will run your package.json’s “start” script.

    While this works for getting your app deployed quickly, it is not the recommended way of serving static content. Because we are serving only static content, we don't really need Node.js installed in our image; we just need a web server.

    This situation—where our building needs are different from our runtime needs—is where chained builds can help.

    Chained builds

    To quote the official OpenShift documentation on chained builds:

    Two builds can be chained together: one that produces the compiled artifact, and a second build that places that artifact in a separate image that runs the artifact.

    What this means is that we can use the Web App Builder image to run our build, and then we can use a web server image, like NGINX, to serve our content.

    This allows us to use the Web App Builder image as a "pure" builder and also keep our runtime image small.

    Let's take a look at an example to see how this all comes together.

    This example app, is a basic React application created using the create-react-app CLI tool.

    I've added an OpenShift template file to piece everything together.

    Let's take a look at some of the more important parts of this file.

    parameters:
      - name: SOURCE_REPOSITORY_URL
        description: The source URL for the application
        displayName: Source URL
        required: true
      - name: SOURCE_REPOSITORY_REF
        description: The branch name for the application
        displayName: Source Branch
        value: master
        required: true
      - name: SOURCE_REPOSITORY_DIR
        description: The location within the source repo of the application
        displayName: Source Directory
        value: .
        required: true
      - name: OUTPUT_DIR
        description: The location of the compiled static files from your Node.js builder
        displayName: Output Directory
        value: build
        required: false

    The parameter section should be pretty self-explanatory, but I want to call out the OUTPUT_DIR parameter. For our React example, we don't need to worry about it, since the default value is what React uses, but if you are using Angular or something else, you could change it.

    Now let's take a look at the image streams.

    - apiVersion: v1
      kind: ImageStream
      metadata:
        name: react-web-app-builder  // 1 
      spec: {}
    - apiVersion: v1
      kind: ImageStream
      metadata:
        name: static-web-app-running-on-nginx  // 2 
      spec: {}
    - apiVersion: v1
      kind: ImageStream
      metadata:
        name:  node-ubi-s2i-image // 3
      spec:
        tags:
        - name: latest
          from:
            kind: DockerImage
            name: registry.access.redhat.com/ubi8/nodejs-14:latest
    - apiVersion: v1
      kind: ImageStream
      metadata:
        name: nginx-image-runtime // 4
      spec:
        tags:
        - name: latest
          from:
            kind: DockerImage
            name: 'centos/nginx-112-centos7:latest'

    First, let's take a look at the third and fourth images. We can see that both are defined as Docker images, and we can see where they come from.

    The third is the Node S2I image, registry.access.redhat.com/ubi8/nodejs-14, which is using the latest tag.

    The fourth is an NGINX image (version 1.12) using the latest tag from the Docker hub.

    Now, let's take a look at those first two images. Both images are empty to start. These images will be created during the build phase, but for completeness, let me explain what will go into each one.

    The first image, react-web-app-builder, will be the result of the "assemble" phase of the node-ubi-s2i-image image once it is combined with our source code. That is why I've named it "-builder."

    The second image, static-web-app-running-on-nginx, will be the result of combining the nginx-image-runtime with the some of the files from the react-web-app-builder image. This image will also be the image that is "deployed" and will contain only the web server and the static HTML, JavaScript, and CSS for the application.

    This might sound a little confusing now, but once we look at the build configurations, things should be a little more clear.

    In this template, there are two build configurations. Let's take a look at them one at a time.

    apiVersion: v1
      kind: BuildConfig
      metadata:
        name: react-web-app-builder
      spec:
        output:
          to:
            kind: ImageStreamTag
            name: react-web-app-builder:latest // 1
        source:   // 2 
          git:
            uri: ${SOURCE_REPOSITORY_URL}
            ref: ${SOURCE_REPOSITORY_REF}
          contextDir: ${SOURCE_REPOSITORY_DIR}
          type: Git
        strategy:
          sourceStrategy:
            from:
              kind: ImageStreamTag
              name: node-ubi-s2i-image:latest // 3
          type: Source
        triggers: // 4
        - github:
            secret: ${GITHUB_WEBHOOK_SECRET}
          type: GitHub
        - type: ConfigChange
        - imageChange: {}
          type: ImageChange

    The first one, react-web-app-builder, is pretty standard. We see that line 1 tells us the result of this build will be put into the react-web-app-builder image, which we saw when we took a look at the image stream list above.

    Next, line 2 is just telling us where the code is coming from. In this case, it is a Git repository, and the location, ref, and context directory are defined by the parameters we saw earlier.

    Line 3 is just telling us to use the node-ubi-s2i-image image that we saw in the ImageStream section

    The last thing to call out, line 4, is just a few triggers that are set up, so when something changes, this build can be kicked off without manual interaction.

    As I said before, this is a pretty standard build configuration. Now let's take a look at the second build configuration. Most of it is very similar to the first, but there is one important difference:

    apiVersion: v1
      kind: BuildConfig
      metadata:
        name: static-web-app-running-on-nginx
      spec:
        output:
          to:
            kind: ImageStreamTag
            name: static-web-app-running-on-nginx:latest // 1
        source: // 2
          type: Image
          images:                              
            - from:
                kind: ImageStreamTag
                name: react-web-app-builder:latest // 3
              paths:
                - sourcePath:  /opt/app-root/src/${OUTPUT_DIR}/.   // 4
                  destinationDir: .  // 5
                 
        strategy: // 6
          sourceStrategy:
            from:
              kind: ImageStreamTag
              name: nginx-image-runtime:latest
            incremental: true
          type: Source
        triggers:
        - github:
            secret: ${GITHUB_WEBHOOK_SECRET}
          type: GitHub
        - type: ConfigChange
        - type: ImageChange
          imageChange: {}
        - type: ImageChange
          imageChange:
            from:
              kind: ImageStreamTag
              name: react-web-app-builder:latest // 7

    This second build configuration, static-web-app-running-on-nginx, starts off in a fairly standard way.

    Line 1 isn't anything new. It is telling us that the result of this build will be put into the static-web-app-running-on-nginx image.

    As with the first build configuration, we have a source section, line 2, but this time we say our source is coming from an image. The image that it is coming from is the one we just created, react-web-app-builder (specified in line 3). The files we want to use are located inside the image and that location is specified in line 4: /opt/app-root/src/${OUTPUT_DIR}/. If you remember, this is where our generated files from our application's build step ended up.

    The destination directory, specified in line 5, is just the current directory (this is all happening inside some magic OpenShift thing, not on your local computer).

    The strategy section, line 6, is also similar to the first build configuration. This time, we are going to use the nginx-image-runtime that we looked at in the ImageStream section.

    The final thing to point out is the trigger section, line 7, which will trigger this build anytime the react-web-app-builder image changes.

    The rest of the template is fairly standard deployment configuration, service, and route stuff, which we don't need to go into. Note that the image that will be deployed will be the react-web-app-runtime image.

    Deploying the application

    Now that we've taken a look at the template, let's see how we can easily deploy this application.

    We can use the OpenShift client tool, oc, to deploy our template:

    $ find . | grep openshiftio | grep application | xargs -n 1 oc apply -f
    
    $ oc new-app --template react-web-app -p SOURCE_REPOSITORY_URL=https://github.com/lholmquist/react-web-app

    The first command is just an overly engineered way of finding the ./openshiftio/application.yaml template. The second creates a new application based on that template. Once those commands are run, we can see that there are two builds:

    2 buildconfigs for a chained build

    Back on the Overview screen, we should see the running pod:

    deployed web application

    Clicking the link should navigate to our application, which is the default React application page:

    Screen that is displayed after navigating to the app

    Extra credit: Make it an Angular application

    For developers who are into using Angular, here is an example of that. The template is mostly the same, except for that OUTPUT_DIR variable.

    Extra, extra credit: Swap NGINX with Apache web server

    This article showed how to use the NGINX image as our web server, but it's fairly easy to swap that out if you wanted to use an Apache server. It can actually be done in one or maybe two (for completeness) steps.

    All you need to do is in the template file, swap out the NGINX image for the Apache image.

    Summary

    While the first article in this series showed how to quickly get a modern web application on OpenShift, this article went deeper into what the Node.js Builder image is doing and how to combine it, using a chained build, with a pure web server such as NGINX for a more production-ready build.

    In the next and final article, we will take a look at how to run our web application's development server on OpenShift, while keeping our local and remote files in sync.

    Additional resources

    • Deploying to OpenShift: a guide for impatient developers: Get the free ebook.
    • Learn more about OpenShift and Kubernetes.
    Last updated: January 12, 2024

    Recent Posts

    • More Essential AI tutorials for Node.js Developers

    • How to run a fraud detection AI model on RHEL CVMs

    • How we use software provenance at Red Hat

    • Alternatives to creating bootc images from scratch

    • How to update OpenStack Services on OpenShift

    Red Hat Developers logo LinkedIn YouTube Twitter Facebook

    Products

    • Red Hat Enterprise Linux
    • Red Hat OpenShift
    • Red Hat Ansible Automation Platform

    Build

    • Developer Sandbox
    • Developer Tools
    • Interactive Tutorials
    • API Catalog

    Quicklinks

    • Learning Resources
    • E-books
    • Cheat Sheets
    • Blog
    • Events
    • Newsletter

    Communicate

    • About us
    • Contact sales
    • Find a partner
    • Report a website issue
    • Site Status Dashboard
    • Report a security problem

    RED HAT DEVELOPER

    Build here. Go anywhere.

    We serve the builders. The problem solvers who create careers with code.

    Join us if you’re a developer, software engineer, web designer, front-end designer, UX designer, computer scientist, architect, tester, product manager, project manager or team lead.

    Sign me up

    Red Hat legal and privacy links

    • About Red Hat
    • Jobs
    • Events
    • Locations
    • Contact Red Hat
    • Red Hat Blog
    • Inclusion at Red Hat
    • Cool Stuff Store
    • Red Hat Summit

    Red Hat legal and privacy links

    • Privacy statement
    • Terms of use
    • All policies and guidelines
    • Digital accessibility

    Report a website issue