I was recently tasked with hosting a full-stack application on an apache server. I soon realized hosting both the frontend and backend on the same server would not be straightforward. With this in mind, I researched extensively. A few things came up. Consequently, using mod-wsgi helped me handle the Django deployment to apache seamlessly. The React deployment documentation directed my frontend deployment.

How did I do it?

We will be working on a Virtual Machine from the Google Cloud Platform running an Ubuntu 18.04 LTS OS. Any other server running this Ubuntu version would work just as well. So without further ado let’s get to it.

Note: This walkthrough will not be code centred. Deployment is what we are keen on, the code we’ll be using can be located here.

We will be working with a backend API made with the Django Rest Framework library. A frontend app that’ll be consuming the various endpoints made with React.

Glossary:

  • React -> React is a JavaScript library for building user interfaces
  • Django -> Django is a Python-based free and open-source web framework, which follows the model-template-view architectural pattern
  • mod-wsgi ->an Apache HTTP Server module that provides a WSGI compliant interface for hosting Python-based web applications under Apache
  • Apache -> An open-source cross-platform web server software, released under the terms of Apache License 2.0
  • REST -> Software architectural style that defines a set of constraints to be used for creating Web services.
  • Google Cloud Platform (GCP) -> A suite of cloud computing services that runs on the same infrastructure that Google uses internally for its end-user products
  • Virtual Machine Instance -> A Google Compute Engine Virtual Machine
  • Git -> Git is a distributed version control system for tracking changes in source code during software development.
  • GitHub -> GitHub is a global company that provides hosting for software development version control using Git
  • Google SDK -> The Google Cloud SDK is a set of command-line tools for developing with Google Cloud

Prerequisites:

  • A functional machine (Computer)
  • A trial GCP  account (Read through the terms of use here)
  • A Github Account
  • Git Software on your computer

Setting up the prerequisites:

For those that are new to most of what has been outlined in the glossary. You’ll need to install certain software on your machine and signup for a couple of accounts before we begin. To install Git go here, Github account here, Google Cloud Platform Account here, Cloud SDK here. When done with that setup we can proceed.

Packaging your software and shipping to GitHub

We want our code to be on a Google Compute Engine Virtual Machine to be able to proceed. Thus we will package it and host it on Github to both track our code and access it on GCP.
Consolidate both the frontend and backend code containing folders in a single folder, I will name mine ldeproject.

From the terminal proceed to move into the folder and initialize it as a git repository by running.

git init

Your terminal should indicate that you are now in the master branch of the repository. We will then proceed to GitHub and create a repository so that we can push our local changes to it.

Create a repo from GitHub:

Fill in the name of the repository. Noteworthy you can leave the remaining fields as defaults or add your own preferences. Finally, create the repository.

Click on the clone link of your repository and copy it to your clipboard.


On copying the repo link proceed to your terminal and cd into your local repository. From the terminal type this command in order to set your origin to the remote GitHub repo you just created.

git remote add origin [your cloned link]

On setting the remote you can now proceed to stage your code, commit and eventually push it upstream.

Proceed to type:

git add .

This command stages all your code changes and now we can proceed to commit the changes.

git commit -m "[a message on changes made to the code]"

Finally, we push the code to GitHub.

git push origin master

We can now proceed to the exciting part of things. Proceed past this point after setting up a GCP account. 

Now we move to GCP

On the GCP console, we will create a Virtual Machine Instance from the Google Compute Engine. Click the hamburger button on the top left of the screen which will open a sidebar with various GCP features. We are concerned with the Google Compute Engine and more specifically VM instances.

Note: To avoid incurring costs after the free tier account expires read through the terms of usage.

Let’s create a VM instance, click on the create instance button, if its the first time this will take a while. You will be presented with a form providing specifics on the kind of virtual machine you prefer. Although you may proceed to fill the form based on your needs there are two key areas I would like to draw to your attention. To follow along with me the Boot Disk OS needs to be an Ubuntu 18.04 LTS. On the Firewall section, you’ll also need to tick the Allow HTTP traffic flag. Finally proceed to create the VM, on creation you should be presented with a list of your VMs.

From this point we will move to the terminal, if you installed the SDK (which I’ll also be using) you’ll need some extra steps to connect to your VM from your local terminal. Alternatively, you can click on the SSH button and use the Cloud Shell provided by google without any further steps. For those using the SDK follow this official guide on how to connect to the VM from your local terminal.

Installing the dependencies

The Ubuntu OS we are running comes as would a new installation of Ubuntu on your machine, with an added advantage, it has on it preinstalled git and the version 3 of python, at the time of writing this blog the specific python version was python 3.6.9 and that’s what we’ll use (note: you have to run python commands with the python3 syntax to use the Python 3.6 runtime). We will install pip, apache and mod-wsgi before proceeding. The libapache2-mod-wsgi-py3 package saves us the strain of having to manually configure mod-wsgi to function with apache.

