Setting up Django, Gunicorn and Nginx in Vagrant virtual machine
Tags: vagrant, gunicorn, nginx, django
Published: 2020-02-16 14:14:20



This post explains setting up a Django app deployed by Gunicorn behind Nginx server in a Ubuntu 16.04 virtual machine set up via Vagrant.


## These are broader steps that you would need to take:
1. Setting up an Ubuntu 16.04 VM using Vagrant.
2. Setting up python and virtual environment.
3. Setting up Gunicorn systemd file.
4. Setting up Nginx to proxy pass Gunicorn.

### 1. Creating an Ubuntu 16.04 VM using Vagrant

Installing Vagrant is pretty simple step.

But first you would need to install Virtual Box or any other VM provider. Virtual Box is popular choice and even easier option amongst the providers to install. 
Just go to the Virtual Box's [downloads page](https://www.virtualbox.org/wiki/Downloads) and select respective host depending on your system's operating system.

If you select OSX host, a .dmg package is downloaded, open it and follow steps. While installing in OSX if you get an error something like **Your Instalation Failed**, follow [this blog](http://osxdaily.com/2018/12/31/install-run-virtualbox-macos-install-kernel-fails/) to troubleshoot.

Once Virtual Box is installed you can move on to installing Vagrant.

Go to Vagrant's [download page](https://www.vagrantup.com/downloads.html) and download installation image as per your OS. 

Check if vagrant is installed. Open terminal and type `vargant`. The output should be something like this:

```
$ vagrant
Usage: vagrant [options] <command> [<args>]

    -v, --version                    Print the version and exit.
    -h, --help                       Print this help.

#...
```

Once installation is complete, open terminal shell and go to directory where you want to setup your virtual machine. For example, create directory called _virtualmachines/_,here you would have all the virtual machines you want to setup. So, for Ubuntu 16.04, create a directory _ubuntu16_04/_. Go inside the directory 

```
mkdir ./virtualmachines && cd virtualmachines
mkdir ubuntu16_04 && cd ubuntu16_04
```

Now is the time create a VM box. You can create a VM from scratch using command:

```
$ vagrant init hashicorp/xenial64
``` 

or setup a base image of OS called a box by executing command:

```
$ vagrant box add hashicorp/xenial64
```

If you want to install any other flavour of Ubuntu replace _xenial_ with the flavour's first name. _64_ in _xenial64_ means the guest OS would be a 64-bit OS. You can find the list of available OS [here](https://app.vagrantup.com/boxes/search).

If you execute `ls` command, you would find a file called _VagrantFile_ present in the directory. This is the source file that is used to configure the VM. Every time that you want to start the VM, the configurations would be set as per this file.
 
Make sure to un-comment line declaring `forwarded_port` for guest and host OS.
The `guest` is your virtual machine and the `host` is your local machine.
Also, if it is commented, un-comment this block:

```
config.vm.provider "virtualbox" do |vb|
    vb.gui = false
    vb.memory = 2048
    vb.cpus = 2
```
and specify `vb.gui`, `vb.memory` and `vb.cpus`.
You can find sample _VagrantFile_ in this [gist](https://gist.github.com/syashakash/cc28f9774498cecb27d79b6f95cfcd2d).

Now to start the VM run :

```
$ vagrant up
```

This would boot your environment, and in less than a minute you would have Ubuntu running in a virtual machine. There would be no output visible, as Vagrant runs the VM without UI. To access Ubuntu you would require ssh. Run:

```
$ vagrant ssh
```

This would start a full-fledged SSH session.

### 2. Setting up python and virtual environment for Django app

Now that you are inside Ubuntu, it is time to install all necessary packages/modules.

Install python and pip using command
```
$ sudo apt install python python-pip
```

This would install python2.7. For python3 use:
```
$ sudo apt install pyhton3 python-pip3
```

Now install virtualenv:

```
$ pip install virtualenv
```

Create a directory called _webapps/_, this is going to contain all the Django projects created henceforth. You can directly clone your git repository here or start a project (remember to create a directory inside which you should start your project).
Such that your _webapps/_ directory tree looks like this :

```
 * webapps/
 | 
 ---- * your_project_parent_directory
      |
      ---- * your_project
           |
           ---- * your_app
           ---- * your_project
                |
                ---- * your_project.wsgi
```

Your project should have virtualenv set up and activated. Make sure you have a <your_project_name>.wsgi file in it. Change directory and get inside your project.

### 3. Setting up Gunicorn systemd file

Now its time to install Gunicorn:

```
$ pip install gunicorn
```

Once gunicorn is installed, run command:

```
$ gunicorn <your_project_name>.wsgi:application --bind 0.0.0.0:8000
```

If the server starts, your gunicorn setup is working. Though there is a robust way of gunicorn interacting with django project.

Create and open a systemd service file for Gunicorn with sudo privileges in your text editor:

```
$ sudo nano /etc/systemd/system/gunicorn.service
```

Your _gunicorn.service_ file should look something like this:

```
[Unit]
Description=gunicorn service
After=network.target

[Service]
WorkingDirectory=/home/vagrant/webapps/your_project_parent_dir/project_name
ExecStart=/home/vagrant/webapps/your_project_parent_dir/env/bin/gunicorn --access-logfile - --bind \ 
unix:/home/vagrant/webapps/your_project_parent_dir/your_project/your_project.sock \
your_project.wsgi:application

[Install]
WantedBy=multi-user.target
```

The `[Unit]` and `[Install]` remain as they are. In the `WorkingDirectory`, you need to provide absolute path to your project. In `ExecStart`, you specify command to run _gunicorn_, but instead using `gunicorn` command directly, you need to specify `which gunicorn`, hence, you provide the path to gunicorn, if it is installed globally, you would need to specify the absolute path to global command file, like  `/usr/local/bin/gunicorn` followed by parameters `--access-logfile` and `--bind`, after `unix:` specify absolute path to _your_project.sock_
Now we would have to start this service.

Run command: 

```
$ sudo systemctl start gunicorn
```

Check status of the service:

```
$ sudo systemctl status gunicorn
```

You can confirm that the operation was successful by checking for the socket file.

```
$ ls your_project_parent_dir/your_project/
```

You should find a file called _your_project.sock_ here.

If not, check for the service configurations or status of it.
To check for errors look up in the journal:

```
$ sudo journalctl -u gunicorn
```

Other reasons could be :

* The owner is **root** and not **sudo**.
* The `WorkingDirectory` path does not point to the project directory.
* The path given for configuration for gunicorn service in `ExceStart` directive is incorrect. Check these:
    * The path to the _gunicorn_ binary points to the actual location of the binary within the virtual environment.
    * The `--bind` directive defines a file to create within a directory that Gunicorn can access.
    * The _your_project.wsgi_ should be immediate child to _your_project directory_.

Remember a reload of daemon service would be required if the file is edited :

```
$ sudo systemctl daemon-reload
$ sudo systemctl restart gunicorn
```

### 4. Setting up nginx

Install Nginx in your guest, type this command in terminal:

```
sudo apt-get install nginx
```

Check if nginx is successfully installed:

```
$ nginx
```

You should not see `command not found` error.

Now we need to create and start a new server block in `/etc/nginx/sites-available`.

```
$ sudo nano /etc/nginx/sites-available/you_project
```

Here specify something like this:

```
server {
    listen 8000;
    server_name 0.0.0.0;
    location = /favicon.ico { access_log off; log_not_found off; }
    location / {
        include proxy_params;
        proxy_pass http://unix:/home/vagrant/webapps/project-name/project-name.sock;
    }
}
```


The argument to `listen` is the port of guest that would be exposed, you should mention this port in `forwarded_port` and map it to some host port in _VagrantFile_.
The `server_name` is the IP location or DNS location of your server, here it is localhost so we provide `server_name` as 0.0.0.0.
In `proxy_pass` after `unix:` specify absolute path to _your_project.sock_ file.

If you have static files you would need to add these extra lines

```
location /static/ {
        root /home/vagrant/webapps/your_project_parent_dir/your_project;
    }
```

inside `server` object.

Save and close the file when you are finished. Now, you can enable the file by linking it to the sites-enabled directory:

```
$ sudo ln -s /etc/nginx/sites-available/your_project /etc/nginx/sites-enabled/
```

Test your nginx for syntax errors:

```
$ sudo nginx -t
```

If the test is ok, you would require to restart the _nginx_ now and check its status.

```
$ sudo systemctl restart nginx
$ sudo systemctl status nginx
```

You are all set. Hit _0.0.0.0:host_port_ on browser to view your Django projects app page.


### References:

* Digital Ocean's [How To Set Up Django with Postgres, Nginx, and Gunicorn on Ubuntu 16.04](https://www.digitalocean.com/community/tutorials/how-to-set-up-django-with-postgres-nginx-and-gunicorn-on-ubuntu-16-04)
* OSX Daily's [How to Install VirtualBox in MacOS Mojave if Installation Fails or Shows Kernel Driver Errors](http://osxdaily.com/2018/12/31/install-run-virtualbox-macos-install-kernel-fails/)