Introduction
Django is undoubtedly one of the most popular and powerful framework / MVC today. This is a high-level Python web framework. Many popular and heavy websites like Instagram, Pinterest use django framework.
As a developer, I struggled initially on how to deploy a website in a Ubuntu server. After going through many tutorials, videos and how-to guides over the internet I compiled following steps to deploy a django website / application on server.
Video demo on how to deploy django application
Prerequisites
You need to have following
- Server with minimum 1vCPU, 1GB RAM and 20GB SSD
- Ubuntu 20.04 OS
- SSH access
- PUTTY ready to access the console / terminal of your server
- You should have sudo permissions
Legend
- example.com – You need to replace this with the domain name you use in your setup
- sleekproject – This is project directory, you can name this according to your project requirements
- sleekenv – This is virtual environment, you can name it as whatever you want to.
- sleeksoft – This is our django project, you can name this based on your project title etc..
Section 1 : Installing required packages
Before we dive into installing required packages, we need to update and upgrade the server and existing packages to the latest version.
sudo apt update -y
sudo apt upgrade -y
Install all the required packages in one command.
sudo apt install python3-pip python3-dev libpq-dev postgresql postgresql-contrib nginx curl file
List of packages installed are
- Pip (Python package installer)
- Python development files
- PostgreSQL and Libraries
- Nginx web server
- CURL
Section 2 : Creating Postgres user and database
Run following command to enter into Postgres environment. By default when Postgres is installed, we will get an user called ‘postgres’ with administrative rights in PostgreSQL. We use this user to create Database and User account in postgres for our project.
sudo -u postgres psql
Create a database for our project
CREATE DATABASE sleeksoft;
Note: Every Postgres statement MUST end with a semi-colon
Create a database user for our project. Make sure to select a secure password.
CREATE USER sleekuser WITH PASSWORD 's133ks0ft';
Note: Here database user is ‘sleekuser’ and password is ‘s133ks0ft’
Set the default encoding to UTF-8, which is recommended by django
ALTER ROLE sleekuser SET client_encoding TO 'utf8';
Set the default transaction isolation scheme to ‘read committed’. This blocks the reads from uncommitted transactions.
ALTER ROLE sleekuser SET default_transaction_isolation TO 'read committed';
Set the default timezone to UTC. This is the default timezone our django project too will have.
ALTER ROLE sleekuser SET timezone TO 'UTC';
Now, give our database user ‘sleekuser’ to have all the privileges in the database ‘sleeksoft’.
GRANT ALL PRIVILEGES ON DATABASE sleeksoft TO sleekuser;
Now we have successfully created a database and a database user who has all the privileges over the database we created in the PostgreSQL. Quit from Postgres environment.
\q
Section 3 : Creating Python Virtual Environment
Upgrade pip and install Python virtual environment.
sudo -H pip3 install --upgrade pip
sudo -H pip3 install virtualenv
Create a project folder where you can create virtual environment and install django, gunicorn etc.. for our project. In this tutorial, I am going to create the project folder inside /home/ directory.
cd /home
mkdir sleekproject
Navigate to the project folder / directory.
cd sleekproject/
Create virtual environment inside the project directory. I am naming the virutal environment as ‘sleekenv’.
virtualenv sleekenv
This will create a directory called ‘sleekenv
‘ within ‘sleekproject
‘ directory. Inside, it will install a local version of Python and a local version of pip
. We can use this to install and configure an isolated Python environment for our project. Before we install our project’s Python requirements, we need to activate the virtual environment.
source sleekenv/bin/activate
The terminal prompt should change to indicate that you are now operating within a Python virtual environment. It will look something like this
(sleekenv) root@host:/home/sleekproject#
With virtual environment activated, now we can install Django, Gunicorn and the PostgreSQL adapter (psycopg2) using pip. In virtual environment we can just use pip instead of pip3 no matter if you are using Python version 3.
pip install django gunicorn psycopg2-binary pillow
We now have all the softwares we needed to start our Django project.
Section 4 : Creating and configuring django project
Create django project called ‘sleeksoft’
django-admin startproject sleeksoft
Now you will see a new directory ‘sleeksoft’ inside our project folder. So the whole path will be /home/sleekproject/sleeksoft. Now we need to configure settings.py file in ‘sleeksoft’ directory.
Open settings.py and add ALLOWED_HOSTS to have the localhost and domain name. Change ‘example.com’ to your domain name.
ALLOWED_HOSTS = ['example.com', 'localhost']
Note: Be sure to include localhost
as one of the options since we will be using connections through a local Nginx server instance.
Update database details in settings.py. These settings will define the link between our django project and the PostgreSQL database we created. By default settings.py will have a Database details linking to SQLite, replace that with the following code.
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql_psycopg2',
'NAME': 'sleeksoft',
'USER': 'sleekuser',
'PASSWORD': 's133ks0ft',
'HOST': 'localhost',
'PORT': '',
}
}
Add / Change setting indicating where the static files should be placed. This is necessary so that Nginx can handle requests for these files. The following setting tells Django to place static files in a directory called static
in the base project directory.
STATIC_URL = '/static/'
STATIC_ROOT = BASE_DIR / 'static'
Let’s make the initial migration, which will setup the database tables etc.. You need to be inside the django project we created to run following commands /home/sleekproject/sleeksoft.
cd sleeksoft/
python3 manage.py makemigrations
python3 manage.py migrate
Now, create a superuser for our website.
python3 manage.py createsuperuser
You should enter username, email and password to create a superuser account. After this, lets do the collectstatic command which will collect all the static files in our website to /static/ folder. Remember we defined ‘/static/’ folder in settings.py.
python3 manage.py collectstatic
Create an exception for port 8000. So, we can test that django setup is installed properly.
sudo ufw allow 8000
Now, we can test our your project by starting up the Django development server with the following command
python3 manage.py runserver 0.0.0.0:8000
In the web browser, visit the server’s domain name or IP address followed by :8000
. Here 8000 is a port number.
http://server_domain_or_IP:8000
If all setup is done properly, you should see following in the browser.
We can login into the django admin by going to /admin/ from home page. There we can use the super user account we created in previous steps. After exploring the Django admin, use CTRL-C in the terminal window to shut down the development server.
Bind Gunicorn with our project
Run following command to bind Gunicorn with our project.
gunicorn --bind 0.0.0.0:8000 sleeksoft.wsgi
This will start Gunicorn on the same interface that the Django development server was running on. We can go back and test the website again. The only difference is we are not doing startserver command from Django, instead Gunicorn will take care of that.
Note: In some cases the admin interface will not have any styling applied since Gunicorn does not know how to find the static CSS content responsible for this. We will configure that in Nginx.
After finished testing, use CTRL-C in the terminal window to stop Gunicorn. We now finished configuring our Django application / Website. We can quit from the virtual environment using the following command.
deactivate
The virtual environment indicator in the command prompt will be removed.
Section 5 : Creating systemd Socket and Service Files for Gunicorn
We have tested that Gunicorn can interact with our Django application, but we should implement an easy and efficient way of starting and stopping the application server. To achieve this, we’ll make systemd service and socket files for Gunicorn.
Create and open a systemd socket file for Gunicorn in the following path.
sudo touch /etc/systemd/system/gunicorn.socket
gunicorn.socket file will be created in the above path, download via FTP and start adding the following code to it.
[Unit]
Description=gunicorn socket
[Socket]
ListenStream=/run/gunicorn.sock
# Our service won't need permissions for the socket, since it
# inherits the file descriptor by socket activation
# only the nginx daemon will need access to the socket
SocketUser=www-data
# Optionally restrict the socket permissions even more.
# SocketMode=600
[Install]
WantedBy=sockets.target
Save, close and upload the file via FTP when you are finished.
Next, create and open a systemd service file for Gunicorn with sudo privileges. The service filename should match the socket filename with the exception of the extension:
sudo touch /etc/systemd/system/gunicorn.service
gunicorn.service file will be created in the above path, download via FTP and start adding the following code to it.
[Unit]
Description=gunicorn daemon
Requires=gunicorn.socket
After=network.target
[Service]
Type=notify
# the specific user that our service will run as
User=root
# Group=someuser
# another option for an even more restricted service is
# DynamicUser=yes
# see http://0pointer.net/blog/dynamic-users-with-systemd.html
RuntimeDirectory=gunicorn
WorkingDirectory=/home/sleekproject/sleeksoft
ExecStart=/home/sleekproject/sleekenv/bin/gunicorn sleeksoft.wsgi
ExecReload=/bin/kill -s HUP $MAINPID
KillMode=mixed
TimeoutStopSec=5
PrivateTmp=true
[Install]
WantedBy=multi-user.target
Save, close and upload the file via FTP when you are finished.
Now, our systemd service file is complete. We can now start and enable the Gunicorn socket. This will create the socket file at /run/gunicorn.sock now and at boot. When a connection is made to that socket, systemd will automatically start the gunicorn.service to handle it.
sudo systemctl start gunicorn.socket
sudo systemctl enable gunicorn.socket
We can confirm that the operation was successful by checking for the socket file. Check the status of the process to find out whether it was able to start:
sudo systemctl status gunicorn.socket
Next, check for the existence of the gunicorn.sock file within the /run directory
file /run/gunicorn.sock
Output : /run/gunicorn.sock: socket
If the systemctl status command show errors or if you do not find the gunicorn.sock file in the directory, it’s an indication that the Gunicorn socket was not able to be created correctly. Check the Gunicorn socket’s logs using the following command
sudo journalctl -u gunicorn.socket
Testing Socket Activation
Currently, we have started the gunicorn.socket unit, the gunicorn.service will not be active yet because the socket has not received any connections. we can check this by using the following command
sudo systemctl status gunicorn
Output :
● gunicorn.service - gunicorn daemon
Loaded: loaded (/etc/systemd/system/gunicorn.service; disabled; vendor preset: enabled)
Active: inactive (dead)
To test the socket activation, we can send a connection to the socket through curl by using the following command
curl --unix-socket /run/gunicorn.sock localhost
You should see the HTML output from our django application in the terminal. This means that Gunicorn was started and was able to serve our Django application. We can verify that the Gunicorn service is running by using the following command
sudo systemctl status gunicorn
Output :
gunicorn.service - gunicorn daemon
Loaded: loaded (/etc/systemd/system/gunicorn.service; disabled; vendor preset: enabled)
Active: active (running) since Mon 2021-05-10 21:00:35 UTC; 4s ago
Main PID: 1157 (gunicorn)
Tasks: 4 (limit: 1153)
CGroup: /system.slice/gunicorn.service
├─1157 /home/sleekproject/sleekenv/bin/python3 /home/sleekproject/sleekenv/bin/gunicorn --access-logfile - --workers 3 --bind unix:/run/gunicorn.sock sleeksoft.wsgi:application
├─1178 /home/sleekproject/sleekenv/bin/python3 /home/sleekproject/sleekenv/bin/gunicorn --access-logfile - --workers 3 --bind unix:/run/gunicorn.sock sleeksoft.wsgi:application
├─1180 /home/sleekproject/sleekenv/bin/python3 /home/sleekproject/sleekenv/bin/gunicorn --access-logfile - --workers 3 --bind unix:/run/gunicorn.sock sleeksoft.wsgi:application
└─1181 /home/sleekproject/sleekenv/bin/python3 /home/sleekproject/sleekenv/bin/gunicorn --access-logfile - --workers 3 --bind unix:/run/gunicorn.sock sleeksoft.wsgi:application
May 10 21:00:35 django1 systemd[1]: Started gunicorn daemon.
May 10 21:00:35 django1 gunicorn[1157]: [2018-07-09 20:00:40 +0000] [1157] [INFO] Starting gunicorn 19.9.0
May 10 21:00:35 django1 gunicorn[1157]: [2018-07-09 20:00:40 +0000] [1157] [INFO] Listening at: unix:/run/gunicorn.sock (1157)
May 10 21:00:35 django1 gunicorn[1157]: [2018-07-09 20:00:40 +0000] [1157] [INFO] Using worker: sync
May 10 21:00:35 django1 gunicorn[1157]: [2018-07-09 20:00:40 +0000] [1178] [INFO] Booting worker with pid: 1178
May 10 21:00:35 django1 gunicorn[1157]: [2018-07-09 20:00:40 +0000] [1180] [INFO] Booting worker with pid: 1180
May 10 21:00:35 django1 gunicorn[1157]: [2018-07-09 20:00:40 +0000] [1181] [INFO] Booting worker with pid: 1181
May 10 21:00:36 django1 gunicorn[1157]: - - [10/May/2021:21:00:36 +0000] "GET / HTTP/1.1" 200 16348 "-" "curl/7.58.0"
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
Check the /etc/systemd/system/gunicorn.service file for problems. If we make changes to the /etc/systemd/system/gunicorn.service file, reload the daemon to reread the service definition and restart the Gunicorn process using the following command
sudo systemctl daemon-reload
sudo systemctl restart gunicorn
Section 6 : Configure Nginx web server
We need to configure Nginx to pass traffic to the Gunicorn process. We need to create and open a new server block in Nginx’s sites-available directory using the following command.
sudo touch /etc/nginx/sites-available/sleeksoft
Download this file via FTP and start editing. We will add following in this server block.
- Specify that this block should listen on the normal port 80 and that it should respond to our server’s domain name or IP address.
- Instruct Nginx to ignore any problems with finding a favicon.
- Instruct Nginx on where to find the static files that we collected in our
/sleekproject/static
directory. All of these files have a standard URI prefix of “/static”, so we can create a location block to match those requests
server {
server_name example.com;
location = /favicon.ico { access_log off; log_not_found off; }
location /static/ {
alias /home/sleekproject/sleeksoft/static/;
}
location / {
include proxy_params;
proxy_pass http://unix:/run/gunicorn.sock;
}
}
Save, close and upload the file via FTP when you are finished. Now, we can enable the file by linking it to the sites-enabled directory
sudo ln -s /etc/nginx/sites-available/sleeksoft /etc/nginx/sites-enabled
Test the Nginx configuration for syntax errors by using the following command
sudo nginx -t
If no errors are reported, then we can go ahead and restart Nginx by using the following command
sudo systemctl restart nginx
Now, we need to open up our firewall to normal traffic on port 80. Since we no longer need access to the development server, we can remove the rule to open port 8000 now.
sudo ufw delete allow 8000
sudo ufw allow 'Nginx Full'
We should now be able to go to our server’s domain or IP address to view our django application/website.
Section 7 : Certbot installtion for enabling SSL
Install snapd
sudo apt install snapd
Execute the following instructions on the command line on the machine to ensure that you have the latest version of snapd
sudo snap install core; sudo snap refresh core
Run this command on the command line on the machine to install Certbot.
sudo snap install --classic certbot
Execute the following instruction on the command line on the machine to ensure that the certbot command can be run.
sudo ln -s /snap/bin/certbot /usr/bin/certbot
Run this command to get a certificate and have Certbot edit your Nginx configuration automatically to serve it, turning on HTTPS access in a single step.
sudo certbot --nginx
When prompted enter the desired value. This will automatically create SSL certificates for the pointed domain.
Now reboot the server.
reboot
Credits:
- Digital Ocean
- Barath Prabu S