sudo apt-get update
sudo apt-get install python3-pip apache2 libapache2-mod-wsgi-py3

To test that the apache2 server has been installed correctly click on the External IP address on your VM instance and you should be redirected to the Apache Ubuntu Default Page

After the dependencies have been successfully installed we continue to clone our program files to our VM. Clone the repository by running:

git clone [your-repository-link]

This will clone the folder with both our projects to the VM, we will begin with the backend and work our way to the frontend.

Django API (BackEnd)

Before we can test out our apache + mod-wsgi marriage. We want to test our API first before deploying, so in good python fashion, we will create an isolated python runtime environment using virtualenv from where we will run our Django code.  Move into your Django app root folder and type the following command (virtualenv package should be available by default but if this is not the case you can use the pip command to install it).

virtualenv venv

The above command creates a folder labelled venv which contains our python runtime, we activate the environment by typing:

source venv/bin/activate

Your terminal should be preceded by (venv) which is indicative of the successful activation of the runtime environment. We proceed to install our Django app dependencies, I have a requirements.txt file and will use it to install all dependencies with one command

pip install -r requirements.txt

These are required dependencies in running our Django application.

By default Django only allows localhost as the only host for our application, since we are not working on a localhost perse we need to add our DOMAIN/IP ADDRESS to the allowed hosts for proper functionality.

To edit the settings file we use the built-in nano editor

nano todoproject/settings.py

