NGINX Conf 2018: Configuring NGINX Unit for Production Applications - Serving a Django Project

NGINX | November 28, 2018

NGINX Unit is a fully dynamic application server that can serve multiple languages as well as multiple versions of each language. It’s dynamic in the sense that you use the RESTful JSON API to make changes to its configuration in memory, without service disruption or configuration reloads.

In my presentation at NGINX Conf 2018 in October, I showed how to configure a new application in an existing production environment. Specifically, with WordPress running on PHP, I deployed a Python application that uses the Django framework. I also showed show how you can load configuration both from a file and as specified with an argument to an API call.

This blog includes all of the commands and configuration code I used in the demo, to make it easier for you to adapt to your own deployment.

Prerequisites

For the demo at NGINX Conf, I had the following software installed:

  • Ubuntu 16.04
  • NGINX Plus, but you can use NGINX Open Source except as noted
  • NGINX Unit with all language modules installed
  • Python 3
  • Django (not configured – that’s what the demo and this blog are about)
  • root privilege, or equivalent access via sudo (which we use where necessary)

I also had PHP and WordPress installed as the existing application.

Creating the Django Project

  1. Change into the directory where we’re creating our Django project:[@portabletext/react] Unknown block type "codeBlock", specify a component for it in the `components.types` prop
  2. Use the django-admin startproject command to initialize the new project. We’re calling it djapp. [@portabletext/react] Unknown block type "codeBlock", specify a component for it in the `components.types` prop
  3. Change into the project directory:[@portabletext/react] Unknown block type "codeBlock", specify a component for it in the `components.types` prop
  4. Use the manage.py script to migrate the database for the project, which is necessary for a newly created project. Django uses SQLite by default, and I accept the default in the demo, but you can use any database that meets your project’s needs. The manage.py script is installed by the django-admin command we ran in Step 2; it performs the same commands and accepts the same arguments as django-admin, but automatically derives and uses some project‑specific settings, which is helpful. For details, see the Django documentation. [@portabletext/react] Unknown block type "codeBlock", specify a component for it in the `components.types` prop
  5. Although it’s not strictly necessary for a sample project like this one, we recommend that you create a Django superuser identity:[@portabletext/react] Unknown block type "codeBlock", specify a component for it in the `components.types` prop
  6. Change into the subdirectory that contains the settings.py file, which was created by the django-admin startproject command in Step 2. [@portabletext/react] Unknown block type "codeBlock", specify a component for it in the `components.types` prop
  7. Using your preferred text editor, open settings.py. Here we’re using nano:[@portabletext/react] Unknown block type "codeBlock", specify a component for it in the `components.types` propFind the ALLOWED_HOSTS line and add in the domain name, hostname, or IP address for the application: [@portabletext/react] Unknown block type "codeBlock", specify a component for it in the `components.types` propAlso add the following line at the end of the file, to name the directory that stores all static content served by the application (see Step 9).[@portabletext/react] Unknown block type "codeBlock", specify a component for it in the `components.types` prop
  8. Change back to the main project directory (where manage.py resides). [@portabletext/react] Unknown block type "codeBlock", specify a component for it in the `components.types` prop
  9. Run the manage.py collectstatic command to collect all static files located in the Django project and put them into the STATIC_ROOT location defined in Step 7.[@portabletext/react] Unknown block type "codeBlock", specify a component for it in the `components.types` prop

Configuring NGINX

By default, Django itself serves the static content for a project, but NGINX Open Source and NGINX Plus offer superior performance. Here we configure NGINX Plus, but you can use NGINX Open Source except for one feature noted below.

  1. Change directory to /etc/nginx/conf.d, the conventional location for function‑specific (or in our case, application‑specific) HTTP configuration files: [@portabletext/react] Unknown block type "codeBlock", specify a component for it in the `components.types` prop
  2. Create a file called django.conf (again, we’re using nano):[@portabletext/react] Unknown block type "codeBlock", specify a component for it in the `components.types` propInsert the following configuration, which enables caching. The configuration also includes two features that are exclusive to NGINX Plus. Uncomment the relevant directives if you are using NGINX Plus and want to take advantage of the features: One thing to note is that in the demo at NGINX Conf I specified the IP address of my local machine as the second argument to the proxy_set_header directive. In a production environment, it makes more sense to use the $host variable as shown below. [@portabletext/react] Unknown block type "codeBlock", specify a component for it in the `components.types` prop
  3. Check the configuration for syntactic validity:[@portabletext/react] Unknown block type "codeBlock", specify a component for it in the `components.types` prop
  4. After fixing any errors, reload the configuration: [@portabletext/react] Unknown block type "codeBlock", specify a component for it in the `components.types` prop

Configuring NGINX Unit

