Deploy a Python Django website to Apache Server running on Ubuntu

There are few options to get your Python Django website running on a webserver. One option would be to run Django on an apache server set up on a Linux distribution like Ubuntu.

Tuedo#002

 by  Markus Felder

02
Feb 2021
 2

Preliminary note

Deploying your Python Django website on a server can be sometimes tricky, but to publish it on a Linux distribution (for example Ubuntu like in our case) offers a maximum of flexibility for the web developer. In recent years, deploying your website on a virtual private server (aka VPS hosting plan) such as Linode has become a standard in the industry.

There is little information on the web about this topic. A great exception is a Youtube tutorial by Corey Schafer1.

Most important prerequisites for this project

  • Ubuntu 20.0.4 (e.g. running on a Linode VPM)
  • Apache 2.4.41
  • Django 3.1.1
  • PostgreSQL 12.5
  • Sass 1.26.10
  • Bulma 0.9.0
  • Webpack 4.44.0

Github repository of this django website

First steps: Naming the host and creating a privileged user

We assume that Ubuntu is already installed on a VPM. We have chosen Linode, which offers quite affordable Linux server solutions that are optionally provided in Europe. It is best to open two bash shells, one for the local machine (with the Django application installed locally), the other to ssh to the remote server provided by your host.

With the SSH data provided by your host you should first ssh to your remote server as root:

# Local Machine
$ ssh root@192.xx.xxx.xx

After entering the correct password, you should be logged in as root to the web server. Now you are ready to update the server, the software and its dependencies.

# Web Server
root@localhost:~$ apt-get update && apt-get upgrade

The next step would be to give the web server a host name and add the IP address and the given name in the host file located in the /etc/ path of your linux distribution.

# Web Server
root@localhost:~$ hostnamectl set-hostname django.server
root@localhost:~$ nano /etc/hosts
127.0.0.1       localhost
192.xx.xxx.xx   django-server

# The following lines are desirable for IPv6 capable hosts
::1     localhost ip6-localhost ip6-loopback
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters

After saving the file it´s always recommended to create a privileged user with admin permissions in order to execute the following configuration on the web server. Now you can log out from root and log in as newly created admin user.

# Web Server
root@localhost:~$ adduser tuedodev
[...]
root@localhost:~$ adduser tuedodev sudo
[...]
root@localhost:~$ exit
[...]
$ ssh tuedodev@192.xx.xxx.xx

Setting up a SSH key based authentication

To streamline the authentication process it´s best practice to set up a SSH key based authentication. In the home folder of your web application you should create a new folder.

# Web Server
tuedodev@django-server:~$ mkdir -p ~/.ssh

Switching back to your bash terminal on your local computer you should now create a private and public SSH key. To increase safety feel free to enter a passphrase which is asked frequently by your machine. The public key generated on our local machine should be copied to the web server via SSH. Furthermore the permissions for the .ssh folder on the web server should be adjusted accordingly.

