Skip to main content
Redhat Developers  Logo
  • AI

    Get started with AI

    • Red Hat AI
      Accelerate the development and deployment of enterprise AI solutions.
    • AI learning hub
      Explore learning materials and tools, organized by task.
    • AI interactive demos
      Click through scenarios with Red Hat AI, including training LLMs and more.
    • AI/ML learning paths
      Expand your OpenShift AI knowledge using these learning resources.
    • AI quickstarts
      Focused AI use cases designed for fast deployment on Red Hat AI platforms.
    • No-cost AI training
      Foundational Red Hat AI training.

    Featured resources

    • OpenShift AI learning
    • Open source AI for developers
    • AI product application development
    • Open source-powered AI/ML for hybrid cloud
    • AI and Node.js cheat sheet

    Red Hat AI Factory with NVIDIA

    • Red Hat AI Factory with NVIDIA is a co-engineered, enterprise-grade AI solution for building, deploying, and managing AI at scale across hybrid cloud environments.
    • Explore the solution
  • Learn

    Self-guided

    • Documentation
      Find answers, get step-by-step guidance, and learn how to use Red Hat products.
    • Learning paths
      Explore curated walkthroughs for common development tasks.
    • Guided learning
      Receive custom learning paths powered by our AI assistant.
    • See all learning

    Hands-on

    • Developer Sandbox
      Spin up Red Hat's products and technologies without setup or configuration.
    • Interactive labs
      Learn by doing in these hands-on, browser-based experiences.
    • Interactive demos
      Click through product features in these guided tours.

    Browse by topic

    • AI/ML
    • Automation
    • Java
    • Kubernetes
    • Linux
    • See all topics

    Training & certifications

    • Courses and exams
    • Certifications
    • Skills assessments
    • Red Hat Academy
    • Learning subscription
    • Explore training
  • Build

    Get started

    • Red Hat build of Podman Desktop
      A downloadable, local development hub to experiment with our products and builds.
    • Developer Sandbox
      Spin up Red Hat's products and technologies without setup or configuration.

    Download products

    • Access product downloads to start building and testing right away.
    • Red Hat Enterprise Linux
    • Red Hat AI
    • Red Hat OpenShift
    • Red Hat Ansible Automation Platform
    • See all products

    Featured

    • Red Hat build of OpenJDK
    • Red Hat JBoss Enterprise Application Platform
    • Red Hat OpenShift Dev Spaces
    • Red Hat Developer Toolset

    References

    • E-books
    • Documentation
    • Cheat sheets
    • Architecture center
  • Community

    Get involved

    • Events
    • Live AI events
    • Red Hat Summit
    • Red Hat Accelerators
    • Community discussions

    Follow along

    • Articles & blogs
    • Developer newsletter
    • Videos
    • Github

    Get help

    • Customer service
    • Customer support
    • Regional contacts
    • Find a partner

    Join the Red Hat Developer program

    • Download Red Hat products and project builds, access support documentation, learning content, and more.
    • Explore the benefits

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

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

    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

    • Tekton joins the CNCF as an incubating project

    • Federated identity across the hybrid cloud using zero trust workload identity manager

    • Confidential virtual machine storage attack scenarios

    • Introducing virtualization platform autopilot

    • Integrate zero trust workload identity manager with Red Hat OpenShift GitOps

    Red Hat Developers logo LinkedIn YouTube Twitter Facebook

    Platforms

    • Red Hat AI
    • Red Hat Enterprise Linux
    • Red Hat OpenShift
    • Red Hat Ansible Automation Platform
    • See all products

    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
    © 2026 Red Hat

    Red Hat legal and privacy links

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

    Chat Support

    Please log in with your Red Hat account to access chat support.