Next.js + New Relic Backend Observability with Docker

Steve Fuller
6 min readJul 4, 2023

So this is a mixture of a story and sort of a high level tutorial on how we were able to get New Relic to work with Next.js in multiple environments using Docker. The aim of this post is to highlight issues, the research points into issues, and the solutions for our deployment process.

Note: This post assumes you have a New Relic account, working knowledge of Next.js, and Docker.

Photo by Taylor Vick on Unsplash

Problem we are trying to solve

We are rewriting an older application into React using Next.js and would like to have the backend observability for this new app in both our Staging and Production environments. We also don’t want to deviate too much from the Next.js ‘with-docker’ example, keep our flow as transparent as possible, and not introduce a custom Next server or anything too custom for that matter.

Where it all began

This all started with the “How to monitor a Next.js application” blog post. This post does a good job of getting us set up locally.

Install your the New Relic packages

npm install newrelic @newrelic/next

Temporarily configure your dev script

package.json

"scripts": {
"dev": "NODE_OPTIONS='-r @newrelic/next' next dev"
}

Here’s the first deviation from the blog post. We are only doing the above to verify connections locally and will remove later.

Add newrelic.js config file

Next we need a newrelic.js config file set at the root of our application. You can use the newrelic.js example provided by newrelic-experimental. Make sure to update the app_name and license_key with your information.

Running your app in dev mode

At this point running npm run dev you should see the New Relic logs populate your terminal. If everything bootstraps correctly you can view data from your APM page

Local dev recap

At this point we’ve

  • Installed the New Relic Node agent and Next.js instrumentation
  • Temporarily configured our dev script to bootstrap New Relic immediately
  • Added a newrelic.js configuration file with the proper app_name and license_key

Once you have confirmed things are working locally you can remove NODE_OPTIONS='-r @newrelic/next' from the dev script

Configuring for Docker

Our Next.js application uses the ‘with-docker’ example provided by Vercel as a base with docker compose.

Our docker-compose.yml is pretty generic with a custom image that controls how our containers are configured for deployments. This isn’t important.

Note: We don’t typically use Docker for local development as it can be a lot of overhead so the image is commented out when testing local builds.

docker-compose.yml

version: '3.8'

services:
nextjs:
image: ..custom image... # commented out when building locally
hostname: localhost
ports:
- 3000:3000
build:
context: .
dockerfile: Dockerfile

Problems and Solutions

newrelic.js is not copied

When running docker compose up --build our final build under /app does not have the newrelic.js file copied over. Therefore no data will be sent

Solution: adding a COPY newrelic.js ./

Dockerfile

...
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static

COPY newrelic.js ./

USER nextjs
...

Running docker compose up --build we now have our newrelic.js file being copied over into /app. However, this will render quite a few errors 😞

@newrelic/next does not start

The blog posts suggests starting @newrelic/next in the package.json with the start script, but the Next.js ‘with-docker’ example does not use this command we need a way to start the Next server and run the library immediately.

Solution: update CMD ["node", "-r", "@newrelic/next", "server.js"]

Solution found via forum post “[Node] Next.js with @newrelic/next and outputStandalone

Dockerfile

...
COPY newrelic.js ./
...
CMD ["node", "-r", "@newrelic/next", "server.js"]

Cannot find module “@newrelic/next”

Now that we have Docker coping newrelic.js, and trying to run the @newrelic/next library we are greeted with our first error that the module can’t be found. This is curious because @newrelic/next is already in the package.json 🤔

Solution: install the library directly during build RUN npm i @newrelic/next . Still leaving the module in package.json

Solution was found via GitHub Issue #69 in the newrelic-node-nextjs repo using the following GitHub Gist: Next.js with-docker Dockerfile update to get Node.js Nextjs plugin to work

Dockerfile

...
RUN npm install @newrelic/next
COPY newrelic.js ./
...
CMD ["node", "-r", "@newrelic/next", "server.js"]

Errors logging when starting @newrelic/client

The next error we got after getting the library running and installing during build (due to the module not being found) was logging. The error here was not having permissions to open the newrelic_agent.log.

Solution: use stdout logging

Solution was found via forum post “[Node] NodeJS + Docker — New Relic failed to open log file”.

newrelic.js

exports.config = {
logging: {
...
filepath: 'stdout'
}
}

Note: You can find out more about logging options in the node-newrelic default.js file

Note: This ended up being a good error in that our infrastructure does a lot with standard out logging already and using the newrelic_agent.log would not have been as useful for us. But, if you want something else there is some conversations on the topic.

Errors should all be resolved 🚀

At this point we should be able to successfully build a Docker container. This Docker container should have a newrelic.js file that copied over and using standard out logs, @newrelic/next is being installed during build, and Next server starting with updated CMD to bootstrap the @newrelic/next library.

Deploying to multiple environments

The solution that we landed on was having a newrelic.js file for each environment. This solution maintains our slightly modified Next.js 'with-docker' file, we don’t have to install any extra libraries (see below for dotenv example), and keeps a fairly transparent flow.

Setting specific newrelic.js environment files

/config
/staging
newrelic.js
/production
newrelic.js
.env

NEW_RELIC_FILE_PATH="./config/['staging' || 'production']

Have docker-compose argument that uses a .env file with a file path variable for which newrelic.js file to copy

docker-compose.yml

services:
nextjs:
...
build:
...
args:
- NEW_RELIC_FILE_PATH=${NEW_RELIC_FILE_PATH}

Update the Dockerfile with file path argument

Dockerfile

...
COPY ${NEW_RELIC_FILE_PATH}/newrelic.js ./

dotenv solution

The original blog post is detailed to immediately invoke @newrelic/next. This approach works but requires some customization.

  • You’ll need to use a default .env file to store your app_name and license_key
  • npm install dotenv
  • Update start script, NODE_OPTIONS='-r dotenv/config -r @newrelic/next' next
  • Use process.env.[NEW_RELIC_...] for app_name and license_key inside the newrelic.js file
  • Lastly the Dockerfile will need the updated CMD to use start

This approach felt a bit less straight forward so opted out of it.

next.config.js solution

An interesting solution was starting @newrelic/next through next.config.js. Pivoted away from this ultimately to keep New Relic loading as quick as possible. Unfortunately I’m unable to find the GitHub issue to reference at this time. Will update if it is as it was a pretty interesting thread.

next.config.js

if (process.env.NODE_ENV === "...") {
require('@newrelic/next');
}

const nextConfig = {...}

Possible custom Next.js server solution

This was another topic where @newrelic/next might be able to loaded into a Next.js custom server. This idea was not entertained at all but wanted to share there is some information discussed in a GitHub issue. Unfortunately I’m unable to find the GitHub issue to reference at this time. Will update if found.

Summary

So that about wraps up the journey for us getting backend observability with Next.js using Docker in multiple environments. To quickly recap

  • Started with “How to monitor a Next.js application” blog post
  • Got New Relic APM working locally with dev script
  • Configured a simple docker-compose.yml and Dockerfile using the Next.js ‘with-docker’ example
  • Modified our Dockerfile slightly to work with @newrelic/next
  • Made 2 newrelic.js files for specific environments
  • Configured docker-compose.yml to accept New Relic file path to load the correct newrelic.js file and updated Dockerfile accordingly

So I hope this gives some help to folks out here who might be struggling with getting this to work. Again there’s multiple solutions and we chose the best one for us which might not be the best for you so there are several solutions that were explored. So hopefully one of those works or if you find a new one please share 🍻

Thanks for reading and keep learning 🤘

--

--

Steve Fuller

Front End Engineer that loves JavaScript, React, Redux, GraphQL, Node, and Boston Terriers