It’s nice to develop an application with a functioning backend and a nice, flexible frontend on your machine. Still, if you want your application to be used publicly, you need to deploy the application to production. From this chapter to the last one, you will learn how to prepare the application we’ve built for deployment, deploy the backend on Amazon Web Services (AWS) and the frontend on Vercel, and finally, go through some security and performance optimizations.
In this chapter, we will learn deployment basics such as jargon and concepts to understand before going further. We will be learning about the following topics:
For this chapter, you will need to have Git installed on your machine. If you are on Linux or macOS, it will come by default. You can check its existence with the following command in the terminal:
git –version
Otherwise, feel free to download the right version at https://git-scm.com/downloads.
After the installation, let’s configure Git if not done yet. In a terminal, enter the following configuration commands to set the username (usually the username on your GitHub account) and the email address (usually the email address on your GitHub account):
git config --global user.name "username" git config --global user.email "[email protected]"
You will also need an active GitHub account. You can register on the official website at https://github.com/. As we will also be deploying the application on a remote AWS server, you will need an AWS account that can be created at https://portal.aws.amazon.com/billing/signup. If you don’t have an AWS account, you can still use any virtual private server (VPS) or virtual private cloud (VPC) you have online. However, this chapter will also document how to create a VPC instance using AWS and how to upload the code and serve the Django API.
Software deployment concerns all the activities that make a software system available to consumers. The term software deployment is also commonly described as application deployment. Following the best software deployment practices will ensure that all applications deployed operate smoothly and work as expected.
There are several benefits of software deployment, such as:
With software deployment defined, we will dive deeper into the tools and methods used for web application deployment.
Deploying a web application for production has drastically evolved over the years. From manual deployment to automated deployment techniques, web application deployment has advanced, making the process more secure, smooth, and as fast as possible. There are many tools for web application deployment, but in this book, we will focus on the automated tools and configure the Django project and the React project for automated deployments when pushes are made on the remote repository of the code.
But where will the code be pushed first? Let’s start describing and learning how to use the tools for our full stack application deployment, starting with Git and GitHub.
Git is a popular tool used for source code version control and collaboration. It not only helps the user keep track of changes made to the code but also allows developers to work through small or large code bases, with collaboration made easier. In the following subsections, we will initialize a Git repository in the backend project, commit the changes, and then push the changes to a remote repository on GitHub.
Open a new terminal in the directory where you created the Django project and enter the following command:
git init
This command will create an empty .git/ directory in the current directory: this is a Git repository. This repository tracks all changes made to files in the project, helping build a history of changes made, with details on the files changed, the name of the person making the changes, and much more information.
After the initialization, we will need to ignore some files in the project. We are talking about files such as .pycache, .env, and the virtual environment directories. After all, we don’t want important information such as secret environment variables to be available in the project or useless cache files to be present in the changes.
Inside the directory of the Django API, create a new file called .gitignore. This file tells Git which files and directories to ignore when tracking changes:
.gitignore
__pycache__ venv env .env
These files and directories in the preceding code will be ignored. Next, we will add the change in the directory to the staging area. The staging area allows you to group related changes before committing them to the project history. As we have successfully added a .gitignore file, we can freely run the git add command:
git add .
The dot (.) at the end of the command tells Git to only look for changed files in the current directory. To have a look at the changes to be committed to the Git history, run the following command:
git status
The git status command is used to show the state of the working directory and also the staging area. Using the command, you can see changes that are tracked or not. The following figure shows an example of the output you should have:
Figure 12.1 – Running the git status command
We can now run the git commit command. A commit is an operation that writes the latest changes of the source code to the version control system history. In our case, with git commit command will save the changes to the local repository:
git commit
The preceding command will prompt you to a text editor in the terminal or an app, depending on your system. Either way, you will need to enter a message. It’s important to enter a meaningful message because this message will be shown in the history of changes made to the source code. You can enter the following line if you want:
Initialize git in API project
After saving the message, you can check the Git history with the git log command:
git log
You will have something similar to the following figure:
Figure 12.2 – Writing a commit message
Important note
Writing meaningful commit messages is important, particularly in a team or a collaborative environment. You can read more about commit messages at https://www.conventionalcommits.org/en/v1.0.0/.
The project repository has been initialized locally; however, we want the code on GitHub. The next section will show you how to upload your code on GitHub.
GitHub is a code hosting platform for collaboration and version control. It helps developers around the world work together on projects and is actually the code hosting platform for the majority of popular open source projects.
On your GitHub account dashboard, on the navigation bar, create a new repository:
Figure 12.3 – Creating a repository on GitHub
Once it’s done, you will be redirected to a new page to enter basic information about the repository, such as the name of the repository and a description, stating if the repository is public or private, and adding a license or a .gitignore file. The repository name is required, and the other pieces of information are optional.
You can now create the repository, and you will have a similar page to this:
Figure 12.4 – Repository created
We have an existing repository, and we want to push it to the GitHub platform. Let’s follow the steps for …or push an existing repository from the command line. Inside the directory of your backend project, open a new terminal, and let’s enter the shell commands:
git remote add origin your_repository_git_url
The git remote command allows you to create, view, and delete connections to Git repositories hosted on the internet or another network. In the preceding command, we are adding a remote repository URL of the GitHub repository. Let’s change the name of the branch we are working on:
git branch -M main
By default, when a repository is created using Git on a local machine, the branch of work is called master. What is a branch in Git?
Well, it is just a separate version of the main repository. This allows multiple developers to work on the same project. For example, if you are working with a backend developer who wants to add support for file uploading on posts and comments, instead of working directly on the main branch, the developer can create a new branch (feature/images-post) from the main branch. After the work is done on this branch, the feature/images-post branch can be merged with the main branch.
With the main branch created, we can now push the changes to GitHub:
git push -u origin main
The git push command is used to upload local repository changes on the source code to a remote repository. In your case, the command will push the current code to your GitHub repository URL.
Reload the repository page on GitHub, and you will see something similar to this:
Figure 12.5 – Code pushed to the repository
And voilà! We have the code uploaded on GitHub. But this is just the code. What if you can have this running on a remote server that you can access from anywhere?
Let’s talk about platforms for web application deployment and deploy the Django backend on AWS.
With the complexity of software development increasing and more innovative and data-intensive applications evolving or being created every year, there has been an explosion of services to allow teams to deploy their products on the internet and scale them with ease. This has created a new kind of service called cloud computing: the on-demand delivery of IT resources over the internet with pay-as-you-go model pricing.
In this book, we will deploy the backend on AWS, mostly on an Elastic Compute Cloud (EC2) instance, which is just a fancy name for a VPS. Well, actually, an AWS EC2 instance is a virtual server in Amazon’s EC2 for running web applications. Let’s start by creating the AWS server.
Important note
The following steps can work for any VPS, not just for an AWS VPS. If you can’t create a VPS on AWS, you can see other solutions such as Linode, Google Cloud Platform (GCP), Azure, or IBM. They provide free credit you can use for learning about their services.
Follow these steps to create an EC2 instance:
Figure 12.6 – Accessing the EC2 console
Figure 12.7 – Creating an EC2 instance
You will be shown a page where you will have to configure the instance.
Figure 12.8 – Naming the EC2 instance
Figure 12.9 – Choosing an operating system on the EC2 instance
We are using Ubuntu here because of its security, versatility, and the policy of regular updates. However, feel free to use any other Linux distros you are familiar with.
Figure 12.10 – Launching the instance
Figure 12.11 – Instance created
Figure 12.12 – Connecting to an EC2 instance
This will redirect you to a page with the information and steps needed to connect via SSH:
Figure 12.13 – Connecting via SSH to an EC2 instance
ssh -i path/to/your_keypair.pem ec2-user@ipaddress
sudo apt update
sudo apt upgrade
The preceding commands update the apt packages index of Ubuntu packages and upgrade all packages on the server.
The Django project will run on port 8000 on the machine, so we have to allow a connection to this port. By default, EC2 instances will only allow connections on ports 80 for HTTP requests, 22 for SSH connections, and—sometimes—443 for Secure Sockets Layer (SSL) connections.
You can allow connections on port 8000 directly on the Details page of the created EC2 instance to access the Security tab on the list of tabs at the bottom of the page and click on the security setting group:
Figure 12.14 – Security tab
On the security group setting, access the Actions menu and click on Edit inbound rules. You will have access to a page where you can add a new rule, as follows:
Click on Save rules to save the changes and allow the EC2 instance to accept connections on port 8000:
Figure 12.15 – Adding a new security rule
The server is now ready for work, and we can now run the Django backend application. Let’s see the next steps in the following sections.
The source code for the Django project is hosted on GitHub. It’s definitely possible to directly use scp to copy the code from your machine to the remote machine but let’s go with Git, as it will be an important command of our workflow. On the terminal of the remote instance, enter the following command:
git clone your_repository_git_url
In my case, I am using the following repository for this project:
git clone https://github.com/PacktPublishing/Full-stack-Django-and-React.git –branch chap12
The git clone command is used to get a copy of an existing repository from a remote machine on the internet or another network. The –branch flag is used to denote a specific branch you want to clone.
Important note
As I am working using the repository of the project in this book, the current code and actions done are on the chap12 branch. In your case, if you are using your own repository, you may not have to use the –branch flag. Also, depending on if the GitHub repository is private or public, you will only enter your GitHub credentials if the repository is private.
The git clone command will clone the content of the project in a new directory. Enter the newly created directory and let’s start configuring the project. We will follow most of the steps done in Chapter 1, Creating a Django Project, until the creation of the Django project:
python3 -m venv venv
source venv/bin/activate
pip install -r requirements.txt
Great! The project is ready, but we need to configure a Postgres server to have the Django project running.
In Chapter 1 of the book, Creating a Django Project, we configured Postgres by directly installing an executable or building the source code. On the EC2 instance, we will directly use the apt tool to install the Postgres server. You can follow these steps to install the Postgres server on the EC2 machine:
sudo apt install postgresql-14
sudo su postgres
psql
CoreRoot/settings.py
... DATABASES = { 'default': { 'ENGINE': 'django.db.backends.postgresql_psycopg2', 'NAME': coredb, 'USER': 'core', 'PASSWORD': 'wCh29&HE&T83', 'HOST': 'localhost', 'PORT': '5342', } } ...
CREATE DATABASE coredb;
CREATE USER core WITH PASSWORD 'wCh29&HE&T83';
GRANT ALL PRIVILEGES ON DATABASE coredb TO core;
GRANT CREATE PRIVILEGE TO core;
And we are done with the creation of the database. Next, let’s connect this database to our Django project:
python manage.py migrate.
python manage.py runserver 0.0.0.0:8000
Figure 12.16 – DisallowedHost error
This is actually an error. This comes from the ALLOWED_HOSTS setting being empty. It is implemented by Django to prevent security vulnerabilities such as HTTP host header attacks. The ALLOWED_HOSTS setting contains a list of hostnames or domain names that Django can serve:
CoreRoot/settings.py
... ALLOWED_HOSTS = [] ...
vim CoreRoot/settings.py
Or, you can use the emacs or nano command. It’s up to you. The following line tells Django to accept requests from whatever is the hostname:
CoreRoot/settings.py
... ALLOWED_HOSTS = ["*"] ...
python manage.py runserver 0.0.0.0:8000
Figure 12.17 – Issues with DisallowedHost resolved
Great! The project is running fine on the internet, and you can even play with the API using an API client such as Postman or Insomnia. Congratulations! You have successfully deployed your Django application on an AWS EC2 machine.
However, we have a lot of issues (we can access debugging information directly on the internet, as in Figure 12.17), and we made some dangerous decisions such as not serving the API through HTTPS or not correctly setting allowed hosts throughout the deployment. Let’s explore these issues in the next section.
We have successfully deployed the Django backend on AWS. However, I decided to ignore some important and best practices for deployment so that we can have the Django server running ASAP. Let’s correct this. Let’s start with the errors that Django can show us. In the terminal of the project on the remote server, run the following command:
python manage.py check –deploy
Here’s the output of the preceding command:
System check identified some issues: WARNINGS: ?: (security.W004) You have not set a value for the SECURE_HSTS_SECONDS setting. If your entire site is served only over SSL, you may want to consider setting a value and enabling HTTP Strict Transport Security. Be sure to read the documentation first; enabling HSTS carelessly can cause serious, irreversible problems. ?: (security.W008) Your SECURE_SSL_REDIRECT setting is not set to True. Unless your site should be available over both SSL and non-SSL connections, you may want to either set this setting True or configure a load balancer or reverse-proxy server to redirect all connections to HTTPS. ?: (security.W009) Your SECRET_KEY has less than 50 characters, less than 5 unique characters, or it's prefixed with 'django-insecure-' indicating that it was generated automatically by Django. Please generate a long and random SECRET_KEY, otherwise many of Django's security-critical features will be vulnerable to attack. ?: (security.W012) SESSION_COOKIE_SECURE is not set to True. Using a secure-only session cookie makes it more difficult for network traffic sniffers to hijack user sessions. ?: (security.W016) You have 'django.middleware.csrf.CsrfViewMiddleware' in your MIDDLEWARE, but you have not set CSRF_COOKIE_SECURE to True. Using a secure-only CSRF cookie makes it more difficult for network traffic sniffers to steal the CSRF token. ?: (security.W018) You should not have DEBUG set to True in deployment. System check identified 6 issues (0 silenced).
That’s a lot of things. As we are building an API, let’s focus on the security issues that concern our API:
Those are mostly the errors that Django has detected. In the last section, Postgres configuration and deployment, we resolved the issue of the DisallowedHost error by having Django allow whichever hostname comes in a Host header. Well, this is actually bad because it can lead to an HTTP Host header attack, a technique used for web cache poisoning, poisoning links in the email, and modification of sensitive operations such as password reset.
Important note
You can read more about HTTP Host header attacks at https://www.invicti.com/web-vulnerability-scanner/vulnerabilities/http-header-injection/.
There are also some issues concerning the developer experience. It’s true that we have seen how to use Git and GitHub to host source code online, clone it on a remote server, and then configure it for deployment. You can repeat the same process, right? But what happens when you have to update the code for features or fixes multiple times per day? It can quickly become draining, so we need a solution for automated deployment on our EC2 server.
Also, we have Postgres and, finally, the Django project running separately. Sometimes, there might come a time when you will need to add another service to the machine. This can be done manually, but it creates an issue: the production environment starts to become different from the development environment.
It is an important habit to make sure that the development environment and the production environment are as similar as possible; this can make the reproduction of bugs easier but also the development of features predictable.
All these issues will be addressed in the next chapters. You will be introduced to environment variables, Docker, NGINX, and continuous integration/continuous deployment (CI/CD) concepts with GitHub Actions.
In this chapter, we have successfully deployed a Django application on an EC2 instance. Before deploying the Django application, we used Git to create a repository on a local machine, then created a remote repository on GitHub and pushed the changes online.
We have also learned how to configure a server for deployment manually with the installation of essential and interesting tools such as the Postgres server. We also explored the errors made when deploying the application and how we will address these errors in the following chapters.
These errors will be resolved in the next chapters, but first, we’ll learn more about environment variables and Docker in the next chapter.