DevOps
How to containerize Next.js app with buildpacks
This blog shows simple steps to containerize Next.js app using cloud native buildpacks.
Introduction
Historically, Docker has been a popular choice for containerizing applications. In the cloud native era, there is another popular player for performing app image build and getting lots of attention in devops community. It’s quite easy to use cloud native buildpacks to containerize the app. There is no specific file or config required in most of the cases. Cloud native buildpacks supports all the popular coding languages and work smoothly to identify the language based on the source-code to containerize app image.
The Cloud Native Buildpacks embrace modern container standards, such as the OCI image format. They take advantage of the latest capabilities of these standards, such as cross-repository blob mounting and image layer “rebasing” on Docker API v2 registries.
— quoted from buildpacks.io
In this blog we would go through the steps to containerize the Next.js based webapp using simple steps. For building Next.js app image with Docker build, please have a look into another blog - How to containerize Next.js app with docker build.
Initial setup
We can build a Next.js based webapp with npx create-next-app@latest. We can extend the web app with more functionalities and API’s.
Based of the usecase, developer can build a standalone output for the Next.js app. Related feature in Next.js for generating standalone output can be enabled in the next.config.js.
module.exports = { output: 'standalone',}
Above config will create a folder at .next/standalone which can then be deployed on its own without installing node_modules.
NOTE: Though standalone output feature is good to have for containerizing app with docker build, but for buildpacks, it’s an optional step.
Introduce Pack CLI
The simplest way to use buildpacks is by installing Pack CLI in the dev environment. There is no need to install docker or any other dependency except Pack CLI for image build.
NOTE: We might need docker to perform a test-run for the built image with docker run.
$ packCLI for building apps using Cloud Native BuildpacksUsage: pack [command]Available Commands: build Generate app image from source code builder Interact with builders buildpack Interact with buildpacks extension Interact with extensions config Interact with your local pack config file inspect Show information about a built app image stack Interact with stacks rebase Rebase app image with latest run image sbom Interact with SBoM completion Outputs completion script location report Display useful information for reporting an issue version Show current 'pack' version help Help about any command
Pack Build requires an image name, which will be generated from the source code. Build defaults to the current directory, but you can use --path to specify another source code directory. Build requires a builder, which can either be provided directly to build using --builder, or can be set using the set-default-builder command. To specify build time environment variables, we can pass via --env or can specify path for env-file with --env-file. All the other options can be checked with --help for pack build sub-command.
NOTE1: A builder is an image that contains all the components necessary to execute a build.
NOTE2 A buildpack is a set of executables that inspects your app source code and creates a plan to build and run your application.
Containerize Next.js app
Containerizing Next.js app to image is as simple as running the pack build command with certain options.
--name: The name and the tag of the app image.--builder: The builder to use for image build.--buildpack: The buildpack for nodejs.
We can run below command to start the image creation process.
pack build nextapp:1.0 \ --path /home/user1/nextapp \ --buildpack paketo-buildpacks/nodejs \ --builder paketobuildpacks/builder:full
Considerations for Next.js app
-
By default, buildpacks look into package.json for the build script and runs the command during build. So, to ensure that the build gets the env params from buildpack for production, after the
next buildwe can either specify the build env param or we can add 2nd build script as"build:prod": "set NODE_ENV=production & next build". -
In case we are using Next.js standalone output generation during build for getting the app deploy resources, we need to change the start script for app as
"start": "node .next/standalone/server.js". If we are not using standalone output, then we do not need any change as start script can be as"start": "next start".
$ cat package.json{ "name": "nextapp", "version": "0.1.0", "private": true, "scripts": { "dev": "next dev", "build": "next build", "build:prod": "set NODE_ENV=production & next build", "start": "node .next/standalone/server.js", "lint": "next lint" }, ...}
NOTE: Above package.json considers start script for Next.js app with standalone output.
Build process
With above considerations, we can run the pack build command and track the image generation. The build process downloads the builder image and corresponding buildpacks image in first attempt. The process identifies the supporting buildpacks needed for the app based on the source-code and configs defined. Also adds environment variables, which can be overridden with CLI.
$ pack build nextapp:1.0 \ --path /home/user1/nextapp \ --buildpack paketo-buildpacks/nodejs \ --builder paketobuildpacks/builder:fullfull: Pulling from paketobuildpacks/builderDigest: sha256:11068838eeb5cb3a34bb34f56de5e9a5c40c1eb31995046e3076ebde7b499c9fStatus: Image is up to date for paketobuildpacks/builder:fullfull-cnb: Pulling from paketobuildpacks/runDigest: sha256:5373ba74412664ac24f132e29b94808467d8522e5f79557d5aea72571c15e7feStatus: Image is up to date for paketobuildpacks/run:full-cnb===> ANALYZINGImage with name "nextapp:1.0" not found===> DETECTING5 of 11 buildpacks participatingpaketo-buildpacks/ca-certificates 3.6.2paketo-buildpacks/node-engine 1.6.0paketo-buildpacks/npm-install 1.1.4paketo-buildpacks/node-run-script 1.0.10paketo-buildpacks/npm-start 1.0.11===> RESTORINGRestoring metadata for "paketo-buildpacks/npm-install:npm-cache" from cacheRestoring data for "paketo-buildpacks/npm-install:npm-cache" from cacheRestoring data for SBOM from cache===> BUILDINGPaketo Buildpack for CA Certificates 3.6.2 https://github.com/paketo-buildpacks/ca-certificates Launch Helper: Contributing to layer Creating /layers/paketo-buildpacks_ca-certificates/helper/exec.d/ca-certificates-helperPaketo Buildpack for Node Engine 1.6.0 Resolving Node Engine version Candidate version sources (in priority order): -> "" <unknown> -> "" Selected Node Engine version (using ): 18.16.1 Executing build process Installing Node Engine 18.16.1 Completed in 3.172s Generating SBOM for /layers/paketo-buildpacks_node-engine/node Completed in 0s Configuring build environment NODE_ENV -> "production" NODE_HOME -> "/layers/paketo-buildpacks_node-engine/node" NODE_OPTIONS -> "--use-openssl-ca" NODE_VERBOSE -> "false" Configuring launch environment NODE_ENV -> "production" NODE_HOME -> "/layers/paketo-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.Paketo Buildpack for NPM Install 1.1.4 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/paketo-buildpacks_npm-install/npm-cache' added 417 packages, and audited 418 packages in 10s 140 packages are looking for funding run `npm fund` for details found 0 vulnerabilities Completed in 13.114s Configuring build environment NODE_ENV -> "development" PATH -> "$PATH:/layers/paketo-buildpacks_npm-install/build-modules/node_modules/.bin" Generating SBOM for /layers/paketo-buildpacks_npm-install/build-modules Completed in 2.025s Executing launch environment install process Running 'npm prune' up to date, audited 418 packages in 1s 140 packages are looking for funding run `npm fund` for details found 0 vulnerabilities Completed in 1.617s Configuring launch environment NODE_PROJECT_PATH -> "/workspace" NPM_CONFIG_LOGLEVEL -> "error" PATH -> "$PATH:/layers/paketo-buildpacks_npm-install/launch-modules/node_modules/.bin" Generating SBOM for /layers/paketo-buildpacks_npm-install/launch-modules Completed in 1.875sPaketo Buildpack for Node Run Script 1.0.10 Executing build process Running 'npm run build' > nextapp@0.1.0 build > next build - warn You are using a non-standard "NODE_ENV" value in your environment. This creates inconsistencies in the project and is strongly advised against. Read more: https://nextjs.org/docs/messages/non-standard-node-env Attention: Next.js now collects completely anonymous telemetry regarding usage. This information is used to shape Next.js' roadmap and prioritize features. You can learn more, including how to opt-out if you'd not like to participate in this anonymous program, by visiting the following URL: https://nextjs.org/telemetry - info Creating an optimized production build... - info Compiled successfully - info Linting and checking validity of types... - info Collecting page data... - info Generating static pages (0/5) - info Generating static pages (1/5) 2023-08-02 07:53:46 [Env: production | about] info: About page called! 2023-08-02 07:53:46 [Env: production | home] info: Home page called! 2023-08-02 07:53:46 [Env: production | about] info: About page called! - info Generating static pages (2/5) - info Generating static pages (3/5) 2023-08-02 07:53:46 [Env: production | home] info: Home page called! - info Generating static pages (5/5) - info Finalizing page optimization... Route (app) Size First Load JS ┌ ○ / 137 B 78.3 kB ├ ○ /about 409 B 88.7 kB └ λ /api/hello 0 B 0 B + First Load JS shared by all 78.1 kB ├ chunks/487-a6e261780a8e8119.js 25.7 kB ├ chunks/9c58a40f-267e5b531cccdcfa.js 50.5 kB ├ chunks/main-app-a1fd79d1fa4b0287.js 215 B └ chunks/webpack-7e6dae598cf9ea88.js 1.71 kB Route (pages) Size First Load JS ─ ○ /404 183 B 75.7 kB + First Load JS shared by all 75.5 kB ├ chunks/framework-7d3832b2720a7694.js 45 kB ├ chunks/main-51a549b6bdec5f8d.js 28.6 kB ├ chunks/pages/_app-e266fe44633eda69.js 194 B └ chunks/webpack-7e6dae598cf9ea88.js 1.71 kB λ (Server) server-side renders at runtime (uses getInitialProps or getServerSideProps) ○ (Static) automatically rendered as static HTML (uses no initial props) npm notice npm notice New minor version of npm available! 9.5.1 -> 9.8.1 npm notice Changelog: <https://github.com/npm/cli/releases/tag/v9.8.1> npm notice Run `npm install -g npm@9.8.1` to update! npm notice Completed in 20.079sPaketo Buildpack for NPM Start 1.0.11 Assigning launch processes: web (default): sh /workspace/start.sh===> EXPORTINGAdding layer 'paketo-buildpacks/ca-certificates:helper'Adding layer 'paketo-buildpacks/node-engine:node'Adding layer 'paketo-buildpacks/npm-install:launch-modules'Adding layer 'buildpacksio/lifecycle:launch.sbom'Adding 1/1 app layer(s)Adding layer 'buildpacksio/lifecycle:launcher'Adding layer 'buildpacksio/lifecycle:config'Adding layer 'buildpacksio/lifecycle: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 nextapp:1.0...*** Images (7ecb7d61bb14): nextapp:1.0Reusing cache layer 'paketo-buildpacks/node-engine:node'Adding cache layer 'paketo-buildpacks/npm-install:build-modules'Adding cache layer 'paketo-buildpacks/npm-install:npm-cache'Adding cache layer 'buildpacksio/lifecycle:cache.sbom'Successfully built image nextapp:1.0
Conclusion
We can verify that the image is created with docker images and can be tested with docker run
$ docker imagesREPOSITORY TAG IMAGE ID CREATED SIZEpaketobuildpacks/run full-cnb 7877b1bacecb 4 weeks ago 687MBnextapp 1.0 7ecb7d61bb14 43 years ago 1.31GBpaketobuildpacks/builder full 11bf29c66db6 43 years ago 2.11GB$ docker run --rm --name app -p 3000:3000 nextapp:1.0Listening on port 3000 url: http://2371e8d541f6:30002023-08-02 07:54:27 [Env: production | hello-route] info: Request headers: {}
Hope this blog will help my friends to get the steps to build app image with buildpacks in quick and easy steps. 🙂