Deploying Django-Nginx app in AWS ec2
Learn how to seamlessly deploy a robust Django application with Nginx on AWS EC2 to scale your web presence and boost performance.
Deploying Django-Nginx app in AWS ec2 🚀
In this tutorial, we will learn how to deploy a Django application with Nginx on AWS EC2. We will also configure the PostgreSQL database and Gunicorn to serve the Django application. We will also set up the static files and media files to be served by Nginx. We will also configure the SSL certificate for the domain using Certbot.
Prerequisites 📋
Before we start, make sure you have the following prerequisites:
Step 1: Launch an EC2 instance 🖥️
First, we need to launch an EC2 instance. You can follow the steps below to launch an EC2 instance:
Step 2: Connect to the EC2 instance 🔌
Once the instance is active, you can first connect to it. You can follow the steps below to connect to the EC2 instance. In the instance details, you will find the "Connect" button. Click on it and follow the instructions to connect to the instance using SSH. Before that make sure you have the key pair to connect to the instance. Also, make sure to change the permission of the key pair file using the following command:
chmod 400 key-pair.pemTo connect to the instance using they keypair and the public IP of the instance, use the following command:
ssh -i "key-pair.pem" user@public-ipStep 3: Install Docker and Docker Compose 🐳
The easiest way to deploy a Django application with Nginx is to use Docker and Docker Compose. First, we need to install Docker and Docker Compose on the EC2 instance. You can follow the steps below to install Docker and Docker Compose:
sudo apt updatecurl -fsSL https://get.docker.com -o get-docker.sh
sudo sh get-docker.shsudo usermod -aG docker $USERdocker --versionAs output, you should see the version of Docker installed.
Step 4: Pull the git repository and configure the Django application 🛠️
Now in this step we need to pull in your Django application from the git repository. You can use the following command to clone the repository:
git clone <your-repository-url>This is the time to really configure the Django application with the settings for the production environment.
Create Dockerfile
Create a file named Dockerfile in the root of the project and add the following content:
FROM python:3.11
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
&& rm -rf /var/lib/apt/lists/*
RUN mkdir -p /www/app/staticfiles && \
mkdir -p /www/app/media
ENV PYTHONUNBUFFERED 1
ENV PYTHONDONTWRITEBYTECODE=1
WORKDIR /usr/src/app
COPY requirements.txt /usr/src/app/requirements.txt
RUN pip install --upgrade pip \
&& pip install -r /usr/src/app/requirements.txt
## entry point bash script
COPY ./entrypoint.sh /usr/src/app/entrypoint.sh
RUN chmod +x /usr/src/app/entrypoint.sh
## start the entrypoint bash script
ENTRYPOINT ["sh", "/usr/src/app/entrypoint.sh"]From the above Dockerfile, we have used ENTRYPOINT to run the entrypoint.sh script. We will create this script in the next step.
Create entrypoint.sh
Create a file named entrypoint.sh in the root of the project and add the following content:
#!/bin/bash
# This script is used to start the container for django development
APP_PORT=${PORT:-8000}
## make migrations and migrate for postgres
python3 manage.py makemigrations
python3 manage.py migrate
python3 manage.py collectstatic --noinput
# using gunicorn server
gunicorn backend.wsgi:application --bind "0.0.0.0:${APP_PORT}"In the above script, we have used
gunicornto serve the Django application. We have also usedcollectstaticto collect the static files. So in the next step let's configure static files and media files to be served by Nginx.
Static files and media files
In the settings.py file, add the following settings to configure the static files and media files:
MEDIA_URL = "/media/"
MEDIA_ROOT = "/www/app/media/"
STATIC_ROOT = "/www/app/staticfiles/"
STATIC_URL = "/static/"
STATICFILES_DIRS = [BASE_DIR / "static"]With the above settings, we have declared the STATIC_ROOT and MEDIA_ROOT to be served by Nginx.
Step 5: Create a Docker Compose file 📝
Create a file named docker-compose.yml in the root of the project and add the following content:
version: '3.8'
services:
web:
container_name: django
build:
context: ./backend
dockerfile: Dockerfile
volumes:
- static:/www/app/staticfiles:rw
- media:/www/app/media:rw
- ./backend:/usr/src/app:rw
ports:
- '8842:8000'
restart: always
networks:
- mainIn the above docker-compose.yml file, we have defined a service named web which will build the Dockerfile from the backend directory. We have also defined the volumes for the static files and media files. We have also defined the port to be exposed. Also, we have defined the network to be used which will come in handy when we configure Nginx.
Step 6: Configure Nginx 🛠️
Dockerfile for Nginx
Create a file named Dockerfile in the root of the project and add the following content:
FROM nginx:latest
# Correct syntax for installing procps
RUN apt-get update && apt-get install -y procps
RUN mkdir -p /home/app/staticfiles
RUN mkdir -p /home/app/mediaIn the above Dockerfile, we have used the
nginximage and created the directories for the static files and media files.
In the docker-compose.yml file, add the following service for Nginx:
nginx:
container_name: nginx
build:
context: ./nginx
dockerfile: Dockerfile
volumes:
- static:/home/app/staticfiles:rw
- media:/home/app/media:rw
- ./nginx:/etc/nginx/conf.d:rw
ports:
- '80:80'
restart: always
networks:
- main
depends_on:
- webIn the above docker-compose.yml file, we have defined a service named nginx which will build the Dockerfile from the nginx directory. We have also used the same volumes for the static files and media files because this way Nginx will be able to serve the static files and media files.
We can't just have the nginx empty. Let's create a directory named nginx in the root of the project. Inside the nginx directory, create a file named nginx.conf and add the following content:
upstream django {
server web:8000;
}
# Server block to handle HTTPS
server {
listen 80;
server_name .domainname.example;
# Main location block
location / {
proxy_pass http://django;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_http_version 1.1;
## additional
proxy_redirect off;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
# Static files
location /static/ {
alias /home/app/staticfiles/;
}
# Media files
location /media/ {
alias /home/app/media/;
}
}Breakdown of the Nginx configuration
We have defined an upstream block to define the server block for the Django application. Notice that we have used the web service name which we defined in the docker-compose.yml file. It is important to use the service name because we are using the same network for the services. And docker container can recognize the service name.
We have defined the server block to handle the HTTP requests. We have defined the server_name to be the domain name. We have also defined the location block to handle the requests. We have used the proxy_pass to pass the requests to the Django application. We have also defined the location block to serve the static files and media files.
We are almost done. We need to update the settings.py file to use the domain name for the ALLOWED_HOSTS setting, CORS_ALLOWED_ORIGINS setting, and CSRF_TRUSTED_ORIGINS setting and some security settings.
Update the settings.py file for the production environment
ALLOWED_HOSTS = ['domainname.example']
CORS_ALLOW_CREDENTIALS = True
CORS_ORIGIN_WHITELIST = [
"http://localhost:3000",
"http://clientdomain.example",
]
CORS_ORIGIN_ALLOW_ALL = False
# Allow specific methods
CORS_ALLOW_METHODS = [
"DELETE",
"GET",
"OPTIONS",
"PATCH",
"POST",
"PUT",
]So what is happening here in the settings.py file?
We have defined the ALLOWED_HOSTS to be the domain name. This is important because Django will only serve the requests from the domain name.
We have defined the CORS_ALLOW_CREDENTIALS to be True because we are using the CORS package to handle the cross-origin requests.
We have defined the CORS_ORIGIN_WHITELIST to allow the requests from the client domain. We have also defined the CORS_ORIGIN_ALLOW_ALL to be False because we want to allow the requests from the specific domains.
We have defined the CORS_ALLOW_METHODS to allow the specific methods.
Next, setup the security settings:
# Security settings
# Redirect all non-HTTPS traffic to HTTPS
SECURE_SSL_REDIRECT = True
# Use secure session and CSRF cookies in production
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True
# Set the HttpOnly flag on the session cookie
SESSION_COOKIE_HTTPONLY = True
# Define the header that your load balancer or reverse proxy uses to indicate a secure connection
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
# HTTP Strict Transport Security settings
SECURE_HSTS_SECONDS = 31536000 # One year
SECURE_HSTS_INCLUDE_SUBDOMAINS = True
SECURE_HSTS_PRELOAD = True
# Prevent client-side JavaScript from accessing the CSRF cookie
CSRF_COOKIE_HTTPONLY = True
CSRF_TRUSTED_ORIGINS = [
"http://localhost:3000",
"http://clientdomain.example",
]
# Ensure your site will only be served over HTTPS and not embedded in a frame
X_FRAME_OPTIONS = 'DENY'Breakdown of the security settings
We have defined the SECURE_SSL_REDIRECT to be True to redirect all non-HTTPS traffic to HTTPS.
We have defined the SESSION_COOKIE_SECURE and CSRF_COOKIE_SECURE to be True to use secure session and CSRF cookies in production.
We have defined the SESSION_COOKIE_HTTPONLY to be True to set the HttpOnly flag on the session cookie.
We have defined the SECURE_PROXY_SSL_HEADER to indicate a secure connection.
We have defined the SECURE_HSTS_SECONDS to be 31536000 to use HTTP Strict Transport Security settings. This means that the browser will remember the HSTS settings for one year. HSTS is a web security policy mechanism that helps to protect websites._createMdxContent
We have defined the CSRF_COOKIE_HTTPONLY to be True to prevent client-side JavaScript from accessing the CSRF cookie.
We have defined the CSRF_TRUSTED_ORIGINS to allow the requests from the specific domains. This is important because we are using the CSRF protection.
We have defined the X_FRAME_OPTIONS to be DENY to ensure your site will only be served over HTTPS and not embedded in a frame. This is important to prevent clickjacking attacks.
Step 7: Setup the SSL certificate using Certbot 🔒
To setup the SSL certificate, we need to install Certbot on the EC2 instance. Certbot uses letsencrypt to provide the SSL certificate.
You can follow the steps below to install Certbot:
A extra step in AWS EC2
The current EC2 instance, let's say I stopped it. Then the next time I start it, the public IP will change. So to avoid this, we can use an Elastic IP. An Elastic IP is a static IP address designed for dynamic cloud computing. An Elastic IP address is associated with your AWS account. With an Elastic IP address, you can mask the failure of an instance or software by rapidly remapping the address to another instance in your account.
Now we have the Elastic IP address associated with the instance. We can use this IP address to point the domain name to the instance.
Install Certbot
sudo apt install certbot python3-certbot-nginxsudo certbot --nginx -d domainname.examplesudo certbot renew --dry-runNow we have the SSL certificate configured for the domain name. We can use the domain name to access the Django application. The saved SSL certificates are located in the
/etc/letsencrypt/live/domainname.exampledirectory. We just need to mount the directory to the Nginx service in thedocker-compose.ymlfile.
So the complete docker-compose.yml file will look like this:
version: '3.8'
services:
web:
container_name: django
build:
context: ./backend
dockerfile: Dockerfile
volumes:
- static:/www/app/staticfiles:rw
- media:/www/app/media:rw
- ./backend:/usr/src/app:rw
ports:
- '8842:8000'
restart: always
networks:
- main
nginx:
container_name: nginx_hci
build:
context: ./nginx/
volumes:
- static:/home/app/staticfiles:rw
- media:/home/app/media:rw
- ./nginx/nginx.conf:/etc/nginx/conf.d/default.conf
- /etc/letsencrypt:/etc/letsencrypt:ro
ports:
- 80:80
- 443:443
depends_on:
- web
restart: always
networks:
- main
volumes:
static:
media:
networks:
main:/etc/letsencrypt directory to the Nginx service. This way Nginx will be able to use the SSL certificates. And also we have defined the ports 80 and 443 to be exposed.The final update in the nginx.conf file will look like this:
upstream django {
server web:8000;
}
# Server block to handle HTTPS
server {
listen 80;
server_name .domainname.example;
# Main location block
location / {
proxy_pass http://django;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_http_version 1.1;
## additional
proxy_redirect off;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
# Static files
location /static/ {
alias /home/app/staticfiles/;
}
# Media files
location /media/ {
alias /home/app/media/;
}
listen 443 ssl;
# SSL certificate and key locations ## I have to update this
ssl_certificate /etc/letsencrypt/live/domainname.example/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/domainname.example/privkey.pem;
}
listen 443 ssl to handle the HTTPS requests. We have also defined the ssl_certificate and ssl_certificate_key to use the SSL certificates. This would be the final configuration for the Nginx service. Based on your application you can extend the configuration.Step 8: Start the services 🚀
Now we have everything configured. The most easiest step now is to build and start the service 😂
docker-compose up --build -dThis will build the services and start the services in the background. You can access the Django application using the domain name. You can also access the Django admin using the domain name followed by /admin.
Congratulations! You have successfully deployed a Django application with Nginx on AWS EC2. You could also use the same steps to deploy the Django application with Nginx on other cloud platforms like Google Cloud, Azure, and Digital Ocean.
And also you can configure nginx as load balancer and orchestrate multiple docker containers to scale the application.
Conclusion 🎉
In this blog post, we have learned to the following:
I hope you enjoyed this tutorial and found it helpful.