# Local Machine
$ ssh-keygen -b 4096
[...]
$ scp ~/.ssh/id_rsa.pub tuedodev@192.xx.xxx.xx:~/.ssh/authorized_keys
# Web Server
tuedodev@django-server:~$ sudo chmod 700 ~/.ssh/
[...]
tuedodev@django-server:~$ sudo chmod 600 ~/.ssh/*
tuedodev@django-server:~$ exit

After logging out you should be able to ssh to the web server without any password requests (if provided while generating the SSH keys the passphrase must of course be entered).

# Local Machine
$ ssh tuedodev@192.xx.xxx.xx

It is generally recommended to disable login within the SSH configuration for the user root and authentication with password altogether.

# Web Server
tuedodev@django-server:~$ sudo nano /etc/ssh/sshd_config

The following adjustments have to be made in the config file:

[...]
PermitRootLogin no
[...]
PasswordAuthentication no
[...]

After saving the config file you should restart the SSH service.

# Web Server
tuedodev@django-server:~$ sudo systemctl restart sshd

Installing the firewall

The next step should be to install the firewall first and then configure it. Be careful about the order of the steps, worst case would be if you block yourself not allowing SSH in the rules and enable them prematurely.

# Web Server
tuedodev@django-server:~$ sudo apt-get install ufw
[...]
tuedodev@django-server:~$ sudo ufw default allow outgoing
[...]
tuedodev@django-server:~$ sudo ufw default deny incoming

Now it´s important to allow SSH in the rules before we enable them. For test purposes we open port 8000 in the rules as well. Finally we enable the rules of the firewall ufw, an acronym that stands for uncomplicated firewall. The final command status displays the rules currently in force.

# Web Server
tuedodev@django-server:~$ sudo ufw allow ssh
[...]
tuedodev@django-server:~$ sudo ufw allow 8000
[...]
tuedodev@django-server:~$ sudo ufw enable
[...]
tuedodev@django-server:~$ sudo ufw status
Status: active

To                         Action      From
--                         ------      ----
22/tcp                     ALLOW       Anywhere
8000                       ALLOW       Anywhere
22/tcp (v6)                ALLOW       Anywhere (v6)
8000 (v6)                  ALLOW       Anywhere (v6)

Installing the PostgreSQL database on Ubuntu

For this website project we are not using the default database SQLite, but PostgreSQL. Of course, this still has to be set up and installed on Ubuntu.

# Web Server
tuedodev@django-server:~$ sudo apt-get install postgresql postgresql-contrib

With the first command you set the password for the pre-installed user postgres, with the second you can switch directly to the terminal as user postgres.

# Web Server
tuedodev@django-server:~$ sudo passwd postgres
[...]
tuedodev@django-server:~$ sudo -u postgres psql

On the PostgreSQL terminal, we first create the database for the website and a user who performs the database queries in production mode. You have to grant this very user all privileges. This sensitive data such as database, user name or password must of course be stored in the database area of settings.py or the corresponding environment variables. With the last command \q the terminal can be left again.

postgres-# CREATEDB tuedo-website;
[...]
postgres-# CREATE USER django_tuedo WITH PASSWORD '[...]';
[...]
postgres-# GRANT ALL PRIVILEGES ON tuedo-website to django_tuedo;
[...]
postgres-# \q

Finally, we restart the PostgreSQL server back inside the virtual environment. Using the second command is useful if you want to examine the system status of PostgreSQL.

# Web Server
tuedodev@django-server:~$ sudo service postgresql restart
[...]
tuedodev@django-server:~$ sudo systemctl status postgresql.service

Transfer the data to the web server

Now that we have completed the Linux configuration, it is time to transfer the data from the local machine to the web server. Traditionally there are several possibilities (FTP, Github), but in our case the transfer via SSH is the preferred option.

If not already done so, a Virtual Environment should now be set up on the local machine to list the dependencies for the installation on the web server in a text file (requirements.txt). This text file should be stored together with the Django project data in the root directory of the Django application and transferred to the server.

# Local Machine (Virtual Environment)
# Following command creates the current dependencies
# and stores them in requirements.txt
(venv) $ pip freeze > requirements.txt

Now we can transfer the data (more precisely the whole Django project folder) via ssh from the local computer to the home directory of the web server.2

# Local Machine (Virtual Environment)
(venv) $ scp -r tuedodotde tuedodev@192.xx.xxx.xx:~/

If everything worked fine, the Django project folder should be listed inside the user's home directory.

# Web Server
tuedodev@django-server:~$ ls
tuedodotde

Creating on Python the virtual environment for the Django application

In order to establish a virtual environment on the web server we need to install the Python installer and package manager pip as well as the programme venv, that creates virtual environments for Python (both versions for Python 3) .

# Web Server
tuedodev@django-server:~$ sudo apt-get install python3-pip
[...]
tuedodev@django-server:~$ sudo apt-get install python3-venv

Now we can create the virtual environment. Inside our Django project folder there will be generated a new folder that manages the virtual environment. The name of the folder is in our case venv.

# Web Server
tuedodev@django-server:~$ python3 -m venv tuedodotde/venv

After switching to the Django project folder, the virtual environment can now be activated.

# Web Server
tuedodev@django-server:~$ cd tuedodotde
tuedodev@django-server:~/tuedodotde$ source venv/bin/activate

Now, if the prompt brackets (venv) are displayed at the beginning, it´s a good sign, that the virtual environment was successfully activated. Now it´s time to install all dependencies listed in the text file requirements.txt. Depending on the dependencies and some version conflicts the installation process is not always going smoothly. In this case it´s highly recommended to install some dependencies manually.

# Web Server (Virtual Environment)
(venv) tuedodev@django-server:~/tuedodotde$ pip install -r requirements.txt

Adjust Django project configuration to production mode

Inside the settings.py file of the Django project we need to do some customization for the production mode. First let´s add the IP address of your web server to the allowed hosts. Later on you need to add there the domain name of your web application. On this occasion it should be ensured that the paths for static and media files, which are important for the production mode, are set correctly.

# settings.py
ALLOWED_HOSTS = ['192.xx.xxx.xx',]
[...]
STATIC_ROOT = os.path.join(BASE_DIR, 'static_root')
MEDIA_ROOT = os.path.join(BASE_DIR, 'media_root')

With the following command Python can now copy all static and media files into the corresponding folders for the production mode. This command should not be forgotten at a later time when changes are made to these files (especially CSS files).

# Web Server (Virtual Environment)
(venv) tuedodev@django-server:~/tuedodotde$ cd src
(venv) tuedodev@django-server:~/tuedodotde/src$ python manage.py collectstatic

Now you can start the web server on port 8000 like in the development mode. As you can remember we had set up the firewall accordingly.

# Web Server (Virtual Environment)
(venv) tuedodev@django-server:~/tuedodotde/src$ python manage.py runserver 0.0.0.0:8000

After entering the IP address and port in the address bar of the browser the website should render properly. You can see for yourself by testing the functionality of the website.

Figure 1 picture
Figure 1: Enter the IP address together with the port number after a kolon.

Installing Apache and WSGI

For production mode, the website still needs a reliable web server. There are two main options for web development: nginx and Apache. We will use Apache in the further course and also install WSGI, which is an acronym for Web Server Gateway Interface and acts as an interface between Apache and Python Django. First let´s install both Apache and WSGI in our virtual environment.

# Web Server (Virtual Environment)
(venv) tuedodev@django-server:~$ sudo apt-get install apache2
[...]
(venv) tuedodev@django-server:~$ sudo apt-get instal libapache2-mod-wsgi-py3
[...]

Let's now turn to the Apache server configuration in the Ubuntu configuration folder. Switching to the Apache subfolder sites-available we copy a default config file for our purposesand open it with a text editor.

# Web Server (Virtual Environment)
(venv) tuedodev@django-server:~$ cd /etc/apache2/sites-available/
[...]
(venv) tuedodev@django-server:/etc/apache2/sites-available$ sudo cp 000-default.conf django_project.conf
[...]
(venv) tuedodev@django-server:/etc/apache2/sites-available$ sudo nano django_project.conf

Now let us make some changes inside the newly created configuration file and add some data at the end of the config file (before the closing VirtualHost tag).

<VirtualHost *:80>
	[...]
	Alias /static /home/tuedodev/tuedodotde/static_root
        <Directory /home/tuedodev/tuedodotde/static_root>
                Require all granted
        </Directory>

        Alias /media /home/tuedodev/tuedodotde/media_root
        <Directory /home/tuedodev/tuedodotde/media_root>
                Require all granted
        </Directory>

        <Directory /home/tuedodev/tuedodotde/src/tuedo>
                <Files wsgi.py>
                        Require all granted
                </Files>
        </Directory>
	# Make sure to customize the following paths to the file structure on your web server
	WSGIScriptAlias / /home/tuedodev/tuedodotde/src/tuedo/wsgi.py
        
	# python-path: path to project / python-home: path to virtual environment
	WSGIDaemonProcess django_app python-path=/home/tuedodev/tuedodotde/src python-home=/home/tuedodev/tuedodotde/venv
        WSGIProcessGroup django_app

</VirtualHost>

Back in the Virtual Environment home directory, we now enable the Django site configuration file we just edited.>Back in the Virtual Environment home directory, we now enable the Django site configuration file we just edited and disable the default configuration file.

# Web Server (Virtual Environment)
(venv) tuedodev@django-server:~$ sudo a2ensite django_project.conf
[...]
(venv) tuedodev@django-server:~$ sudo a2dissite 000-default.conf
[...]

At the end of the Apache configuration we have to adjust some permissions for file folders and files.

# Web Server (Virtual Environment)
(venv) tuedodev@django-server:~$ sudo chown :www-data tuedodotde/
(venv) tuedodev@django-server:~$ sudo chown :www-data tuedodotde/static_root
(venv) tuedodev@django-server:~$ sudo chmode -R 775 tuedodotde/static_root
(venv) tuedodev@django-server:~$ sudo chown :www-data tuedodotde/media_root
(venv) tuedodev@django-server:~$ sudo chmode -R 775 tuedodotde/media_root

Handling the Environmental Variables in Apache

Confidential data should always be read from the settings.py configuration file using environment variables so that it is not accidentally released to the public when the Github repository is published. On the Apache server, customization can be a bit tricky, so for simplicity's sake we read in the environment variables in production mode via a JSON file, which in turn is stored securely in Ubuntu's configuration folder. So let´s first create a JSON file inside the etc folder and open it.

# Web Server (Virtual Environment)
(venv) tuedodev@django-server:~$ sudo touch /etc/env-production.json
(venv) tuedodev@django-server:~$ sudo nano /etc/env-production.json

We can now provide the JSON file with the confidential information such as SECRET_KEY or the access data for the database. They are normally stored in settings.py and are now read in using the JSON file.

{
    "DEBUG": "False",
    [...]
    "SECRET_KEY":"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
    "DB_ENGINE": "django.db.backends.postgresql",
    "DB_NAME": "tuedo-website",
    "DB_USER": "django_tuedo",
    "DB_PASSWORD": "xxxxxxxxxxxxxxxxxxxxxxxxxxxx",
    [...]
}

Now we have to modify the Django configuration file settings.py accordingly, so that the values are read from the JSON file.

(venv) tuedodev@django-server:~$ sudo nano tuedodotde/src/tuedo/settings.py

Below is an example of few values filled by environmental variables. Be careful with Boolean variables, because you don´t want to work with the string value of True or False in the config file settings.py.

"""
Django settings for tuedo project.

Generated by 'django-admin startproject' using Django 3.0.6.

For more information on this file, see
https://docs.djangoproject.com/en/3.0/topics/settings/

For the full list of settings and their values, see
https://docs.djangoproject.com/en/3.0/ref/settings/
"""

import os
import json

with open('/etc/env-production.json') as env_file:
        ENVIRONMENT = json.load(env_file)

[...]
SECRET_KEY = ENVIRONMENT.get('SECRET_KEY')
[...]
DEBUG = ENVIRONMENT.get('DEBUG') == 'True'
[...]
DATABASES = {
    'default': {
        'ENGINE': ENVIRONMENT.get('DB_ENGINE'),
        'NAME': ENVIRONMENT.get('DB_NAME'),
        'USER': ENVIRONMENT.get('DB_USER'),
        'PASSWORD': ENVIRONMENT.get('DB_PASSWORD'),
        'HOST': ENVIRONMENT.get('DB_HOST'),
        'PORT': ENVIRONMENT.get('DB_PORT'),
    }
}
[...]

Before we start the Apache server again, we close port 8000 in the firewall, which we opened earlier for testing purposes. We also allow http traffic.

# Web Server (Virtual Environment)
(venv) tuedodev@django-server:~$ sudo ufw delete allow 8000
[...]
(venv) tuedodev@django-server:~$ sudo ufw allow http/tcp
[...]
(venv) tuedodev@django-server:~$ sudo service apache2 restart

We did it! The website should now appear correctly after entering the IP address in the address bar of the browser (now without specifying the port).

Figure 2 picture
Figure 2: The IP address without the port number acts as a temporary domain name.

After we managed to deploy a Django application on a Linux Ubuntu server and Apache, we should still connect the IP address with a meaningful domain name and secure the website with a SSL certificate. But that will be the topic of another post.

  1. You can watch the Youtube video by Corey Schafer when you click on this link.
  2. In our case the project folder is called tuedodotde.

Add a comment

 

 

 

Comment by Daniel at 29.03.2023 on 18.40 h

There is a problem with DB section
For me this commands was work:
CREATE DATABASE your_database_name;
GRANT ALL PRIVILEGES ON DATABASE your_database_name TO your_username;

Comment by Konrad at 11.10.2022 on 19.43 h

there is one l missing in word install : sudo apt-get instal libapache2-mod-wsgi-py3

p.s. thx for helpful tuorial