Omonbude Emmanuel
Software Engineer
HTML/CSS/JAVASCRIPT
PYTHON
DJANGO/DJANGO REST FRAMEWORK
NODE JS
DOCKER, AWS, KUBERNATES
FIREBASE, PostgreSQL, MySQL,Mongo DB
FLUTTER
DART
ANGULAR
TYPESCRIPT

Deploy Django with Postgres, Nginx, and Gunicorn on Ubuntu Server

Omonbude Emmanuel | Nov. 23, 2023, 4:57 a.m.

998

Introduction

 

Deploying a Django web application involves more than just putting your code on a server. It requires a well-configured stack that includes a web server, application server, and a relational database.

In this guide, we will walk you through the process of deploying a Django application using Postgres as the database, Nginx as the web server, and Gunicorn as the application server on a server running Ubuntu.

Prerequisites

 

To successfully follow this guide, ensure that you have a VPS server with  Ubuntu installed and with a basic firewall.

Our approach involves installing Django within a dedicated virtual environment. By doing so, each project can maintain its own environment, ensuring clean separation of dependencies and requirements.

After setting up the database and deploying your Django application, the next step involves installing and configuring the Gunicorn application server. Acting as an intermediary between client requests and Python calls, Gunicorn facilitates seamless communication with your application. Following this, we'll integrate Nginx with Gunicorn to harness its high-performance connection handling and straightforward security features.

This guide will walk you through these steps, providing a comprehensive approach to deploying and optimizing your Django application on Ubuntu.

LETS BEGIN!

Set Up a Firewall for Network Security

 

Establishing a firewall on your server enhances network security by defining rules for incoming and outgoing server requests. In this guide, we'll utilize Uncomplicated Firewall (UFW), which is pre-installed on Ubuntu, simplifying the setup process without requiring additional installations.

To activate UFW on your server, use the following command:

Note: Ensure that you have SSH access to your remote server.

ufw enable

Allow all outgoing ports by running the command below. You can allow all outgoing requests because it doesn’t pose a threat from external sources.

ufw default allow outgoing

Run the command below to prevent access to your server from the outside world.

ufw default deny incoming

When you ran the command above, you also blocked SSH requests. To continue using SSH to access our server, you must enable SSH. To do this, run the following command:

ufw allow ssh

Allow http/tcp traffic on the firewall so that your browser can access your app.

sudo ufw allow http/tcp

 

Installing Python

 

sudo add-apt-repository ppa:deadsnakes/ppa

sudo apt update

sudo apt install python3

 

Initial Server Setup

 

Run the following commands to update the package list, install necessary dependencies, and set up PostgreSQL:

sudo apt update


sudo apt install python3-pip python3-dev libpq-dev postgresql postgresql-contrib nginx curl

 

Setting Up Django in a Virtual Environment

 

  •  Navigate to your project's directory:
  • Install the virtual environment package and Create a virtual environment using the following command
sudo apt install python3-venv
python3 -m venv myvenv
  • Activate the virtual environment: 

source myvenv/bin/activate

  •  Install Django and other dependencies:
pip install django gunicorn psycopg2-binary

 psycopg2-binary is a PostgreSQL adapter for Python. It's a Python module that allows Python programs to interact with PostgreSQL databases.

 

Create a Database and User

Access an interactive PostgreSQL session by entering the following command:

sudo -u postgres psql

create a database for your project:

CREATE DATABASE mydjangodb;

Next, create a database user for our project. Make sure to select a secure password:

CREATE USER mydjangouser WITH PASSWORD 'PASSWORD123'; 

Now, we can give our new user access to administer our new database:

GRANT ALL PRIVILEGES ON DATABASE mydjangodb TO mydjangouser; 

ALTER DATABASE mydjangodb OWNER TO mydjangouser;

GRANT USAGE, CREATE ON SCHEMA PUBLIC TO mydjangouser;

Exit the PostgreSQL prompt.

\q

 

Creating a new django project

django-admin startproject myproject

if you have an existing project on github already, you can clone it in this directory

 

Edit the settings.py

nano myproject/settings.py

We first of all change the ALLOWED_HOSTS in our settings file.

 

ALLOWED_HOSTS is a Django settings variable used for security. It defines a list of valid host/domain names that the Django application can serve. Properly configuring it helps prevent HTTP Host header attacks by ensuring that the Host header in incoming requests matches one of the allowed values. This setting is crucial for production environments to enhance the security of your Django application. 

 ALLOWED_HOSTS = ['yourdomain.com', 'youripaddress', 'sub.yourdomain.com', 'localhost']

