Django to Phoenix - Part 2: Nginx
Tags: docker, nginx
Published: 2017-10-28 14:09:20

---
title: "Django to Phoenix - Part 2: Nginx"
published: true
description: 
tags: 
  - docker
  - nginx
---

## Background

I have a website built with [Django](https://www.djangoproject.com/) and I would like to start writing parts of it in [Phoenix](http://phoenixframework.org/).

- [Part 0: Background](https://dev.to/michaeljones/django-to-phoenix-part-0-6ij)
- [Part 1: Docker for Dev](https://dev.to/michaeljones/django-to-phoenix-part-1-56h)


## Starting Point

In the last section we set up a development Dockerfile & image so that we could run our Django [runserver](https://docs.djangoproject.com/en/1.11/ref/django-admin/#runserver) from inside a [Docker](https://www.docker.com/) container. We did this so that we could add [nginx](https://www.nginx.com/resources/wiki/) 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](https://en.wikipedia.org/wiki/APT_(Debian)) 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:

```Dockerfile
# 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
```

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):

```nginx
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/*;
}
```

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

```Dockerfile
copy ./dev/nginx.conf /etc/nginx/
```

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:

```Dockerfile
copy ./dev/site.conf /etc/nginx/sites-enabled/
```

Where our `site.conf` looks like this:

```nginx

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;
	}
}
```

The first important part is: `listen 8000` which [tells nginx](http://nginx.org/en/docs/http/ngx_http_core_module.html#listen) 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:

```Dockerfile
# Remove default nginx config which attempts to bind to port 80
run rm /etc/nginx/sites-enabled/default
```

## 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](https://dev.to/michaeljones/django-to-phoenix-part-1-56h).

Here we change it like so:
```diff
- python manage.py z runserver 0.0.0.0:8000
+ python manage.py z runserver 0.0.0.0:7000
```

## 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](http://supervisord.org/) 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:
```supervisord
[program:nginx]
command=/usr/sbin/nginx
stdout_events_enabled=true
stderr_events_enabled=true
```

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:

```console
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)
```

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.