Ubuntu server is arguably the most popular operating system for servers. This article explains the steps to deploy a Django project in an Ubuntu server. These instructions will work on other Debian based operating systems such as Debian server since Ubuntu is based on Debian.

We will be using the following stack to deploy our Django project.

  • Gunicorn (Green unicorn) as WSGI server
  • PostgreSQL as the database server
  • Nginx as a reverse proxy server and static file server
  • Supervisor to manage Gunicorn processes

Initial Setup

Create an account in your favourite cloud service (AWS, Digital Ocean etc.) and create an Ubuntu server instance. Note down the ssh credentials when you are creating the server. Specifically, you will need the following credentials.

  • IP address of your server
  • Username of the Ubuntu user account
  • Password for the user account if you are using a password to connect to the server. Your cloud provider usually emails this to you.
  • Key file if you are using key file instead of a password to connect to the server. Your cloud provider will ask you to download this before creating the server.

We will be using ssh to do the deployment. You should check your cloud providers docs on connecting to servers using ssh if you are not familiar with this process. Also, if your cloud provider has a firewall, configure it to allow traffic through ports 22 and 80.

Once you got the server up, ssh into your server. Depending on the authentication method, your ssh command will vary. You should use the following command if you are using password authentication.

ssh your-username@your-server-ip-address

Use the following command if you have to use a key file to authenticate.

ssh your-username@your-server-ip-address -i path-to-your-key-file

Give yes if you get a message about unknown host. You are likely to get an error about permissions of your key file if you are using key authentication. Run the following command to fix it and try again.

chmod 600 path-to-your-key-file

The next step is to update the list of packages. Run the following apt command and wait till it finishes. You may need to enter your password to run this.

sudo apt update

Get Your Source Code on to the Server

Since you are going to deploy, you should already have a version of your Django project with you. Chances are, you are using git to manage your source code. The instructions below are for git-based source code management. If you are using some other version control systems or just FTP, get the source code to the server using your preferred way and proceed to the next step.

We need to get git installed on the server first. We will be using apt for this.

sudo apt install git

Now, let us create a directory in your user’s home directory to store our source packages. I will be naming this directory as projects but you can use whatever is your preferred path. Just remember to use the correct path in the coming instructions if you are using a different location.

mkdir ~/projects
cd ~/projects

Let’s clone the source code to this directory.

git clone your-repository-url

You will need to do some additional steps if you are using ssh URL for cloning the repository. I am omitting those steps for simplicity. You will probably able to do all those yourself if you are using ssh URL.

PostgreSQL Setup

You can skip this part if you are using a cloud-based database solution like AWS RDS or if you are using SQLite.

We will be using apt to install PostgreSQL and related packages. Run the following apt command.

sudo apt install postgresql postgresql-contrib libpq-dev

PostgreSQL will be installed with a default user but we do not know the password of this user. An Ubuntu user will also be created during the installation process. We need to log in as this Ubuntu user and reset the password for the default postgres user. Use su command to login as postgres user account.

sudo su postgres

Now, open the psql shell by running the psql command.

psql

Once the psql shell is started, use \password command to reset the password for user postgres.

\password

You will be asked to enter your new password and confirm the new password. Once it is complete, exit from psql shell using \q command.

\q

Now, you need to logout from the postgres user shell and come back to the normal Ubuntu user shell. Run exit command for this.

exit

You can reset your postgres password by following the above steps if you ever forget it.

Let’s login to psql shell and create a database for our project.

psql -h 127.0.0.1 -U postgres

Enter the password you created in the last step when prompted.

Once you are in psql shell, run the create database command to create a new database. Do not forget the semi-colon at the end of the command.

create database your-database-name;

Now exit from the shell by using \q command. You can create a new user for your project and assign permissions to that user but for keeping things simple, we will be using the default postgres user in this article.

You will need to update your Django settings file with the following configuration.

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql',
        'NAME': 'your-database-name',
        'USER': 'postgres',
        'PASSWORD': 'your-postgres-password',
        'HOST': '127.0.0.1',
        'PORT': '5432',
    }
}

Make sure you replaced values for NAME and PASSWORD with your own values. You may want to do some extra configuration if you are depending on environment variables or configuration files to feed database configuration. Again, for keeping things beginner friendly, we are assuming a simple setup here.

Python Setup

We have to go the source code to the server and PostgreSQL installed. Now, let’s move to set up our python environment. We need to install the required python dependencies using apt. I am assuming you are using Python 3. If not use python instead of python3. (You should be using Python 3 since Python 2.7 is sentenced to death and waiting for execution.)

sudo apt install build-essential python3-dev python3-pip python3-venv libjpeg-dev

