This guide demonstrates separating the Node.js runtime and Node modules into dedicated layers to improve build performance and caching.
In this guide, we demonstrate how to separate the Node.js runtime and Node modules installation into dedicated layers. Separating these components not only clarifies the build process but also leverages caching and boosts launch performance.Below is a comprehensive step-by-step walkthrough with clearly structured code blocks.
In this section, we create a dedicated layer for the Node.js runtime. Here, we download and extract Node.js into a specified directory. The runtime layer’s metadata is configured in a TOML file to ensure it is available at launch.
Copy
Ask AI
#!/usr/bin/env bashset -euo pipefailecho "Building image using my-js-buildpack buildpack"# Create the Node.js runtime layer directorynode_js_layer="${CNB_LAYERS_DIR}/node-js"mkdir -p "${node_js_layer}"default_node_js_version="18.18.0"# Retrieve the user's desired Node.js version from the plan or use the defaultnode_js_version=$(jq -r '.entries[] | select(.name == "node-js") | .metadata.version' < "$CNB_BP_PLAN_PATH" || echo "${default_node_js_version}")echo "nodejs version: ${node_js_version}"echo "--> Downloading and extracting NodeJS"node_js_url="https://nodejs.org/dist/v${node_js_version}/node-v${node_js_version}-linux-x64.tar.xz"wget -q -O - "${node_js_url}" | tar -xJf - --strip-components=1 -C "${node_js_layer}"# Create the TOML file for Node.js layer metadatacat > "${CNB_LAYERS_DIR}/node-js.toml" << EOL[types]build = falselaunch = truecache = falseEOL
After executing these steps, the Node.js runtime is installed into the custom layer defined by the node_js_layer variable. To ensure that this runtime is available for subsequent build steps and the final image, update the PATH to include the Node.js binary directory:
Copy
Ask AI
export PATH="${node_js_layer}/bin:$PATH"
You can verify the installation with the following commands:
Installing Application Dependencies in a Separate Node Modules Layer
Next, we focus on installing your application’s dependencies in a distinct layer. This separation allows you to cache Node modules separately, improving build times and resource utilization. Since dependency installation normally occurs in the current directory, we copy the package files into a new directory, install the dependencies there with npm ci, and then create a symbolic link back to the workspace.First, capture the current working directory to reference later:
Copy
Ask AI
workdir=$(pwd)
Now, create the layer for your Node modules, install dependencies, and configure the launch process:
Copy
Ask AI
# Create layer for the node modulesnode_modules_layer="${CNB_LAYERS_DIR}/node-dependencies"mkdir -p "${node_modules_layer}"echo "---> Installing Application Dependencies"# Copy package files to the node_modules layer and install dependencies therecp package*.json "${node_modules_layer}"cd "${node_modules_layer}"npm cicd "$workdir"# Create launch configuration for the final runtime imagecat > "${CNB_LAYERS_DIR}/launch.toml" << EOL[[processes]]type = "web"command = ["node", "index.js"]default = trueEOL# Symlink the installed node_modules back into the workspace to satisfy the application structureln -s "${node_modules_layer}/node_modules" "/workspace/node_modules"
This setup ensures that your dependencies reside in their own layer while keeping your application code in the workspace. The symbolic link enables Node.js to locate the node_modules folder during runtime.
After configuring both layers, you are ready to build the image. During the export stage, you should see that both the Node.js runtime layer and the node modules layer have been added:
Once the image is successfully built, run a container and verify that the application functions as expected. For example, you can use a command like the one below to check for a “Hello World” response:
Copy
Ask AI
curl localhost:8000
By following these steps, you have successfully separated the Node.js runtime and Node modules into individual layers. This modular approach optimizes caching and ensures that your final runtime image includes only the necessary components, enhancing both build performance and deployability.