#Replace 'yourdomain.com', 'sub.yourdomain.com', and 'localhost' with the actual domain names or IP addresses that your Django application is allowed to serve.

#You can use a wildcard ('*') to allow any subdomain.

Next step is to configure our data base

Find the DATABASES setting in the settings.py file and configure it according to your database setup. If you're using PostgreSQL, the configuration may look like this: 

The details can be gotten from the database we created with postgres

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql',
        'NAME': 'mydjangodb',
        'USER': 'mydjangouser',
        'PASSWORD': 'PASSWORD123',
        'HOST': 'localhost',
        'PORT': '',
    }
}

Next, we Configure static files in Django by specifying the static URL, defining directories where static files are located (STATICFILES_DIRS), and setting the absolute path for collecting static files (STATIC_ROOT).

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

SAVE THE FILE WITH CTRL + S and exit with CTRL + x

 

Apply Migrations 

python3 manage.py makemigrations

python3 manage.py migrate

 

Create Superuser

This step is optional and allows you to create an administrative superuser account for the Django admin interface. You will have to select a username, provide an email address, and choose and confirm a password.

python manage.py createsuperuser

 

Collect static 

We can collect all of the static content into the directory location we configured. You will have to confirm the operation. The static files will then be placed in a directory called static within your project directory.

python manage.py collectstatic

 

Allowing Incoming Traffic on Port 8000 with UFW

When deploying a web application or service that utilizes port 8000, it's essential to configure the firewall to permit incoming traffic on this specific port. In Ubuntu, the Uncomplicated Firewall (UFW) provides a straightforward way to manage firewall rules.

sudo ufw allow 8000

You can test your project by launching the Django development server using the following command:

python manage.py runserver 0.0.0.0:8000 

This command initiates the development server and makes your project accessible. 

 

In your web browser, go to the domain name or IP address of your server, and append ":8000" to the end of the address.

http://server_domain_or_IP:8000

Type CTRL + C to kill the server

Verifying Gunicorn's Capacity to Serve the Project

Before exiting the virtual environment, it's crucial to verify Gunicorn's capability to serve the project. Navigate to the project directory and employ Gunicorn to load the project's WSGI module, ensuring the proper functioning of the application.

gunicorn --bind 0.0.0.0:8000 myproject.wsgi

The command gunicorn --bind 0.0.0.0:8000 myproject.wsgi is used to start the Gunicorn web server and bind it to the specified IP address and port.

It starts Gunicorn and makes the Django application accessible on the specified IP address and port. Testing Gunicorn with this command ensures that it can successfully serve the Django project in a production environment

now we're done, we can deactivate our virtual environment by running the command

deactivate

 

Create Gunicorn systemd file

 A systemd file is a system configuration file designed to automate the execution of your application. Instead of manually running the Gunicorn command each time or monitoring your app, the systemd file manages this process for you. It ensures that your application restarts automatically in various scenarios, such as server maintenance or power outages. This automation enhances the reliability and stability of your application, allowing systemd to handle the lifecycle of your app seamlessly.

Start by creating a systemd socket file for gunicorn.

sudo nano /etc/systemd/system/gunicorn.socket

The contents of the file should look like this:

[Unit]
Description=gunicorn socket

[Socket]
ListenStream=/run/gunicorn.sock

[Install]
WantedBy=sockets.target

Save with CTRL+S and exit with CTRL+X

Next we create a systemd service file for gunicorn with the following command:

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

Paste the following code

[Unit]
Description=gunicorn daemon
After=network.target

[Service]
User=root
Group=www-data
WorkingDirectory=/home/yourusername/path-to-your-projectdir
ExecStart=/home/yourusername/path-to-your-projectdir/yourprojectenv/bin/gunicorn \
--access-logfile - \
--workers 3 \
--bind unix:/run/gunicorn.sock \
myproject.wsgi:application

[Install]
WantedBy=multi-user.target

NOTE

WorkingDirectory is the path to where your manage.py file is

ExecStart is the path to where your virtualenv/bin/gunicorn

User should be the user you're logged in as , in my case, I'm logged in as the root user
Replace the directory to your own directory and make sure the path is correct. When you have completed and checked your file, you can run the following commands to start your systemd file.

Now we can start and enable gunicorn

sudo systemctl start gunicorn.socket
sudo systemctl enable gunicorn.socket

 

check for the status of your gunicorn 

 