Add your IP/DOMAIN to ALLOWED_HOSTS, (add the IP address only, don’t include the http://)

ALLOWED_HOSTS = [
'xx.xxx.xxx.xxx'
]

We can now proceed to run our migrations

./manage.py makemigrations
./manage.py migrate

Finally we serve our application using

./manage.py runserver 0.0.0.0:3389

I used 3389 because it was an available port on my VM network settings, you can also look up the available ports on yours from the Network Interfaces section of your VM, alternatively, you can configure the firewall rules in your VPC to allow the use of Django’s default 8000 port on TCP:

You can click on your External IP to see your running application (remember to append the port)

Now we deploy to Apache using mod-wsgi

Since we are done testing the API we will disable the virtual environment by running.

deactivate

We start off by configuring our Apache server aided by mod-wsgi to serve our Django app as directed by the official Django documentation. This will help translate the client connections we make to the API into WSGI format (which Django officially uses) this is why we need the mod-wsgi module when deploying to apache.

To configure the WSGI pass, we edit the default virtual host file. Run the following command to open the 000-default.conf file using the nano editor:

sudo nano /etc/apache2/sites-available/000-default.conf

We want to grant access to the wsgi.py in our project, to do this we add the following lines to the 000-default.conf file:

<Directory /home/[user]/ldeproject/djangoapache/todoproject>
        <Files wsgi.py>
            Require all granted
        </Files>
</Directory>

On completing the setup we proceed to include the portion that will handle the WSGI pass. While at it we will also include the python path (venv) we used earlier in our test as the runtime to be used during deployment. We will also need a process group.

Add this to the same file:

WSGIDaemonProcess djangoapache python-home=/home/[user]/ldeproject/djangoapache/venv python-path=/home/[user]/ldeproject/djangoapache
WSGIProcessGroup djangoapache
WSGIScriptAlias /api /home/[user]/ldeproject/djangoapache/todoproject/wsgi.py

After all the edits your 000-default.conf should resemble:


<Directory /home/[user]/ldeproject/djangoapache/todoproject>
        <Files wsgi.py>
            Require all granted
        </Files>
</Directory>

WSGIDaemonProcess djangoapache python-home=/home/[user]/ldeproject/djangoapache/venv python-path=/home/[user]/ldeproject/djangoapache
WSGIProcessGroup djangoapache
WSGIScriptAlias /api /home/[user]/ldeproject/djangoapache/todoproject/wsgi.py

You will notice that we included a /api on the WSGIScriptAlias from where the API is to be served as the BASE_URL, this is by design we intend for our frontend to be served from the BASE_URL i.e (www.domain.com), there might be more efficient ways of doing this i.e., by creating new virtual hosts but for the purview of this tutorial we will stick to the base_url/api for the API base URL and the default base_url for the frontend.

Permissions

We need to grant some folder permissions to our project to enable certain accesses that are denied by default.

chmod 664 ~/ldeproject/djangoapache/db.sqlite3

The above command gives write permissions to our db.sqlite3 database so that we can be able to add new records as well as edit existing ones therein.

sudo chown :www-data ~/ldeproject/djangoapache/db.sqlite3

The above command gives ownership rights to the www-data group, which happens to be the group Apache runs under, thus giving Apache access to our SQLite DB.

However, to be able to write to the DB itself we also need to give ownership rights to the same group (www:data) over the DB’s folder parent. We achieve this by running:

sudo chown :www-data ~/ldeproject/djangoapache

Now restart the Apache server for the changes we have made to take effect, for that we run:

sudo systemctl restart apache2 

You can test your API by going to your DOMAIN/EXTERNAL IP and appending /api and the API should be served as expected.

That’s as far as the backend goes. Or is it?

What about the CORS error?

As I was trying to deploy the frontend I realized that a certain error was being raised that concerned something referred to as CORS you can read more about it here, to get around this I used a Django package called django-cors-headers, so lets set it up.

Activate the virtual environment and install django-cors-headers.

source venv/bin/activate && pip install django-cors-headers

Add this to settings.py 

Installed apps:

INSTALLED_APPS = [
    ...
    'corsheaders',
   ...
]

Middleware:

Note: CorsMiddleware should be placed as high as possible, especially before any middleware that can generate responses such as Django’s CommonMiddleware or Whitenoise’s WhiteNoiseMiddleware. If it is not before, it will not be able to add the CORS headers to these responses.

MIDDLEWARE = [  # Or MIDDLEWARE_CLASSES on Django < 1.10
    ...
    'corsheaders.middleware.CorsMiddleware',
    'django.middleware.common.CommonMiddleware',
    ...
]

Whitelist our frontend Base URL

CORS_ORIGIN_WHITELIST = [
    "http://35.224.112.224/",
]

We can now proceed to deploy our React App.

React App (FrontEnd)

We move into the react folder and install node(a javascript runtime environment) before we can install the dependencies in our package.json. Run this:

sudo apt-get install nodejs
sudo apt-get install npm

Note: The above commands might not give you the most recent version of npm and this would throw an error on attempting to install our dependencies if you run across such an error run the following:

sudo npm install -g npm@latest

Let’s proceed to install the dependencies in our file by running

sudo npm i

Before we can run the build for our application we need to include a .htaccess (contains extra Apache configurations) file in our public folder in the react app, run the following command:

nano public/.htaccess

When the editor opens up paste this in the file:

Options -MultiViews
   RewriteEngine On
   RewriteCond %{REQUEST_FILENAME} !-f
   RewriteRule ^ index.html [QSA,L]

Proceed to save and close the file. Now we build our react app by running:

sudo npm run build

The above command bundles our app and minifies our assets (css/js files) and packages everything into a folder labelled build. We copy everything in this folder to our server folder located /var/www/html which is where Apache will read the index file that serves our frontend app, you will notice that there already exist an index.html file in this folder which is the default page we saw while testing out our Apache install, we will remove this file and replace it with the one in our build folder.

[sudo] rm /var/www/html/index.html

We can now proceed to copy all the files from our build folder to /var/www/html by running:

sudo cp -r ~/ldeproject/reactapache/apachereactapp/build/* /var/www/html/

Let’s restart our Apache server for the changes to take effect:

sudo systemctl restart apache2

Now upon visiting your External IP address, you should see your content being displayed as expected.

Learning Strategy

In learning how to perform this deployment I relied heavily on official documentation for both the Django API and react app. It also made the challenge much easier by breaking down the challenge into atomic bits that I could be able to solve independently eventually compiling them into a single solution. The most challenging bit was the Apache configuration mainly because this was the first time I’d interacted with XML but it became more manageable once I understood the underlying concept.

In retrospect

Looking back at the entire process I can draw a couple of lessons but the one that stands out would be the importance of planning through the various stages of your app’s development from development all the way to deployment before writing the first line of code. This helps you visualize the process more clearly and educates you on the proper technologies to implement, for both efficiency and simplicity. Over-engineering a solution I realized makes maintenance a burden on your successors.

In Conclusion

The process took longer than expected, having an estimate of 7 hours to perform both the backend and frontend development proved insufficient, it took approximately half an hour more than expected. This was mainly due to extra research on using mod-wsgi with apache.

This is a very simple version of a production deployment but it sets the basics of what would be expected if a larger application were involved. It would be advisable to use the Google App Engine on the Google Cloud Platform in the case of application deployment, which would make the process more hands-off, but it would deny you as much liberty as you would have if you deployed it on the Google Compute Engine’s VM instance. Do your research before acting.

As a challenge, you can follow this tutorial on Flask and React to practice what we just learnt. Good luck and happy DevOping!

Learning Tools

React deployment on Apache

Django deployment on Apache using mod-wsgi.

Featured image is courtesy of pixabay