Using New Relic with Docker for Monitoring .NET Core Applications

Keith Smith
Imagine Learning Engineering
6 min readJul 13, 2020

This is part 1 of a walkthrough series on using New Relic with containerized Docker applications. Part 1 describes how to modify an existing .NET Core Docker application to run New Relic APM. Before we get started, it’s important to note that the following:

  • The .NET agent supports applications running .NET Core version 2.0, 2.1, 2.2, 3.0, and 3.1.
  • We’re assuming the use of multi-stage Docker builds in this tutorial, but a simple Dockerfile without multi-stage can implement everything in this tutorial.
  • We’re using Alpine-based images in Docker for smaller images. This isn’t required but is a best practice for getting the best scalability and performance with .NET Core apps.

Additionally, to complete this tutorial, you will need the following:

  • A .NET Core application (Even a simple Hello World will work). Microsoft has a tutorial for getting started with a basic .NET Core app here. For a bit of a deeper dive into .NET Core, you can get started with a .NET Core MVC App here.
  • Access Key to a New Relic account. This can be a free account set up at https://newrelic.com/signup/ or your organization’s New Relic account.

Detailed New Relic requirements can be found here.

Why Docker?

Docker is an amazing tool. It opens up an application to be able to leverage powerful orchestration tools like Kubernetes, which manage the application lifecycle and enable self-healing of apps. Apps can be built and run locally on a developer’s machine, shipped with all dependencies required for the application. This means that if it runs locally, it will run in production.

Additionally, Docker allows for greater application density. Application density refers to the ability to run multiple applications on a cluster of machines, leveraging all of the resources available (CPU, Memory, etc). Traditionally apps were run on dedicated hardware, then in VMs. These apps were shipped with a full operating system, and were not very resource efficient. Docker provides a way to leverage the same underlying OS to have multiple separated apps on the same hardware. This reduces costs and increases efficiency over traditional VMs.

Adding New Relic to an Existing Dockerfile

To add New Relic APM to an existing .NET Core project, there are a few steps to follow:

  1. Install the agent.
  2. Enable the agent.
  3. Verify that the agent is reporting to New Relic.

For this portion of the walkthrough, we’re going to take the following Dockerfile for an app named ilk8sapp and add New Relic to it. For those adding a new Dockerfile to their app, the following Dockerfile can be modified and added to the src folder within your app. All that is needed is to replace <app-name> with your app name:

FROM mcr.microsoft.com/dotnet/core/sdk:3.1-alpine AS builder
WORKDIR /app
# Copy csproj and restore as distinct layers
COPY *.csproj ./
RUN dotnet restore
# Copy everything else and build
COPY . ./
RUN dotnet publish -c Release -o /app/out <app-name>.csproj
# Build runtime image
FROM mcr.microsoft.com/dotnet/core/aspnet:3.1-alpine
# copy the app from the build image
WORKDIR /app
COPY --from=builder /app/out .
ENTRYPOINT ["dotnet", "<app-name>.dll"]

New Relic is added to the runtime image as it will be delivered with this application in the final production image.

Install The Agent

Instructions for installing the agent using Docker can be found here. However… there is a problem. These instructions rely on `apt-get`, which for size reasons isn’t available in Alpine based images. So we have to download the latest agent from https://download.newrelic.com. The latest agent is available in the subfolder https://download.newrelic.com/dot_net_agent/latest_release/.

Because this agent version changes as new agents are released, let’s programmatically add the most current agent to the Dockerfile:

FROM mcr.microsoft.com/dotnet/core/sdk:3.1-alpine AS builder
WORKDIR /app
# Copy csproj and restore as distinct layers
COPY *.csproj ./
RUN dotnet restore
# Copy everything else and build
COPY . ./
RUN dotnet publish -c Release -o /app/out <app-name>.csproj
# Build runtime image
FROM mcr.microsoft.com/dotnet/core/aspnet:3.1-alpine
# install the agent
RUN mkdir /usr/local/newrelic-netcore20-agent \
&& cd /usr/local \
&& export NEW_RELIC_DOWNLOAD_URI=https://download.newrelic.com/$(wget -qO - "https://nr-downloads-main.s3.amazonaws.com/?delimiter=/&prefix=dot_net_agent/latest_release/newrelic-netcore20-agent" | grep -E -o 'dot_net_agent/latest_release/newrelic-netcore20-agent_[[:digit:]]{1,3}(\.[[:digit:]]{1,3}){3}_amd64\.tar\.gz') \
&& echo "Downloading: $NEW_RELIC_DOWNLOAD_URI into $(pwd)" \
&& wget -O - "$NEW_RELIC_DOWNLOAD_URI" | gzip -dc | tar xf -
# copy the app from the build image
WORKDIR /app
COPY --from=builder /app/out .
ENTRYPOINT ["dotnet", "<app-name>.dll"]

The bolded section above will grab the name of the latest .NET Core agent, then download and extract the agent into the install location. This will get the agent running.

Let’s take a quick look at what we just added.

RUN  mkdir /usr/local/newrelic-netcore20-agent \
&& cd /usr/local \

This section will create the directory for the agent installation and navigate to the installation folder.