sudo systemctl status gunicorn.socket

You should have something like the screenshot below.

Screenshot

If there is an error reported by the systemctl status command or if the gunicorn.sock file is not present in the directory, it suggests an issue with the successful creation of the Gunicorn socket. To investigate further, review the logs for the Gunicorn socket by entering:

sudo journalctl -u gunicorn.socket

 

Check the systemd status

This verifies that gunicorn is working

sudo systemctl status gunicorn


Image screenshot
If the output from curl or the output of systemctl status indicates that a problem occurred, check the logs for additional details:
 
sudo journalctl -u gunicorn
To validate the socket activation mechanism, you can send a connection to the socket using curl with the following command:
curl --unix-socket /run/gunicorn.sock localhost 

 

If successful, you will receive the HTML output from your application in the terminal. This confirms that Gunicorn was initiated and could serve your Django application.

 

Troubleshooting Gunicorn

 

Check your /etc/systemd/system/gunicorn.service file for problems.

If you make changes to the /etc/systemd/system/gunicorn.service file, reload the daemon to reread the service definition and restart the Gunicorn process by typing:

sudo systemctl daemon-reload
sudo systemctl restart gunicorn

 

Configure Nginx to server your Django Application

Start by creating and opening a new server block in Nginx’s sites-available directory:

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

paste the following code

 

 server {
    listen 80;
    listen [::]:80;
    server_name yourdomain.com www.yourdomain.com youripaddress;

    location = /favicon.ico { access_log off; log_not_found off; }
    location /static/ {
        root /home/yourusername/path-to-yourstatic-folder;
    }

    location / {
        include proxy_params;
        proxy_pass http://unix:/run/gunicorn.sock;
    }
}

 

Close the file. Enable the file using the command:

 

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

 

Test your Nginx configuration for syntax errors by typing:

 

sudo nginx -t

If no errors are reported, go ahead and restart Nginx by typing:

 

sudo systemctl restart nginx

It is necessary to allow regular traffic on port 80 by adjusting our firewall settings. As we no longer require access to the development server, we can eliminate the rule that opened port 8000 as well.

 

sudo ufw delete allow 8000
sudo ufw allow 'Nginx Full'

Now everything is done. Go ahead and paste your server ip or domain name in your browser, it should work!!

 

Troubleshooting Nginx

  1. If Nginx Is Showing the Default Page Instead of the Django Application

    If Nginx is displaying the default page instead of forwarding requests to your application, it's likely that you need to modify the server_name directive in the /etc/nginx/sites-available/myproject file to match your server's IP address or domain name.

    Nginx relies on the server_name to identify the appropriate server block for handling incoming requests. If you encounter the default Nginx page, it indicates that Nginx was unable to match the request to a specific server block, so it's falling back on the default block configured in /etc/nginx/sites-available/default.

    To ensure that Nginx selects the server block associated with your project, the server_name specified in your project's server block should be more specific than the one defined in the default server block.

  2. Nginx Is Displaying a 502 Bad Gateway:
    This error typically indicates that Nginx is unable to communicate with the upstream server, such as Gunicorn or another application server. Several reasons can lead to this error:

    Application Server Not Running: Ensure that your application server (e.g., Gunicorn) is running and configured correctly. Check its logs for any issues.

    Incorrect Upstream Configuration: Verify the proxy_pass configuration in your Nginx configuration file. It should correctly point to the upstream server.
     

    location / {    

        proxy_pass http://unix:/run/gunicorn.sock;
    }
    


    Firewall Issues: Check if your firewall is blocking communication between Nginx and the upstream server. Ensure that the necessary ports are open.
    Insufficient Resources: If your server is running out of resources (CPU, memory), it may lead to the "502 Bad Gateway" error. Check system resource usage.

The main source of additional information is found in Nginx's error logs. Typically, these logs provide details about the conditions that led to issues during the proxying process. To access the Nginx error logs, use the following command:

 

sudo tail -F /var/log/nginx/error.log

NOTE

If you make any changes to your code, always restart the gunicorn with the following command for it to reflect.

service gunicorn restart

Conclusion 

Successfully deploying a Django application with Postgres, Nginx, and Gunicorn on Ubuntu requires careful configuration and attention to detail. By following the steps outlined in this guide, you can effectively set up a robust and scalable production environment for your Django project. Remember to thoroughly test your application and ensure it functions as expected before deploying it to a live production environment.

© 2024 Omonbude Emmanuel

Omonbude Emmanuel