Deploy  Django + MySql Application into AWS EC2 instance with nginx and SSL
Tags: python, django, nginx, aws
Published: 2021-05-13 04:11:18

All the version used in this tutorial

- Python 3.8.X
- Django 3.2.2
- gunicorn 20.1.0
- MySQL 8.0.20 (RDS)


#### EC2 Setup
- Create EC2 instance (Screenshots attached below), during this instance creation.
  we'll have to download `pem/cer` file. It'll be used later to access this instance from the terminal via SSH
![aws_ec2](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/sepcum5zrogbcvfyytte.gif)
- To connect the instance, Follow *_how to connect to instance guide_* from the EC2 dashboard.
- After login into our instance, run the following commands to update our instance -> `sudo apt update; sudo apt upgrade -y`.
- In order to use pip3 and venv we have to install some packages -> `sudo apt install python3-pip python3-venv`
- Let's create one virutal env -> `python3 -m venv env`. And activate it -> `source env/bin/activate`.
- Now we have to install `gunicorn`<sup>[*_`details`_*](#gunicorn)</sup> (make sure you're in the virtual environment) -`pip3 install gunicorn`.

>  Note: if your project has a MySQL database, you have to install some other packages
> `sudo apt install python3-dev default-libmysqlclient-dev build-essential libssl-dev`

- Now let us pull the repository and install all the dependencies. After this do the necessary `migrations`.
- Let us install nginx<sup>[*_`details`_*](#nginx)</sup> -> `sudo apt install nginx -y`. now you can check with `sudo nginx`.
- Before starting the `nginx` setup we have to change our instance's  *_`security groups`_* (Check below screenshots)
![ec2_security_group](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gpwy0c1466ey9okj07rx.gif)
 

-  Now let us do a little test, we're going to start our application by gunicorn (what is it? will discuss it in later.) -`gunicorn --bind 0.0.0.0:8000 courier_allocation.wsgi:application`, after running this we'll see something like 👇🏻
![Screenshot 2021-05-09 at 4.57.28 PM](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/s4lmrdb1aat4p2bamby9.png) 
and now we can access our application from our instance IP address, like this `ec2-XX-XX-XX-XX.ap-south-1.compute.amazonaws.com:8000`.
- But now if we close our terminal then we won't be able to access our application.
- Now we have to do some setup so that our application can run in the background. Here, we'll use `supervisor`<sup>[*_`details`_*](#supervisor)</sup>. To install this package we have to run `sudo apt install -y supervisor`.
- After installation, we have to create one supervisor config file. For this follow the below steps.

> 1. First go to `cd /etc/supervisor/conf.d/`
> 2. Here we'll create one config file -> `sudo touch gunicorn.conf`
> 3. In this `conf` file paste the below code.
> ```
    [program:gunicorn]
    directory=/home/ubuntu/PROJECT_FOLDER
    command=/home/ubuntu/env/bin/gunicorn --workers 3 --bind unix:/home/ubuntu/PROJECT_FOLDER/app.sock DJANGO_PROJECT_FOLDER.wsgi:application
    autostart=true
    autorestart=true
    stderr_logfile=/home/ubuntu/logs/gunicorn.err.log
    stdout_logfile=/home/ubuntu/logs/gunicorn.out.log
    [group:guni]
    programs:gunicorn
```
---

- Now we have to run some commands

> 1. `sudo supervisorctl reread` -> Output - *_`guni: available`_*
> 2. `sudo supervisorctl update` -> Output -  *_`guni: added process group`_*
> 3. `sudo supervisorctl status` -> Output -  *_`guni:gunicorn                    RUNNING   pid 20199, uptime 0:00:17`_*

---
- Now let's do the nginx<sup>[*_`details`_*](#nginx)</sup> configuration
  
> 1. First we have to go to - `cd /etc/nginx/sites-available/`.
> 2. Here we need to create one config file - `sudo touch django.conf` (you can choose any file name). Paste the below code inside your newly created file
```
    # Nginx Configuration for sample Django application
    ######################################################
    server {
        listen 80;
        server_name ec2-XX-XX-XX-XX.ap-south-1.compute.amazonaws.com;
        location / {
            include proxy_params;
            proxy_pass http://unix:/home/ubuntu/PROJECT_FOLDER/app.sock;
        }
    }
```
> 1. After this run - `sudo ln django.conf /etc/nginx/sites-enabled/` 
> 2. now let's test - `sudo nginx -t` - Output -> `nginx: the configuration file /etc/nginx/nginx.conf syntax is ok` `nginx: configuration file /etc/nginx/nginx.conf test is successful`
> 3. Sometime you might get this error -> `nginx: [emerg] could not build server_names_hash, you should increase server_names_hash_bucket_size: 64`. To fix this we have to add `server_names_hash_bucket_size  128;` at the top of your http block (located in `/etc/nginx/nginx.conf`).
> 4. Now we have to restart our nginx service -> `sudo service nginx restart`.
> 5. Our `nginx`<sup>[*_`details`_*](#nginx)</sup> is up and running 😁. You can directly access your app from Public IP Address (EC2)

   > Note: Still a lot of configuration is left, like cache, route multiple ports, load balancer etc. Will discuss it in a different post. 🙂

---
### SSL Setup
`* Requirements: domain address which is mapped to your Instance Public IP.`

- Setup SSL certificate with certbot is quite easy. We just need to install some packages and a little bit of setup

> 1.  Install packages - `sudo apt install certbot python3-certbot-nginx`

- Before we generate the certificate, we have to add our domain name in our `django.conf` 

> 1. Change existing `server_name` to - `server_name test.domain.com;` (if you want to map multiple domains then use as space separate like this - `server_name test.domain.com testing.domain.com;`)

- After setup is done, run the below two commands

> 1. `sudo certbot certonly --nginx`
> 2. `sudo certbot certonly --nginx`

- Now we have to `restart` our `nginx` - `sudo service nginx restart`
- Now you can visit your secure website 😊.


---

### Django Static File issue Fix after deployment (production)

> You might notice that in production, `CSS/JS/Images/Fonts` files are not working for `/admin`.
This is because of the `Static` folder issue. In the local development environment `Django` use `Python HTTPServer`, and it automatically handles `Static files`. But in production our server is running through `nginx`, that's why we have to do little setup. Let's do the setup.

- First, we have to create one `static` folder in our `root` directory.
- Now inside our project main `settings.py` we just need to add one line of code

> STATIC_ROOT =  BASE_DIR / 'static/'

- Now run this command `python3 manage.py collectstatic`<sup>[*_`3`_*](#collectstatic)</sup>. With this command, `Django` will copy all static assets into your `Root Static Folder`. In our case, it's the `Static` folder.

- Now one setup is left, which is `nginx`,

> Go to your `nginx` `django.conf` file and add the below code after the `location` code block
> ```
location /static/ {
               autoindex on;
               alias /home/ubuntu/PROJECT_FOLDER/static/;
}
```
- Now restart `nginx` server - `sudo service nginx restart`.


----

####nginx
> `Nginx`, pronounced like “*_engine-ex_*”, is an open-source web server that, since its initial success as a web server, is now also used as a reverse proxy, HTTP cache, and load balancer.

> Nginx is built to offer low memory usage and high concurrency. Rather than creating new processes for each web request, Nginx uses an asynchronous, event-driven approach where requests are handled in a single thread.

> With Nginx, one master process can control multiple worker processes. The master maintains the worker processes, while the workers do the actual processing. Because Nginx is asynchronous, each request can be executed by the worker concurrently without blocking other requests. [more info](https://kinsta.com/knowledgebase/what-is-nginx/#:~:text=Nginx%2C%20pronounced%20like%20%E2%80%9Cengine%2D,HTTP%20cache%2C%20and%20load%20balancer.)


####gunicorn: 
> *Gunicorn* is a stand-alone *WSGI* web application server that offers a lot of functionality. It natively supports various frameworks with its adapters, making it an extremely easy to use drop-in replacement for many development servers that are used during development.

####supervisor:
> At some point you'll likely find yourself writing a script that needs to run all the time - a "long running script". These are scripts that shouldn't fail if there's an error or ones that should restart when the system reboots.

> To accomplish this, we need something to watch these scripts. Such tools are process watchers. They watch processes and restart them if they fail, and ensure they start on system boot.

> And *Supervisord* is a simple and popular choice for doing these tasks and monitoring our process



####collectstatic
> Using the *collectstatic* command, Django looks for all static files in your apps and collects them into a single location that can easily be served in production, i.e. the STATIC_ROOT. In our case, we are telling Django that when we run `python3 manage.py collectstatic`, gather all static files into a folder called `static` in our project root directory. 


That's it for this article. I hope you found it useful. Feel free to drop your comments below and like the post.