&& export NEW_RELIC_DOWNLOAD_URI=https://download.newrelic.com/$(wget -qO - "https://nr-downloads-main.s3.amazonaws.com/?delimiter=/&prefix=dot_net_agent/latest_release/newrelic-netcore20-agent" | grep -E -o 'dot_net_agent/latest_release/newrelic-netcore20-agent_[[:digit:]]{1,3}(\.[[:digit:]]{1,3}){3}_amd64\.tar\.gz') \

This long export statement creates a variable called NEW_RELIC_DOWNLOAD_URI. The command programmatically reached out to the s3 repository for the latest release of the agent and will grep for the name of the latest agent.

&& echo "Downloading: $NEW_RELIC_DOWNLOAD_URI into $(pwd)" \
&& wget -O - "$NEW_RELIC_DOWNLOAD_URI" | gzip -dc | tar xf -

Lastly, we use the variable NEW_RELIC_DOWNLOAD_URI to download the agent and decompress it into the installation folder.

The agent is now running, but several variables are required to configure the agent to begin sending metrics to your New Relic account.

Enable the Agent

The next step is to add variables to the Dockerfile to enable the agent. The following variables are required:

CORECLR_ENABLE_PROFILING=1
CORECLR_PROFILER={36032161-FFC0-4B61-B559-F6C5D41BAE5A}
CORECLR_NEWRELIC_HOME=/usr/local/newrelic-netcore20-agent
CORECLR_PROFILER_PATH=/usr/local/newrelic-netcore20-agent/libNewRelicProfiler.so
NEW_RELIC_LICENSE_KEY=YOUR_LICENSE_KEY
NEW_RELIC_APP_NAME=YOUR_APP_NAME

The two bolded values are the only thing that differs between apps. The other variables will remain unchanged. To obtain the license key, login to New Relic and visit the account settings in the top right corner.

Location of NR license key in New Relic Account Settings.

Because this is going to be stored in source, the New Relic license key should not be put directly into the Dockerfile in production scenarios. This should be added in a secure manner to the container through the CI/CD pipeline as the app is deployed. An example of how to do this correctly would be to use a sealed secret in Kubernetes and add this environment variable through a configmap in the deployment yaml. This same example could be followed to change the name of the application in a CI/CD pipeline if desired.

To make this tutorial more simple, we’re going to avoid a CI/CD pipeline and put the license key directly into the Dockerfile. In practice, don’t do this.

Adding those variables to our Dockerfile looks something like this:

FROM mcr.microsoft.com/dotnet/core/sdk:3.1-alpine AS builder
WORKDIR /app
# Copy csproj and restore as distinct layers
COPY *.csproj ./
RUN dotnet restore
# Copy everything else and build
COPY . ./
RUN dotnet publish -c Release -o /app/out <app-name>.csproj
# Build runtime image
FROM mcr.microsoft.com/dotnet/core/aspnet:3.1-alpine
# install the agent
RUN mkdir /usr/local/newrelic-netcore20-agent \
&& cd /usr/local \
&& export NEW_RELIC_DOWNLOAD_URI=https://download.newrelic.com/$(wget -qO - "https://nr-downloads-main.s3.amazonaws.com/?delimiter=/&prefix=dot_net_agent/latest_release/newrelic-netcore20-agent" | grep -E -o 'dot_net_agent/latest_release/newrelic-netcore20-agent_[[:digit:]]{1,3}(\.[[:digit:]]{1,3}){3}_amd64\.tar\.gz') \
&& echo "Downloading: $NEW_RELIC_DOWNLOAD_URI into $(pwd)" \
&& wget -O - "$NEW_RELIC_DOWNLOAD_URI" | gzip -dc | tar xf -
# Enable the agent
ENV CORECLR_ENABLE_PROFILING=1 \
CORECLR_PROFILER={36032161-FFC0-4B61-B559-F6C5D41BAE5A} \
CORECLR_NEWRELIC_HOME=/usr/local/newrelic-netcore20-agent \
CORECLR_PROFILER_PATH=/usr/local/newrelic-netcore20-agent/libNewRelicProfiler.so \
NEW_RELIC_LICENSE_KEY=<YOUR_LICENSE_KEY> \
NEW_RELIC_APP_NAME="<APP-NAME>"
# copy the app from the build image
WORKDIR /app
COPY --from=builder /app/out .
ENTRYPOINT ["dotnet", "<app-name>.dll"]

With these variables added, the next step is to build and run this app locally to ensure that it’s working correctly and to verify that it’s sending metrics to New Relic.

Verify the Agent

It’s time to build this app. To build the app, run:

docker build -t <app_repo>:<tag> .

Assuming that the app builds successfully, run the app to begin reporting to New Relic:

docker run -it -p 8080:80 <app_repo>:<tag>

The above command will run the app interactively so you can see the output on the command line. Navigate to http://localhost:8080/<endpoint> to hit your app to send trace details to New Relic.

After the app is running, you should be able to login and see it in New Relic APM immediately. Traces take several seconds to be available but will show up in the various APM graphs shortly.

If you see these traces, Success! The app has been successfully tooled with New Relic APM. Well done! You’ve finished this walkthrough.

In Part 2, we’ll take a dive into using APM for troubleshooting, identifying performance bottlenecks, and configuring monitoring and alerting for an application, integrating with tools like OpsGenie and Slack.

Part 2 can be found here.

--

--