Adding New Relic to your dockerized Django app

This tutorial will walk you through installing the New Relic agent in a Dockerized setup. This example is for Django, but the approach should apply to any setup.

Setting up a quick example Dockerized Django app

We'll use a slightly simplified version of the example from the docker-compose docs:

Dockerfile

 FROM python:3.4
 ENV PYTHONUNBUFFERED 1
 RUN mkdir -p /code
 WORKDIR /code
 ADD requirements.txt /code/
 RUN pip install -r requirements.txt
 ADD . /code/

What's happening:

  • We're setting up our base image with python 3.4
  • We make a working directory inside the container at /code
  • We copy requirements.txt from our machine into the container, then we pip install our requirements.
  • Finally, we add our current directory (e.g.: our project) to /code inside the container

docker-compose:

 version: '3'
 services:
   web:
     build: .
     command: python manage.py runserver 0.0.0.0:8000
     volumes:
       - .:/code
     ports:
       - "8000:8000"

(note: I'm specifying the latest version of docker-compose here. If you're using an older version of docker, you can change the version string to 1 or 2 .. but you should consider upgrading. They've done some pretty rad stuff in the new compose.)

What's happening:

  • We build the image from our Dockerfile
  • We tell our container to run the python manage.py runserver command
  • We expose port 8000 inside the machine to 8000 on our host machine.

Set everything up

Finally, let's get our app running:

# create out django app:
docker-compose run web django-admin.py startproject composeexample .
# run the app:
docker-compose up

You should now be able to see your "it worked" page at: http://localhost:8000

Add the new relic agent

Based on the new relic setup instructions, we're going to need to do the following:

Install the new relic agent:

Edit requirements.txt and add newrelic

requirements.txt

django==1.10
newrelic

You'll need to build your image again to apply these changes:

docker-compose build web

Create your New Relic config file:

docker-compose run --rm web newrelic-admin generate-config <your-key-goes-here> newrelic.ini

Notes:

  • We run this inside our container. Because our WORKDIR in the container is mapped to our current directory, a newrelic.ini file will appear in your current directory.

You may want to have a look at that file and edit a few values. In particular app_name.

NB: remember that we're running the agent inside our container. So your paths will need to reflect this. For example, if you want to turn on the log file, you would set it's value to be something like this:

log_file = /code/newrelic-python-agent.log

Run our app with New Relic:

The last thing we need to do is to wrap our run command with new-relic-admin. Edit docker-compose and change the command to be:

Normally, you would run your command something like this:

NEW_RELIC_CONFIG_FILE=newrelic.ini newrelic-admin run-program YOUR_COMMAND_OPTIONS

But with docker-compose we can make this a little neater. We'll

  1. Add NEW_RELIC_CONFIG_FILE=newrelic.ini as an environment variable
  2. Set newrelic-admin run-program .. as our command

docker-compose.yml

 version: '3'
 services:
   web:
     build: .
     command: newrelic-admin run-program python manage.py runserver 0.0.0.0:8000
     volumes:
       - .:/code
     ports:
       - "8000:8000"
     environment:
       - NEW_RELIC_CONFIG_FILE=/code/newrelic.ini

Notes:

  • We've prepended our command with the newrelic-admin version.
  • We've set our NEW_RELIC_CONFIG_FILE as an environment variable (note: we used /code/.. as the path)

Now, when you run docker-compose up your app should start logging to New Relic. You can double-make-sure by testing your configuration:

docker-compose run --rm web newrelic-admin validate-config /code/newrelic.ini

Hopefully you will see output something along the lines of:

Registration successful. Reporting to:

  https://rpm.newrelic.com/accounts/../applications/...

So now you know enough to successfully setup New Relic for a Dockerized Django app. In the next section, we'll go over some slightly fancier implementations.

New Relic in production

Now, you probably don't really want to be running the new relic agent on your dev machine (and you prob don't want to be using runserver on your production deploy btw). So, let's extend this to a slightly more "production ready" imlementation.

Remove New Relic from docker-compose

Revert your docker-compose back to it's original version.

Create a docker-compose.production.yml file

We'll create a production file which will extend our default docker-compose file and apply the necessary changes to run with new relic.

docker-compose.production.yml

version: '3'
services:
  web:
    command: newrelic-admin run-program gunicorn composeexample.wsgi:application -b :8000 --reload
    environment:
      - NEW_RELIC_CONFIG_FILE=/code/newrelic.ini

Here we just override the command and environment settings for production.
Finally, we need to add gunicorn to our requirements.txt.

Add gunicorn, then build the app:

docker-compose build web

We can now run a productionized version of our app with:

docker-compose -f docker-compose.yml -f docker-compose.production.yml up

Notes:

  • We tell docker compose to use the default docker-compose.yml file, but to extend that with our production docker-compose file. (which runs our app with gunicorn and new relic

So now, if we run docker-compose up on dev, we won't be tracking to New Relic.

Bonus section: Celery as a background process

If you're using celery with your Django app, you probably have a worker service. What you want to do is exactly the same setup (use the same newrelic.ini file and the same app_name). This way, New Relic will record your celery actions as a background process. Your worker service might look something like this:

..
  worker:
    build: .
    command: newrelic-admin run-program celery -A composeexample worker
    volumes:
      - .:/code
    environment:
      - C_FORCE_ROOT=true
      - NEW_RELIC_CONFIG_FILE=/code/newrelic.ini
..

Final code: