Deploying Django to Google Cloud Platform - Compute Engine

In the past, whenever I wanted to launch a Django website on a Linux server, I would follow the steps outlined in the famous Corey Shafer's tutorial on launching a Django Application on Linode.com. However, recently I've had to launch a Django application on the Google Cloud Platform, and even though most of the steps are the same, I had to do a lot of trial and error to figure out the exact steps required to achieve the same. And this is what I will show you in this tutorial.

Deploying Django to Google Cloud Platform - Compute Engine

If you are unfamiliar with Google Cloud Platform (GCP), you may be interested to know that GCP offers a free $300 credit when you first sign up. You can use this credit on any of the many Cloud computing services offered by them, but if you simply want to host a website, you can easily host almost any kind of website using this credit on their Compute Engine platform - without spending any money at all. With the range of options that GCP offers, this is definitely a really appealing option.

A bit of a disclaimer before we begin, we will be using the Comptue Engine platform in this tutorial, but there are actually other methods of deploying a Django Project on GCP, including one by using App Engine, which may actually be a better method than what is outlined here. However, I faced several issues when I tried it and just had a really hard time with it.

Hence, I decided to stick to what I was most familiar with, which was to rent a Virtual Machine using the Compute Engine platform and use that to host a website built with Django. This is the process that I will discuss in detail in this tutorial.

So I encourage you to do your own research to figure out if this is the best approach for you.

I have divided the tutorial in two parts. In Part 1 (this one), I will show you how to deploy a simple Django project on a GCP Compute Engine instance, which will be accessible through an IP address. In Part 2, I will show how to point a custom Domain name to this GCP instance, and do things like setting up an SSL certificate so you have "Https" instead of "Http"; and also redirect "non-www" to "www" version of the website.

As mentioned above, Corey Schafer's tutorial is an inspiration to this one, and I have tried to achieve similar things as were achieved in that tutorial.

Following are the 6 main sections of this article:

  1. Creating a Compute Engine instance
  2. Connecting to the instance
  3. Setting up the project files on the server
  4. Installing Apache & Mod WSGI
  5. Setting File Permissions
  6. Finishing Up

1. Creating a Compute Engine instance

The first step is to create an account at Google Cloud Platform and click "Console". Then from the menu on the left, go to Compute Engine > VM Instances. Then click Create Instance.

Creating an Instance

Set a name for your instance. Select a region and your Machine type:

Entering details of the machine

You can see that I've named my instance "django-project". I've selected my region as "us-central1 (Iowa)", and my Machine type as "e2-medium".

You also need to select your operating system and your Disk Size. Also, make sure to Allow Http and Https Traffic:

Choose OS and Disk Size

I've chosen "Debian 10" as my operating system and a disk size of 100GB.

Now there is only one step left before you hit the "Create" button, which is to set up SSH keys that will allow you to connect to the server without a password.

Setting Up SSH

For this step, I'm going to use PuttyGen, which comes along with Putty, which is also something we will use later to SSH into the machine. So please go ahead and download it.

Open PuttyGen, set the bit rate as 4096, and hit Generate. You will have to move your mouse randomly in the area to get the result. After the key is generated, you can change the username by changing the "Key comment" field to something simpler. I've chosen "admin" as my username and my window looks like this:

Creating SSH keys

You should now save the Private key in a folder of your local machine. You can choose to set a passphrase for it when prompted, but it is not required. Also copy the generated key and save it in a text file. After this, go back to the instance creation page.

Just before the Create button, there is a link: Management, security, disks, networking, sole tenancy, click to expand the options. In that, select the Security tab and paste in your generated key, which you had copied earlier.

Adding the SSH keys

With this, we are done setting up our instance. Review all the options above and hit the Create button.

After waiting for a few seconds, this is what my instance page looks like:

Instances page

2. Connecting to the instance

Setup PuTTy

Now that we have created a Compute Engine instance, we have to connect to it. In this tutorial, I will use PuTTy, but you are free to use whichever software you prefer.

Open PuTTy and from the menu on the left, go to SSH > Auth. Click the Browse button and choose the Private key file you saved in the previous step.

Putty

Now go to the Session menu item, enter the External IP address of your instance, and click Open. You will get a prompt, asking if you trust the server. This is a prompt which you will always get when connecting to a new server. Click "Yes" and in the PuTTy command window, enter in your username (I had set mine as "admin"). You should be able to login with just your username and no password since we are using SSH.

Logging in SSH

Logging in SSH 2

Once you are logged in. Enter in the following commands to update and upgrade the installed packages:

Update and Upgrade packages

sudo apt-get update && sudo apt-get upgrade

You might have to enter "Y" in order to continue upgrading packages. Once this is done, you can keep the PuTTy window open as we will come back to it shortly.

The project

I've created a sample project, in my local machine that I will use for demonstration purposes in this tutorial. It is an extremely simple Django project that only has a Home Page view and this is what it looks like:

Home Page