To finish up, we need to configure NGINX Unit to serve the requests to the application.

  1. Run this curl command to display the current NGINX Unit configuration, which is for WordPress running on PHP. I don’t show the output here, but the WordPress configuration appears in Step 6 below, along with the Python application’s configuration, which we’re about to add.Note that I use sudo for the curl command, which you may not need to do for most curl commands. Here it’s necessary because to access the UNIX socket we need the read‑write permission that root has on it.[@portabletext/react] Unknown block type "codeBlock", specify a component for it in the `components.types` prop
  2. Change to the directory for NGINX Unit configuration files. Keep in mind that these files are optional and just a convenient way to load collections of configuration without typing all the data as an argument to a call to the NGINX Unit API. Because the content of the files is uploaded through the API (like all configuration data), NGINX Unit does not know about file locations and cannot automatically read files as it starts (unlike NGINX Open Source and NGINX Plus). Instead, NGINX Unit saves its runtime state in a separate directory. [@portabletext/react] Unknown block type "codeBlock", specify a component for it in the `components.types` prop
  3. Create a file called django.config (again, we’re using nano):[@portabletext/react] Unknown block type "codeBlock", specify a component for it in the `components.types` propAdd the following JSON, which represents our Python application. [@portabletext/react] Unknown block type "codeBlock", specify a component for it in the `components.types` prop
  4. Run this curl command to load the JSON contained in django.config as a new application object to be managed by NGINX Unit, called djapp:[@portabletext/react] Unknown block type "codeBlock", specify a component for it in the `components.types` propIn this command:
    • The HTTP PUT method creates a new NGINX Unit configuration object at the location named by the final argument (the URL). See the final bullet below.
    • The --data-binary argument tells curl to load the contents of django.config exactly as provided, preserving newlines and carriage returns, and not doing processing of any kind.
    • The --unix-socket argument defines where the NGINX Unit API is listening. (We use the sudo command because we’re using the default owner of the socket, root.)
    • The final argument locates and names the new application object to populate with the JSON‑formatted configuration data in django.config: config is the top‑level NGINX Unit configuration object, applications the parent for application objects, and djapp the name of the new application object.
  5. Define the listener object for the application. Rather than loading a file of configuration data as in Step 4, we define the data directly on the curl command line, specifying that the djapp application listens on port 8000. [@portabletext/react] Unknown block type "codeBlock", specify a component for it in the `components.types` prop
  6. Repeat the curl command from Step 1 to display the NGINX Unit configuration, which now includes our Python application, djapp, highlighted in orange:[@portabletext/react] Unknown block type "codeBlock", specify a component for it in the `components.types` prop

Summary

In this post we started with NGINX Unit running PHP applications for WordPress in production, and added a Python application. In the demo, I use the NGINX Plus dashboard to show that there is no disruption to the existing applications when a new application is added, but you can use any system‑monitoring tool, such as the ps command, for that purpose. The dynamic nature of NGINX Unit configuration saves resources for your running applications, and ensures zero downtime on new deployments and smooth transition between application versions.

To learn more, visit unit.nginx.org.


Share

Related Blog Posts

Automating Certificate Management in a Kubernetes Environment
NGINX | 10/05/2022

Automating Certificate Management in a Kubernetes Environment

Simplify cert management by providing unique, automatically renewed and updated certificates to your endpoints.

Secure Your API Gateway with NGINX App Protect WAF
NGINX | 05/26/2022

Secure Your API Gateway with NGINX App Protect WAF

As monoliths move to microservices, applications are developed faster than ever. Speed is necessary to stay competitive and APIs sit at the front of these rapid modernization efforts. But the popularity of APIs for application modernization has significant implications for app security.

How Do I Choose? API Gateway vs. Ingress Controller vs. Service Mesh
NGINX | 12/09/2021

How Do I Choose? API Gateway vs. Ingress Controller vs. Service Mesh

When you need an API gateway in Kubernetes, how do you choose among API gateway vs. Ingress controller vs. service mesh? We guide you through the decision, with sample scenarios for north-south and east-west API traffic, plus use cases where an API gateway is the right tool.

Deploying NGINX as an API Gateway, Part 2: Protecting Backend Services
NGINX | 01/20/2021

Deploying NGINX as an API Gateway, Part 2: Protecting Backend Services

In the second post in our API gateway series, Liam shows you how to batten down the hatches on your API services. You can use rate limiting, access restrictions, request size limits, and request body validation to frustrate illegitimate or overly burdensome requests.

New Joomla Exploit CVE-2015-8562
NGINX | 12/15/2015

New Joomla Exploit CVE-2015-8562

Read about the new zero day exploit in Joomla and see the NGINX configuration for how to apply a fix in NGINX or NGINX Plus.

Why Do I See “Welcome to nginx!” on My Favorite Website?
NGINX | 01/01/2014

Why Do I See “Welcome to nginx!” on My Favorite Website?

The ‘Welcome to NGINX!’ page is presented when NGINX web server software is installed on a computer but has not finished configuring

Deliver and Secure Every App
F5 application delivery and security solutions are built to ensure that every app and API deployed anywhere is fast, available, and secure. Learn how we can partner to deliver exceptional experiences every time.
Connect With Us
NGINX Conf 2018: Configuring NGINX Unit for Production Applications - Serving a Django Project | F5