Package libjpeg-dev is required if you are using Pillow for image processing. You can remove it if you are not using Pillow. Just keep it there if you are not sure.

The python3-venv package is for creating virtual environments. You can refer this article if you prefer using virtual environment wrapper.

We need to create a virtual environment for our project. We will be using the venv Python module for creating virtual environments.

First cd to your project directory

cd ~/projects/your_project

and then create a virtual environment.

python3 -m venv env

Activate the virtual environment by sourcing the activate file.

source env/bin/activate

Next, you need to install your dependencies from your requirements file (if you have one).

pip install -r requirements.txt

You will have to install each package by hand if you do not have a requirements file. (This might be a good time to create a requirements file for your project.) You can use pip to install your dependencies even if you do not have a requirements file. The following command will install Django and psycopg2 packages.

pip install django psycopg2-binary

After installing the required Python packages, you should be able to run your migrations.

python manage.py migrate

Check your database settings if you get an error here. Once you have your migrations applied, move to next step.

Nginx Setup

We will be using Nginx as a reverse proxy server. What that means is, browsers will be contacting Nginx but Nginx is not actually running the Django project. Nginx will forward the requests from clients to Django app running in a port and when the Django app responds with a response, Nginx will pass that response to the client browser.

Installing Nginx is very simple using apt.

sudo apt install nginx

Once the installation is complete, visit your server’s IP address from a browser. You should see Nginx’s welcome page. Check your cloud provider’s firewall settings if your browser is not able to connect to your server.

We need to show our Django application instead of the welcome page. For this, we have to create a configuration file and instruct Nginx to forward all requests to a port where Django app is running.

Nginx’s configuration files live in the /etc/nginx/ directory. There are two directories, sites-available and sites-enabled. Nginx will look in the sites-enabled directory for configuration files but we will be creating our configurations in the sites-available directory. We will then create a symbolic link in sites-enabled directory. These details may sound very complicated but the implementation is pretty easy. Just follow along with the rest of the instructions.

In sites-enabled directory, we already have a configuration to serve the default welcome page. Let us delete this file.

sudo rm -f /etc/nginx/sites-enabled/default

Now, create a new configuration file in sites-available directory and open it in nano editor.

sudo nano /etc/nginx/sites-available/your_project_name

Enter the following content into the file.

server {
    listen 80;
    server_name _;

    location / {
        include proxy_params;
        proxy_pass http://127.0.0.1:8000;
    }
}

Press Ctrl+x to exit from nano and confirm the save dialogue by pressing y.

This configuration instructs Nginx to listen on port 80 and forward all requests to port 8000.

The next step is to create a symbolic link to this file in sites-enabled directory.

sudo ln -s /etc/nginx/sites-available/your_project_name /etc/nginx/sites-enabled/

To test your configuration files, run

sudo nginx -t

Check the syntax of your configuration file if you get an error from this command. Once the test is successful, restart the Nginx service.

sudo systemctl restart nginx

Now open your server IP address in a browser. You should see a page with a 502 Bad Gateway Error. That is because we do not have anything running is port 8000.

If you start the Django development server by using python manage.py runserver command, you will see your Django app instead of 502 error. Make sure you are running it from your project directory and you have your virtual environment activated.

Running your Django app like this is not a permanent solution. In the next step, we will see how we can improve this by using gunicorn and supervisor.

Gunicorn and Supervisor Setup

Gunicorn is a lightweight WSGI server which can be used to run any WSGI supported apps such as Django, Flask or event plain WSGI without any framework. We will be using pip to install gunicorn.

Activate your virtual environment if not already activated and run the following command.

pip install gunicorn

Now we have to find the path of your WSGI file. You will have a structure similar to this if you are using the default Django setup.

- your_project
    - your-app-1
    - your-app-2
    - other apps
    - your_project
        wsgi.py
        other files such as urls.py, settings.py

In this case, your WSGI file path is your_project.wsgi (Do not forget to substitute your_project with correct name) because your wsgi.py file is inside the directory your_project.

Let’s see if gunicorn can successfully run our Django app. cd to your project directory if you are not already there. Run the following command to start gunicorn.

gunicorn your_project.wsgi

You should see gunicorn starting messages. If you go to the IP address from the browser, you should see your Django project page. You will not see any static files loaded in your site. Gunicorn will not load any static or media files from your application. We need to configure Nginx to do this. You will see how to do this in the next section.

Check your WSGI file path if you got any errors while running gunicorn. Also, you should run this command from the outer your_project directory, not from the inner one.

We have substituted runserver with gunicorn but this also has the same problems of Django development server. Your server will stop if you press Ctrl+c or if you logout from your ssh session.