The following 2 images show the structure of the project. Folder structure 1 Folder structure 2

  • The project's name is myproject.
  • mp is the virtual environment folder.
  • requirements.txt contains the dependencies of the project.
  • static contains the static files of the site like css, images and javascript.
  • media is reserved for media files that may be uploaded.

You can generate the requirements.txt file for your own project, using the following code:

output dependencies in a .txt file

pip freeze > requirements.txt

You will also have to add the following code to your my settings.py to let Django know of the "static" and "media" folders:

Specify the "static" and "media" folders

import os

STATIC_URL = '/static/'
STATIC_ROOT = os.path.join(BASE_DIR, 'static')
MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')

You can adapt the code and project structure according to your preferences.

I will be using a virtual environment when we deploy this project on the GCP instance and will use the requirements.txt file to install dependencies in that virtual environment.

Setup FileZilla

I will be using FileZilla to upload the project files to the server. Here is how you can set that up.

Open FileZilla. Then go to Edit > Settings. From the SFTP menu item select Add Key and select the private key you saved earlier. Then hit Ok. This will load in the private key and allow us to connect to the server without a password.

FileZilla 1

Go to Site Manager in FileZilla. Create a New Site, and enter the IP address of your instance and the username. Like so:

Create new site

Click Connect to connect to the server with your username and SSH keys. The directories should look like this:

Project directories in FileZilla

3. Setting up the project files on the server

Uploading files to the server

First, create a directory in the home directory of the server. You can create it either using PuTTy or FileZilla. I did it using FileZilla and this is what it looks like:

Uploading files

Now enter the directory and upload the files from your local computer to this directory on the server. Make sure to exclude the virtual environment folder (mp in my case).

Uploading files 2

Note: I was facing an issue with the upload of the db.sqlite3 file. I had to create a copy of the file, rename it to just "db", upload it to the server, and then rename that file to "db.sqlite3". You can try this technique if you face the same issue.

Now that our files are on the server, you can close FileZilla and switch to PuTTy.

Setting up the environment

Make sure you are logged into the server in PuTTy, just as we had left it in Section 2.

Now enter in the following commands to install pip and venv:

Install pip package manager

sudo apt-get install python3-pip

Installing venv: a virtual environment library

sudo apt-get install python3-venv

Make sure you're in the home directory of your user by entering pwd:

Output of pwd command

If you're not then cd into it.

Now create a virtual environment inside the myproject folder (Obviously you would change this to your own project name):

Initialize a virtual environment "mp"

python3 -m venv myproject/mp

Next change directory into the myproject folder:

cd into the project's directory

cd myproject

Now activate the virtual environment

Activate the virtual environment

source mp/bin/activate

Next, install the dependencies that are listed in the requirements.txt file

Installed the dependencies listed in the requirements.txt file

pip install -r requirements.txt

Now we need to add the instance's IP address in the settings.py file of our project. You can either do this using nano as below or through FileZilla.

This will allow you to edit the "settings.py" in the terminal (PuTTy)

nano myproject/settings.py

settings.py file

Then finally, use the following command to collect the static files in the static folder.

Collecting the static files in the "static" folder.

python manage.py collectstatic

With this, we are done with the basic preparation for launching our Django. Next, we need to set up a Webserver so we are able to serve the Django application.

4. Installing Apache & Mod WSGI

We will be installing the popular Apache 2 webserver for our project. We will also install the Mod_WSGI module that will allow us to host our python-based web application under Apache.

Install Apache2 Webserver and Mod_WSGI interface

sudo apt-get install apache2 && sudo apt-get install libapache2-mod-wsgi-py3

You might be prompted to confirm the installation. Enter "Y", whenever necessary.

The next few steps that remain in this section of the tutorial can be a little hard to understand in the beginning.

All that we are trying to do in these steps is to let Apache know the location of our project files, and how we'd like our project to be configured.

First, let'scd into the sites-available directory.

cd to the "sites-available" directory

cd /etc/apache2/sites-available

This folder has a default configuration file named 000-default.conf. So instead of creating our configuration file from scratch, we will use this file.

The following code will copy the 000-default.conf and create a duplicate of it named myproject.conf.

Creating our configuration file using the default one

sudo cp 000-default.conf myproject.conf

Now use nano to edit this file.

Editing the newly created configuration file

sudo nano myproject.conf

Now, you can analyze the following code and edit the file yourself. Or simply delete all the lines of the file by holding CTRL+K and paste in the following code:

<VirtualHost *:80>
    # The ServerName directive sets the request scheme, hostname and port that
    # the server uses to identify itself. This is used when creating
    # redirection URLs. In the context of virtual hosts, the ServerName
    # specifies what hostname must appear in the request's Host: header to
    # match this virtual host. For the default virtual host (this file) this
    # value is not decisive as it is used as a last resort host regardless.
    # However, you must set it for any further virtual host explicitly.
    #ServerName www.example.com

    ServerAdmin webmaster@localhost
    DocumentRoot /var/www/html

    # Available loglevels: trace8, ..., trace1, debug, info, notice, warn,
    # error, crit, alert, emerg.
    # It is also possible to configure the loglevel for particular
    # modules, e.g.
    #LogLevel info ssl:warn

    ErrorLog ${APACHE_LOG_DIR}/error.log
    CustomLog ${APACHE_LOG_DIR}/access.log combined

    # For most configuration files from conf-available/, which are
    # enabled or disabled at a global level, it is possible to
    # include a line for only one particular virtual host. For example the
    # following line enables the CGI configuration for this host only
    # after it has been globally disabled with "a2disconf".
    #Include conf-available/serve-cgi-bin.conf

  Alias /static /home/admin/myproject/static
  <Directory /home/admin/myproject/static>
    Require all granted
  </Directory>

  Alias /media /home/admin/myproject/media
  <Directory /home/admin/myproject/media>
    Require all granted
  </Directory>

  <Directory /home/admin/myproject/myproject>
    <Files wsgi.py>
      Require all granted
    </Files>
  </Directory>

  WSGIScriptAlias / /home/admin/myproject/myproject/wsgi.py
  WSGIDaemonProcess django_app python-path=/home/admin/myproject python-home=/home/admin/myproject/mp
  WSGIProcessGroup django_app

</VirtualHost>

# vim: syntax=apache ts=4 sw=4 sts=4 sr noet

The above code defines the filesystem structure of our project and also set permissions for Apache to open files and folders from the specified locations.

You simply have to replace bits of this code to match your setup. More specifically, you will have to replace myproject with your project's name, admin with your username, and lastly mp with the name of your virtual environment folder.

Save the file and exit.

Now, we simply need to enable the new site, and disable the default one:

Enable the "myproject" site (one defined by "myproject.conf")

sudo a2ensite myproject

Disable the default site

sudo a2dissite 000-default.conf

With this, we are done setting up our server and the configurations for our project. All that's left to do now is setting the right permissions to our files and folders.

5. Setting File Permissions

cd into the home directory (You can just enter cd). Use the pwd command to confirm your location: Print working directory

Enter in the following commands, one at a time.

Setting the right permissions

sudo chown www-data:www-data myproject/
sudo chown :www-data myproject/db.sqlite3
sudo chmod 664 myproject/db.sqlite3
sudo chown -R :www-data myproject/media
sudo chmod -R 775 myproject/media

The first two lines assign ownership of the myproject folder and db.sqlite3 to the www-data user and group. The third line modifies the permissions of the db.sqlite3 file and makes it writable. This will now allow you to not only read but also write to the database.

The fourth line gives ownership of the media folder to the www-data group of Apache and the last line modifies the permissions of the folder appropriately. The -R flag does this recursively (for any subfolders and files as well).

Now simply restart Apache.

Restart the apache2 service

sudo service apache2 restart

Now if you visit the IP address of your instance, your Django application should be working as expected, as is mine:

Final check

Hurray!

6. Finishing Up

One of the things mentioned in the Django deployment checklist is to keep sensitive information like email addresses, passwords, and the SECRET_KEY that are usually defined in the settings.py file, outside of the project files, but still be accessible.

One way to do this is to store this information in a file on the server, and simply load this file in the settings.py and use the information stored in there.

The first step to achieve this is to open your settings.py file to be edited. You can either do it through nano inside PuTTy, or use FileZilla to edit the file using your own text editor locally. This time I have used FileZilla and Sublime Text to edit this file:

Copy the "SECRET_KEY" and either keep it in your clipboard or save it in a temporary file on your local computer. Then add the following code in the settings.py file.

Load config.json file and use the stored SECRET_KEY

import json
with open('/etc/config.json') as config_file:
    config = json.load(config_file)

# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = config["SECRET_KEY"]

The code above will load the config.json file (which we are about to create) and save its contents in the config variable. We then define the "SECRET_KEY" usingconfig["SECRET_KEY"].

Reminder: You should also set your DEBUG variable to False whenever you are done debugging.

Create config.json using the touch command

sudo touch /etc/config.json

Open this file for editing using nano

sudo nano /etc/config.json

Paste in the "SECRET_KEY" that you copied from the settings.py file earlier and save it in the JSON format.

This is what mine looks like:

Saving SECRET_KEY in json file

Save the file and exit.

Now you can restart the apache2 service and reload the page in your browser, and it should run just as it did before, but now you no longer should have any sensitive information directly written in your settings.py file.

Restart Apache

sudo service apache2 restart

Please test all the functionality of your application, including logging in to your admin, and creating and editing content. If you run into any problems, review Section 4 and 5 of this tutorial carefully, and make sure you haven't made any mistakes modifying the code to your own specifications.

Hopefully, you were able to follow along without much trouble.

In Part 2, I will show you how to point a domain name to your instance, set up an SSL certificate to use HTTPS instead of the HTTP protocol, and also how to redirect your website from non-www to www version of your site.

Thank you for reading!