A Developer's Guide to Deploying Django Application Efficiently on Ubuntu with Nginx
Tags: django, ubuntu, nginx, devops
Published: 2024-05-14 05:49:13

Hosting a Django application involves multiple components to ensure it runs efficiently and securely. In this blog, we'll cover the step-by-step process of hosting a Django application with a PostgreSQL database, using Gunicorn as the application server managed by Supervisor, Nginx as the web server, and Redis for caching.

Connect to your instance via SSH, and proceed with the necessary setup. 
> I have used AWS EC2 t4g.small instance with Ubuntu AMI for this Hosting post.

**Step 1: Installing and Setting Up PostgreSQL**
```
# Install PostgreSQL
sudo apt update
sudo apt install postgresql postgresql-contrib

# Enable and start Postgresql
sudo systemctl enable postgresql
sudo systemctl start postgresql
```
```
# Create database and role:
sudo su - postgres
createdb db_name
echo "CREATE ROLE db_user WITH PASSWORD 'db_password';" | psql
echo "ALTER ROLE db_user WITH LOGIN;" | psql
echo "GRANT ALL PRIVILEGES ON DATABASE "db_name" to db_user;" | psql
exit
```
> Replace db_name with your database name, db_user with your database user and db_password with your database password

**Step 2: Organizing project structure**

```
cd
mkdir repo.git app conf logs media static
```
These directories will respectively hold your Git repository, application code, configuration files, logs, media files, and static files. This organized structure ensures that all components of your Django project are neatly separated, making it easier to manage and maintain the application throughout its lifecycle.

**Step 3: Initialize and Configuring Git Repository**
Initialize a bare Git repository. A bare repository is used to manage code collaboration without working copies.
```
cd repo.git
git init --bare
git --bare update-server-info

# Configure the repository to work with our deployment setup
git config core.bare false
git config receive.denycurrentbranch ignore
git config core.worktree /home/ubuntu/app/
```
The core.bare false configuration makes the repository behave like a non-bare repository. The receive.denycurrentbranch ignore allows us to push to the currently checked-out branch. The core.worktree configuration points to the directory where our application code will live.

Create a Post-Receive Hook
A Git hook is a script that is executed at certain points in the Git workflow. We'll use a post-receive hook to automatically deploy the code whenever a push is made to the repository.
```
cat > hooks/post-receive <<EOF
#!/bin/sh
git checkout -f
. ../env/bin/activate
cd ../app

pip install -r requirements/prod.txt
python manage.py collectstatic --noinput
python manage.py migrate -v 0
sudo supervisorctl reload
EOF
```
```
# Make the script executable
chmod +x hooks/post-receive
```

**Step 4: Adding remote repository in local machine**

```
# In Your Project, in Local Machine
git remote add prod ubuntu@ip_address:/home/ubuntu/repo.git/
ssh-copy-id ubuntu@ip_address
git push prod master
```

**Step 5: Setup Virtual environment**
Using a virtual environment is a best practice for Python projects, as it isolates your project dependencies from the system-wide packages. 
```
sudo apt install python3-pip
pip3 install virtualenv

cd 
virtualenv env -p python3
source env/bin/activate
```

**Step 6: Project Setup in Server**
```
cd app
pip install -r requirements/prod.txt
pip install gunicorn
python manage.py migrate
python manage.py collectstatic
```

**Step 7: Installing and Configuring Supervisor**
Supervisor is a process control system that helps manage and monitor your applications. It keeps your processes running by automatically restarting them if they fail. It ensures that your applications stay up and running, providing a simple way to manage long-running processes.
```
sudo apt install supervisor
sudo systemctl enable supervisor
sudo systemctl start supervisor
```
```
vi conf/supervisor.conf
```
```
[program:django_project]
command=/home/ubuntu/env/bin/gunicorn config.wsgi:application --workers 3 --bind 127.0.0.1:8000

user=ubuntu
directory=/home/ubuntu/app/
autostart=true
autorestart=true
stdout_logfile=/home/ubuntu/logs/django.log
stderr_logfile=/home/ubuntu/logs/django_err.log
```
> config.wsgi:application change this to your wsgi folder path

This Supervisor configuration file manages the Django project by running the Gunicorn server with three worker processes, binding it to 127.0.0.1:8000. The django_project program runs under the ubuntu user within the /home/ubuntu/app/ directory. The process is set to autostart with the system and automatically restart if it fails. Logs are directed to /home/ubuntu/logs/django.log for standard output and /home/ubuntu/logs/django_err.log for errors, ensuring comprehensive monitoring and management of the application.

```
# Soft-link supervisor configuration to supervisor conf.d directory
sudo ln -s /home/ubuntu/conf/supervisor.conf /etc/supervisor/conf.d/project_name.conf
sudo supervisorctl reload
```

**Step 8: Installing and Configuring Nginx**
Nginx is a high-performance web server and reverse proxy that efficiently handles incoming requests and serves static content. It enhances the performance and scalability of Django application by forwarding requests to backend servers like Gunicorn, ensuring your application can manage high traffic with stability and low resource consumption.
```
sudo apt install nginx
sudo systemctl enable nginx
sudo rm /etc/nginx/sites-enabled/default
```

```
vim conf/nginx.conf
```

```
upstream django_project {
    server 127.0.0.1:8000;
}


server {
    listen 80;
    server_name domain_name.com;

    access_log /home/ubuntu/logs/nginx.access.log;
    error_log /home/ubuntu/logs/nginx.error.log;

    # limit_conn conn_limit_per_ip 100;
    # limit_req zone=req_limit_per_ip burst=100 nodelay;

    location /robots.txt {
        alias /home/ubuntu/static/robots.txt;
    }
    
    location /favicon.ico {
        alias /home/ubuntu/static/img/favicon.ico;
    }
    
    location ~ ^/(media|static)/  {
        root    /home/ubuntu/;
        expires 30d;
    }

    location / {
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Host $http_host;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_redirect off;
        proxy_pass http://django_project;
        client_max_body_size 50m;
        add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
        add_header X-Content-Type-Options nosniff;
        add_header X-XSS-Protection "1; mode=block";
        add_header X-Frame-Options SAMEORIGIN;
    }
    
    # Prevent hidden files (beginning with a period) from being served
    location ~ /\. { access_log off; log_not_found off; deny all; }
}
```
This Nginx configuration sets up a reverse proxy for a Django application running on 127.0.0.1:8000. It listens on port 80 and uses the server name domain_name.com. Access and error logs are stored in specified directories. Static and media files are served directly from the /home/ubuntu/ directory, with caching enabled for 30 days. The configuration also handles robots.txt and favicon.ico requests directly.

In the main location block, it sets various headers for security and proxies requests to the Django application. It limits the maximum client request body size to 50MB and includes security headers to enforce HTTPS, prevent content sniffing, XSS attacks, and clickjacking. Additionally, it denies access to hidden files, enhancing security by preventing unauthorized access.

> Don't forget to replace domain_name with your own domain.

Soft-link this configuration to nginx conf.d directory
```
sudo ln -s /home/ubuntu/conf/nginx.conf /etc/nginx/conf.d/django_project.conf
```

**Step 9: Securing Server with SSL**

```
sudo apt install certbot python3-certbot-nginx
sudo certbot --nginx
# Follow the on-screen instructions to complete the setup.
```

**Step 10: Reload Nginx and Supervisor**
```
# Checking Nginx configuration
sudo nginx -t

# Restarting Nginx
sudo systemctl restart nginx

# Restarting supervisor
sudo supervisorctl reload
```
Whenever you make changes to your Nginx or Supervisor configuration, it's crucial to restart these services to apply the updates.