Porting web applications with NodeJS front-ends to build with .NET 8.0 UBI images

Solution Verified - Updated

Environment

  • Red Hat OpenShift Container Platform (OCP)
    • 3.x
    • 4.x
  • .NET 6.0 and later

Issue

  • The .NET 8.0 UBI images no longer contain NodeJS. How can I port applications with a NodeJS front-end from previous versions of the .NET UBI images to the .NET 8.0 UBI images?

Resolution

The options to build a .NET 8.0 web application with a NodeJS front-end using the UBI .NET 8.0 images are:

  • A multi-stage build that builds the NodeJS front-end in a separate image
  • Performing the build in an image derived from the .NET 8.0 base image that includes NodeJS

Both are valid options. This solution describes the second option because it is similar to how the build worked with previous .NET images (which included NodeJS), and it minimizes the required changes to the application.

The steps are based on an OpenShift source-to-image (s2i) build of a .NET 7.0 application. Similar steps can be used for other build flows and for .NET 6.0.

The initial BuildConfig of a .NET 7.0 application looks like this:

...
  strategy:
    type: Source
    sourceStrategy:
      from:
        kind: ImageStreamTag
        namespace: openshift
        name: 'dotnet:7.0'
...

The dotnet:7.0 image in this snippet is the This content is not included..NET 7.0 UBI image, which includes NodeJS 18. The image is referenced through an ImageStream that was added to the openshift namespace.

We will update the application BuildConfig to use an image that is based on the .NET 8.0 base image to which we add NodeJS 18. In OpenShift this derived image can be created by using a BuildConfig of type Docker. In other build flows, the Dockerfile from this BuildConfig may be used directly.

Step-by-Step

  1. Import the latest .NET and NodeJS image stream definitions into the project namespace:

    $ oc apply -f https://raw.githubusercontent.com/redhat-developer/s2i-dotnetcore/main/dotnet_imagestreams.json
    $ oc apply -f https://raw.githubusercontent.com/sclorg/s2i-nodejs-container/master/imagestreams/nodejs-rhel.json
    
  2. Create a file named dotnet-8-node-18.yaml with the following content. This file defines the derived s2i image.

        kind: ImageStream
        apiVersion: image.openshift.io/v1
        metadata:
          name: dotnet-8-node-18
        ---
        kind: BuildConfig
        apiVersion: build.openshift.io/v1
        metadata:
          name: dotnet-8-node-18
        spec:
          output:
            to:
              kind: ImageStreamTag
              name: 'dotnet-8-node-18:latest'
          strategy:
            type: Docker
            dockerStrategy:
              from:
                kind: ImageStreamTag
                name: dotnet:8.0
          source:
            type: Dockerfile
            dockerfile: |
              FROM dotnet
    
              USER 0
              ENV NODEJS_VERSION=18
              RUN microdnf -y module enable nodejs:$NODEJS_VERSION && \
                  microdnf install -y --setopt=tsflags=nodocs --setopt=install_weak_deps=0 npm && \
                  microdnf clean all -y && \
                  rm -rf /var/cache/yum/*
    
              USER 1001
          triggers:
            - type: ConfigChange
            - type: ImageChange
            - type: ImageChange
              imageChange:
                from:
                  kind: ImageStreamTag
                  name: nodejs:18-ubi8
          runPolicy: Serial
    

    In the dotnet-8-node BuildConfig there are triggers to automatically rebuild the image when either the .NET base image or the NodeJS 18 base image changes. These triggers enable you to automatically rebuild this image when either one of the base images change due to security patches.

  3. Import the dotnet-8-node-18.yaml file:

    $ oc apply -f dotnet-8-node18.yaml
    
  4. Update the application source-code to target .NET 8.0.

  5. Change the BuildConfig to use the derived dotnet-8-node18 image:

        sourceStrategy:
           from:
             kind: ImageStreamTag
    -        namespace: openshift
    -        name: 'dotnet:7.0'
    +        name: 'dotnet-8-node-18:latest'
    

    The build is now performed in an image that includes both .NET 8.0 and NodeJS 18.

  6. Optionally, some additional configuration might be needed for NodeJS to work well in the s2i build, such as configuration of an NPM mirror or an HTTP proxy. You can achieve this by adding an s2i assemble script in the source repository at .s2i/bin/assemble.
    The following example shows a script that performs initialization based on environment variables known by the .NET 7.0 s2i image:

        #!/bin/bash
    
        set -e
    
        PATH=$HOME/node_modules/.bin:$PATH
        if [ -n "$NPM_MIRROR" ]; then
          echo "---> Setting npm mirror"
          npm config set registry "$NPM_MIRROR"
        fi
        if [ -n "$HTTP_PROXY" ]; then
          echo "---> Setting npm http proxy"
          npm config set proxy "$HTTP_PROXY"
        fi
        if [ -n "$HTTPS_PROXY" ]; then
          echo "---> Setting npm https proxy"
          npm config set https-proxy "$HTTPS_PROXY"
        fi
        if [ -n "${DOTNET_NPM_TOOLS}" ]; then
          echo "---> Installing npm tools..."
    
          pushd "$HOME"
          npm install ${DOTNET_NPM_TOOLS}
          popd
        fi
    
        # call base .NET assemble script
        $STI_SCRIPTS_PATH/assemble
    

Root Cause

.NET 6 and .NET 7 images do have NodeJS embedded, but .NET 8 does not.

Category

This solution is part of Red Hat’s fast-track publication program, providing a huge library of solutions that Red Hat engineers have created while supporting our customers. To give you the knowledge you need the instant it becomes available, these articles may be presented in a raw and unedited form.