Using Spring Apps Enterprise Tier with Tanzu Buildpacks for Node applications
This post will cover using Spring Apps Enterprise Tier with Tanzu Buildpacks to deploy node-based applications.
Overview
Azure Spring Apps (Enterprise Tier) utilizes Tanzu components to help do more for developers and give more value to deployed applications.
One of these components that can be used is Tanzu Buildpacks.
The two Buildpack options we’ll be focusing on in this post is the Tanzu Web Servers Buildpack and Tanzu Node.js Buildpack.
Prerequisites
A prerequisite to this post is to have an Azure Spring Apps Enterprise Tier instance and Java/Polyglot application already created. You can follow this documentation on how to create the Spring App Instance.
Deploying Single Page Applications (SPA’s)
React
There is a few ways you can deploy a React application, and SPA’s, to a polyglot application on Spring Apps.
-
First, create a React project - for instance, you can use Create React App.
-
Next, create a production build to generate the
/build
folder (this is the by-design name of the production build folder). Use eithernpm run build
(oryarn run build
, if using yarn) to create this build. -
The folder structure will now look something like this:
Deploying only the production build
NOTE: This method is using the “default” configured buildpack on Azure Spring Apps Enterprise
With this method, you’ll deploy the build folder you generated locally. The caveat to this is that to update the production build output - you’ll have to run npm run build
(or yarn run build
) prior to deployment each time.
NOTE: Also, ensure /build
is removed from your .gitignore
The below command will:
- Use NGINX as the Web Server to serve our static files
- Update NGINX to point its site root to the current root directory, which is
workspace
- Deploy the contents within the build folder
az spring app deploy --resource-group "your-rg" --service "your-asa-enterprise" --name "your-app" --source-path "./build" --build-env BP_WEB_SERVER=nginx BP_WEB_SERVER_ROOT=""
We can see which other build packs are referenced by the default configured one in the deployment logs during the build:
7 of 20 buildpacks participating
paketo-buildpacks/ca-certificates 3.5.1
tanzu-buildpacks/node-engine 1.1.0
tanzu-buildpacks/npm-install 1.0.0
tanzu-buildpacks/node-run-script 1.0.0
tanzu-buildpacks/nginx 0.11.1
tanzu-buildpacks/node-module-bom 0.3.4
tanzu-buildpacks/npm-start 1.0.0
After deployment, if we use the Connect blade to connect to the container in the pod - we can validate our deployed content:
We can also see our root
document path change reflected in NGINX - note, that in all examples here - NGINX document root paths are always relative to workspace
- application content is by default deployed to /workspace
:
Build the production folder on each deployment
To be able to deploy a React application that builds the project properly for production, we’ll use the Tanzu Webserver Buildpack.
Review this documentation for further information on builders and build packs on Azure Spring Apps Enterprise.
When using the default builder that is configured for Enterprise Tier, you may notice that if you deploy with the below command, which assumes this is ran from the project root, relative to package.json
- it will properly run npm run build
, but will actually run the application against it’s Development Server - as seen in the message below. This will also show in the builder output after deployment is finished.
We do not want to do this - as development servers should only be intended for local development. They are not production stable.
az spring app deploy --resource-group "your-rg" --service "your-asa-enterprise" --name "your-asa-app" --source-path "./" --build-env BP_WEB_SERVER=nginx BP_WEB_SERVER_ROOT="build" BP_NODE_RUN_SCRIPTS=build --verbose
(node:39) [DEP_WEBPACK_DEV_SERVER_ON_AFTER_SETUP_MIDDLEWARE] DeprecationWarning: 'onAfterSetupMiddleware' option is deprecated. Please use the 'setupMiddlewares' option.
(Use `node --trace-deprecation ...` to show where the warning was created)
(node:39) [DEP_WEBPACK_DEV_SERVER_ON_BEFORE_SETUP_MIDDLEWARE] DeprecationWarning: 'onBeforeSetupMiddleware' option is deprecated. Please use the 'setupMiddlewares' option.
Starting the development server...
Instead, we can do the below to add the Webserver build pack:
-
Go to the Azure Portal for the Spring App instance and navigate to the Build Server blade - then click Add
-
Next, give the builder a name, followed by the OS stack, and lastly selecting the
web-servers
build pack.
Use the below command to deploy the application. This command will:
- Use NGINX as the Web Server to serve our static files
- Update NGINX to point its site root to the
build
directory, which is our production build output - Deploy from the project root and build for production
- Use the webserver buildpack we configured for building
az spring app deploy --resource-group "your-rg-name" --service "your-asa-enterprise" --name "your-asa-app" --source-path "./" --build-env BP_WEB_SERVER=nginx BP_WEB_SERVER_ROOT="build" BP_NODE_RUN_SCRIPTS=build --builder=websrver --verbose
Angular
Create an Angular application - you can use the quickstart from the Angular CLI here.
Deploying only the production build
The only difference from the React example is that fact that Angulars production build is output to a folder named /dist
.
From the root of your project, run npm run build
to generate Angulars production build.
In the root of your project, run the below command to deploy the build:
NOTE: Also, ensure /dist
is removed from your .gitignore
az spring app deploy --resource-group "your-rg-name" --service "your-asa-enterprise" --name "your-app" --source-path "./dist" --build-env BP_WEB_SERVER=nginx BP_WEB_SERVER_ROOT=""
Note, that this method is using the default builder.
Build the production folder on each deployment
This method is using a custom webserver builder.
Following the same approach in the React section for building the production folder on each deployment, using the Webserver Tanzu buildpack, run the following command - we replace the value of BP_WEB_SERVER_ROOT
to use dist
:
az spring app deploy --resource-group "your-rg" --service "your-asa-enterprise" --name "your-asa-app" --source-path "./" --build-env BP_WEB_SERVER=nginx BP_WEB_SERVER_ROOT="dist" BP_NODE_RUN_SCRIPTS=build --builder=websrver --verbose
‘ng not found’
When deploying an Angular application using the Build the production folder on each deployment method above - you may see /workspace/start.sh: 3: ng: not found
if you’re using the default builder provided by Tanzu through Spring Apps.
To get around this, make sure to create a new builder using just the web server buildpack and specify this as a builder to your deployment command - eg., (--builder=mynewwebserverbuilder
)
Client-side routing
If client side routing is enabled, for example with React and using react-router-dom
- you may encounter HTTP 404’s from NGINX:
To resolve this, deploy with the builder argument of BP_WEB_SERVER_ENABLE_PUSH_STATE
to true
. This redirects all requests back to index.html where our client-side routing can take over, instead of the browser trying to serve up an actual .html
page of the route we’re requesting.
Deploying server-based applications
You can deploy an application that runs a node server-based framework, such as Express.js - an example of this is found here - Deploying a SPA served by a Node backend on the same Linux App Service.
Using that same example of a SPA served by Express.js - we can deploy this as a polyglot application to Azure Spring Apps Enterprise.
Using the package.json
in the above blog post example:
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start": "node server.js",
"build": "cd ./frontend && npm run build"
}
To deploy and run this, we’ll use the Tanzu Node.js Buildpack:
- Go to the Azure Portal for the Spring App instance and navigate to the Build Server blade - then click Add
- Give the buildpack a name, choose the OS, and select the
nodejs
pack
Deploy the application with the following command - replace the --builder
value with the name of your Node Buildpack name configured earlier:
az spring app deploy --resource-group "your-rg" --service "your-asa-enterprise" --name "your-asa-e-node-1" --source-path "./" --build-env BP_RUN_NODE_SCRIPTS="build" --builder=node-buildpack --verbose
npm install
is ran by default - but we also include BP_RUN_NODE_SCRIPTS="build"
to build our Front End to be served by Express.js (based off the example above).
While the deployment is building - the output should show in your current terminal session.
For example:
of 19 buildpacks participating
paketo-buildpacks/ca-certificates 3.5.1
tanzu-buildpacks/node-engine 1.1.0
tanzu-buildpacks/npm-install 1.0.0
tanzu-buildpacks/node-module-bom 0.3.4
tanzu-buildpacks/node-run-script 1.0.0
tanzu-buildpacks/node-start 1.0.0
tanzu-buildpacks/npm-start 1.0.0
Paketo Buildpack for CA Certificates 3.5.1
https://github.com/paketo-buildpacks/ca-certificates
Launch Helper: Contributing to layer
Creating /layers/paketo-buildpacks_ca-certificates/helper/exec.d/ca-certificates-helper
Tanzu Node Engine Buildpack 1.1.0
Resolving Node Engine version
Candidate version sources (in priority order):
-> ""
<unknown> -> ""
Selected Node Engine version (using ): 18.13.0
Executing build process
Installing Node Engine 18.13.0
Completed in 4.509s
Generating SBOM for /layers/tanzu-buildpacks_node-engine/node
Completed in 0s
Configuring build environment
NODE_ENV -> "production"
NODE_HOME -> "/layers/tanzu-buildpacks_node-engine/node"
NODE_OPTIONS -> "--use-openssl-ca"
NODE_VERBOSE -> "false"
Configuring launch environment
NODE_ENV -> "production"
NODE_HOME -> "/layers/tanzu-buildpacks_node-engine/node"
NODE_OPTIONS -> "--use-openssl-ca"
NODE_VERBOSE -> "false"
Writing exec.d/0-optimize-memory
Calculates available memory based on container limits at launch time.
Made available in the MEMORY_AVAILABLE environment variable.
Tanzu NPM Install Buildpack 1.0.0
Resolving installation process
Process inputs:
node_modules -> "Not found"
npm-cache -> "Not found"
package-lock.json -> "Found"
Selected NPM build process: 'npm ci'
Executing build environment install process
Running 'npm ci --unsafe-perm --cache /layers/tanzu-buildpacks_npm-install/npm-cache'
Completed in 2.285s
Configuring build environment
NODE_ENV -> "development"
PATH -> "$PATH:/layers/tanzu-buildpacks_npm-install/build-modules/node_modules/.bin"
Generating SBOM for /layers/tanzu-buildpacks_npm-install/build-modules
Completed in 1.688s
Executing launch environment install process
Running 'npm prune'
Completed in 1.506s
Configuring launch environment
NODE_PROJECT_PATH -> "/workspace"
NPM_CONFIG_LOGLEVEL -> "error"
PATH -> "$PATH:/layers/tanzu-buildpacks_npm-install/launch-modules/node_modules/.bin"
Generating SBOM for /layers/tanzu-buildpacks_npm-install/launch-modules
Completed in 1.743s
Tanzu Node Module Bill of Materials Generator Buildpack 0.3.4
Resolving CycloneDX Node.js Module version
Selected CycloneDX Node.js Module version: 3.10.4
Executing build process
Installing CycloneDX Node.js Module 3.10.4
Completed in 148ms
Configuring environment
Appending CycloneDX Node.js Module onto PATH
Running CycloneDX Node.js Module
Running 'cyclonedx-bom -o bom.json'
Completed in 432ms
[..truncated..]
Tanzu Node Run Script Buildpack 1.0.0
Executing build process
Running 'npm run build'
> azure-webapps-linux-node-spafe-nodebe@1.0.0 build
> cd ./frontend && npm i && npm run build
[..truncated..]
added 1491 packages, and audited 1492 packages in 46s
235 packages are looking for funding
run `npm fund` for details
6 high severity vulnerabilities
To address all issues (including breaking changes), run:
npm audit fix --force
Run `npm audit` for details.
> frontend@0.1.0 build
> react-scripts build
Creating an optimized production build...
Compiled successfully.
File sizes after gzip:
53.27 kB build/static/js/main.dec2504d.js
1.78 kB build/static/js/787.cda612ba.chunk.js
541 B build/static/css/main.073c9b0a.css
The project was built assuming it is hosted at /.
You can control this with the homepage field in your package.json.
The build folder is ready to be deployed.
You may serve it with a static server:
npm install -g serve
serve -s build
Find out more about deployment here:
https://cra.link/deployment
Completed in 1m3.798s
Tanzu Node Start Buildpack 1.0.0
Assigning launch processes:
web (default): node server.js
Tanzu NPM Start Buildpack 1.0.0
Assigning launch processes:
web (default): sh /workspace/start.sh
Adding layer 'paketo-buildpacks/ca-certificates:helper'
Adding layer 'tanzu-buildpacks/node-engine:node'
Adding layer 'tanzu-buildpacks/npm-install:launch-modules'
Adding layer 'launch.sbom'
Adding 1/1 app layer(s)
Adding layer 'launcher'
Adding layer 'config'
Adding layer 'process-types'
Adding label 'io.buildpacks.lifecycle.metadata'
Adding label 'io.buildpacks.build.metadata'
Adding label 'io.buildpacks.project.metadata'
Setting default process type 'web'
Saving acrc9a6a81672a74391a.azurecr.io/test-asa-e-node-1-default:result...
*** Images (sha256:41113f76ca5f1be56eec624e4c1eeb14c55d3fb12505ce31b6c4c5d65b7afa62):
acrc9a6a81672a74391a.azurecr.io/test-asa-e-node-1-default:result
acrc9a6a81672a74391a.azurecr.io/test-asa-e-node-1-default:result-1
Adding cache layer 'tanzu-buildpacks/node-engine:node'
Adding cache layer 'tanzu-buildpacks/npm-install:build-modules'
Adding cache layer 'tanzu-buildpacks/npm-install:npm-cache'
Adding cache layer 'tanzu-buildpacks/node-module-bom:cyclonedx-node-module'
Adding cache layer 'cache.sbom'
Build successful
At this point, the application should be successfully deployed. We can validate the content through the Console option: