DEV Community

Michael Jones
Michael Jones

Posted on

Django to Phoenix - Part 2: Nginx

Background

I have a website built with Django and I would like to start writing parts of it in Phoenix.

Starting Point

In the last section we set up a development Dockerfile & image so that we could run our Django runserver from inside a Docker container. We did this so that we could add nginx to the container in order to split requests between our Django development server and our future Phoenix development server.

Adding Nginx to our Dockerfile

In our last post we covered the Dockerfile copy command that allows us to copy files from our working directory into the Docker image. So we're going to use that to add nginx configuration to our image. There are 4 steps to this.

Step 1: Install nginx with apt-get

Near the top of our Dockerfile, we have a run command that runs apt-get to install the various Ubuntu packages that we need. We need to update the line to add the 'nginx' package. So it is going to look something like:

# Install dependencies for builds
run apt-get install -y git python python-pip make g++ wget curl libxml2-dev \
        libxslt1-dev libjpeg-dev libenchant-dev libffi-dev supervisor \
        libpq-dev python-dev realpath apt-transport-https nginx
Enter fullscreen mode Exit fullscreen mode

With nginx at the end of the install line. Note that changing such an early and important line like this requires quite a long rebuild for the Docker image so bear that in mind.

Step 2: Add nginx.conf

This is the nginx base configuration. Like Apache, nginx has a pattern of having a base configuration and then site specific config files. I'll include the base config here for completeness but I'm pretty sure I haven't made any changes to it from the standard nginx config (though I have stripped comments for conciseness):

user www-data;
worker_processes 4;
pid /run/nginx.pid;

daemon off;

events {
        worker_connections 768;
}

http {

        sendfile on;
        tcp_nopush on;
        tcp_nodelay on;
        keepalive_timeout 65;
        types_hash_max_size 2048;

        include /etc/nginx/mime.types;
        default_type application/octet-stream;

        access_log /var/log/nginx/access.log;
        error_log /var/log/nginx/error.log;

        gzip on;
        gzip_disable "msie6";

        include /etc/nginx/conf.d/*.conf;
        include /etc/nginx/sites-enabled/*;
}
Enter fullscreen mode Exit fullscreen mode

So we add this to our Dockefile file with a line like this:

copy ./dev/nginx.conf /etc/nginx/
Enter fullscreen mode Exit fullscreen mode

As we're building up an Ubuntu image inside Docker, we copy the config into the standard place that Ubuntu expects to see the nginx config. I've added that line quite close to the bottom of my Dockerfile but it doesn't really matter where it goes as long as it is after the apt-get command that installs nginx.

Note: As I write this I realise that this config is probably the same as one that comes with the nginx install in the container so this is probably redundant. Yay, for writing!

Step 3: Add our site config

At the bottom of the above config you can see the line: include /etc/nginx/sites-enabled/*;. This means that any additional config files in that system directory will be read by nginx when it starts up. So we add our site config to that directory:

copy ./dev/site.conf /etc/nginx/sites-enabled/
Enter fullscreen mode Exit fullscreen mode

Where our site.conf looks like this:


server {
    listen 8000;

    server_name localhost;

    location / {

        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

        proxy_set_header Host $http_host;

        # we don't want nginx trying to do something clever with
        # redirects, we set the Host: header above already.
        proxy_redirect off;

        proxy_pass http://localhost:7000;
    }
}
Enter fullscreen mode Exit fullscreen mode

The first important part is: listen 8000 which tells nginx to listen on that port for incoming requests. Note, that is the port that we're running our Django runserver on so we're going to have move that to another port as you can't have two programs listening on the same port.

The next important part is: location /. This creates a config block that only applies to requests that match the specified location. The matching is done such that requests to any paths starting with / are matched. As all paths start with /, this will match all of them.

Note, when I say "path", what I mean is part of the URL after the host. So the path for https://www.tangotimetable.com/events would be /events. As this starts with /, it will match our location block.

Inside the location block, the most important part is proxy_pass http://localhost:7000;. This tells nginx to forward all the requests through to the process listening on port 7000. This will be our Django runserver as soon as we change it to listen on that port.

The config is based on my production config where the other proxy_ options are required. I've honestly forgotten what they do.

Step 4: Remove the default site config

When you install nginx on an Ubuntu system, it comes with a default site config that listens on port 80 and displays an nginx landing page. This isn't a problem for our development set up as we're not using port 80 but it is a problem in production when we might want to use that. It is also something that caught me out so I wanted to highlight it. To get rid of it, we add the following lines to our Dockerfile:

# Remove default nginx config which attempts to bind to port 80
run rm /etc/nginx/sites-enabled/default
Enter fullscreen mode Exit fullscreen mode

Changing our Django runserver port

As noted above, we're switching from the Django runserver listening on port 8000 to listening on port 7000. This is so that we can have nginx listening on port 8000. So we need to update the run-django script that launches the server. This script was introduced in the last post.

Here we change it like so:

- python manage.py z runserver 0.0.0.0:8000
+ python manage.py z runserver 0.0.0.0:7000
Enter fullscreen mode Exit fullscreen mode

Launching nginx in the container

Finally, we need to make sure that our nginx process is launched when the container is started. If you remember, we're using a program called supervisord to manage the processes that run in the container so we have to add a config entry for nginx to our supervisord config.

This looks like:

[program:nginx]
command=/usr/sbin/nginx
stdout_events_enabled=true
stderr_events_enabled=true
Enter fullscreen mode Exit fullscreen mode

I put this above the [program:django] block but I doubt it matters.

Conclusion

That's it! If I run our docker run script from the last post I see this output:

2017-10-28 09:18:42,008 CRIT Supervisor running as root (no user in config file)
2017-10-28 09:18:42,012 INFO supervisord started with pid 7
2017-10-28 09:18:43,015 INFO spawned: 'nginx' with pid 10
2017-10-28 09:18:43,017 INFO spawned: 'django' with pid 11
2017-10-28 09:18:44,034 INFO success: nginx entered RUNNING state, process has stayed up for > than 1 seconds (startsecs)
2017-10-28 09:18:44,034 INFO success: django entered RUNNING state, process has stayed up for > than 1 seconds (startsecs)
Enter fullscreen mode Exit fullscreen mode

Which shows supervisord starting both nginx & Django and being happy that they stay up and running for more than 1 sec. If I visit http://localhost:8000 I see my Django apps home page as I would expect.

If you have any questions or advice, please comment below.

Top comments (0)