The simple solution for this is to run gunicorn in daemon mode. In this mode gunicorn will run in background and serve the requests. Use the following command to start gunicorn in daemon mode.

gunicorn your_project.wsgi -D

You will not see any output from this command but if you go to your browser and load your app, you will see it running. You can safely log out from your ssh session and your server will still be running.

Since gunicorn is running in background, pressing Ctrl+c will not stop your server. The easiest way to stop gunicorn daemon is by using the pkill command.

sudo pkill gunicorn

The main problem with this approach is that you need to manually start gunicorn process after a server reboot. Also, if your operating system decides to kill gunicorn process, there is no way to automatically restart it. If any of these happens at the middle of the night, your users will see the 502 error page until you start gunicorn daemon. You can skip the rest of this step and move to the next section if this is not an issue for you. Just remember to kill gunicorn and start it again if you change your code. Gunicorn will not automatically load code changes.

The next step is to use supervisor to manage our gunicorn process. Supervisor will take care of restarting gunicorn if it ever gets killed for some reason. Before proceeding further, make sure the gunicorn process is not running. (Use the pkill command from above.)

We will be using APT to install supervisor.

sudo apt install supervisor

Supervisor configuration files are located in /etc/supervisor/conf.d/ directory. Let us create a configuration file and open it in nano.

sudo nano /etc/supervisor/conf.d/your_project.conf

Enter the following content to this file.

[program:your_project_gunicorn]
command = /home/your_ubuntu_username/projects/your_project/env/bin/gunicorn your_project.wsgi -b 127.0.0.1:8000 -w 3
directory = /home/your_ubuntu_username/projects/your_project

A few things to note while you are editing this file

  • The command option specifies the command for supervisor to manage. Here we are specifying the command to start gunicorn process
  • The command starts with a path to gunicorn binary file. Make sure you replace your_ubuntu_username and your_project with correct values. Update this command accordingly if you are using a different location for storing projects and virtual environments.
  • The -b option specifies the IP and port for running gunicorn.
  • The -w option specifies the number of gunicorn workers. The formula to calculate this is 2n + 1 where n is the number of CPU cores your server has. Your value will be 3 if you have a single-core CPU.
  • The directory option specifies the directory from where the supervisor should run the command. Update this value to match your paths.

Once you complete editing, press Ctrl+x to exit and confirm the save dialogue.

The next step is to notify supervisor about our new configurations. For this, you need to issue a reread command.

sudo supervisorctl reread

Then you need to update the process group.

sudo supervisorctl update

Now supervisor knows about our configurations. Issue a restart all command to force supervisor to start all process.

sudo supervisorctl restart all

You should run this command if you ever make changes to your python files.

You should be able to see your project running when you open server IP address in a browser. Check your configuration files and paths if you are still seeing a 502 page.

In the next step, we will go through a few extra things to make our deployment more robust.

Serving Static and Media Files

Your site will be displayed in the browser without any static assets such as CSS files. In this step, we are going to instruct Nginx to serve static and media files.

The first step to run the collectstatic command. You will need to configure a STATIC_ROOT settings variable for this. Check Django documentation if you are not familiar with static paths in Django.

If you do not have a STATIC_ROOT configured and you are using Django’s default directory structure, you can add the following line to your settings file to it working.

STATIC_ROOT = os.path.join(BASE_DIR, 'static')

This will set the static directory inside your project directory as the static root.

Now, run the collectstatic management command. Make sure you are in the project directory and you have the virtual environment activated.

python manage.py collectstatic

The next step is to configure Nginx to serve the static and media files. Open your Nginx configuration

sudo nano /etc/nginx/sites-available/your_project_name

and update it to look like the following. Make sure you are using the correct paths for media and static directories.

server {
    listen 80;
    server_name _;

    location / {
        include proxy_params;
        proxy_pass http://127.0.0.1:8000;
    }

    location /static/ {
        root /home/your_ubuntu_username/projects/your_project/;
        add_header Pragma public;
        add_header Cache-Control "public";
    }

    location /media/ {
        root /home/your_ubuntu_username/projects/your_project/;
        add_header Pragma public;
        add_header Cache-Control "public";
    }
}

Exit nano by pressing Ctrl+x. To make sure you haven’t introduced any errors in the conf file, run the Nginx test command.

sudo nginx -t

Next, you need to restart Nginx to load the changed configuration file.

sudo systemctl restart nginx

You should see all your static and media files loaded in the browser.

Congratulations! You have successfully deployed your Django project.


Last updated on July 7, 2019
Tags: Python Django Ubuntu Server Nginx Gunicorn Supervisor PostgreSQL