How to: Deploy a Django application using Ubuntu (AWS Lightsail)

Learn how to deploy a Django application using Apache and Ubuntu on AWS Lightsail
image
Scott James
Dec. 4, 2020
690 Views
16 min.

Overview


I will preface this tutorial by stating that this is by no means the easiest or quickest way to deploy a Django application to the web. However, if you want full control over the configuration of your server and the ability to cherry-pick and install specific versions of Django, Python, Apache or modWSGI for your production environment, this tutorial is for you. Going through each step individually is also a great way to learn more about Apache and Linux configuration in general!

While many Django deployment guides found online opt to install software components from standard Linux distribution libraries (with commands like "$ sudo apt-get install libapache2-mod-wsgi-py3"). This tutorial opts against such practice as these packages are often out dated, reducing compatibility with newer versions of Django while increasing potential security risks.

Taking a step back from 'Platform as a service' providers such as Heroku that are able to do a lot of the heavy lifting for you, this tutorial seeks to provide a from scratch, step by step approach to Django deployment on apache.

AWS and Lightsail was chosen for this tutorial as it allows for integration of other services (such as static/media file hosting in s3) to your project without having to leave the AWS ecosystem. Starting at $3.50US a month, Lightsail provides a robust foundation to deploy a smaller scale Django project without having to delve into the cost and added complexity of running and managing load balancers, EC2 instances & customised CDN infrastructure required for larger scale projects. 

 

The below diagrams help contextualise the various components that are part of this tutorial.


An overview of the network Architecture used in this tutorial

 


Diagram outlining the required software components situated within the instance

 

Ingredients


For the sake of this tutorial I am using the latest available versions of each software component. However, depending on your requirements, older versions may be substituted into this tutorial so long as compatibility of the individual components is maintained. Eg. Your desired version of Django supports your desired version of Pyhton.

 

The Recipe


  1. Set up Django project for deployment

    The first step in this process involves setting your Django project up for deployment to the web. Below is a checklist of steps you should follow to ensure your project is ready to deploy:

    • Ensure that there are no keys or passwords written in plain text anywhere in your project! Including your settings.py

      One of the cleanest solutions here is to use environment variables to store sensitive data. Visit the blog post .......... for steps on how to set up and access environment variables for a Django project
    • Create a requirements.txt file that lists your projects dependancies for easier installation on the web server later in the tutorial.
      Activate your python development environment on your local machine, navigate to your project source code directory and run the following command: 

      $ pip freeze>requirements.txt
    • If you are using the default sqlite database move your db.sqlite file into its own sub folder eg. 'db'. Then in the DATABASES section of your settings.py update the 'NAME' field to reflect this change so that Django can locate the Database in its new location.

      |||{"file":"Settings.py"}|||
      DATABASES = {
          'default': {
              'ENGINE': 'django.db.backends.sqlite3',
              'NAME': os.path.join(BASE_DIR , 'db/db.sqlite3'),
          }
      }
      This step is required as ModWSGI requires read/write permissions to the sqlite database and its contianing folder when in production. This allows for more granular control over permissions on the production server.
    • Your project structure should resemble the below after completing these stepsThe easiest way to transfer your project files to a web server later in the tutorial is by using Git. So if your source code files are not yet in a repository, follow the steps ........ to push your code up to a remote repository.

  2. Spin up an AWS Lightsail Ubuntu instance

    •  

      Log into the AWS console and navigate to the Lightsail service.

      NOTE: Make sure that you are creating your Ubuntu instance in the correct AWS reigion! With a single instance delpoyment such as this, load times may be affected if end users are geographically distanced from your server. (A post detailing a more available and scalable architecture will be coming soon)
    • Click "Create Instance on the right hand side of the console

    • Select "Linux/Unix" > "OS Only" > "Ubuntu 20.04 LTS"

    • Either continue with the default SSH Key pair (used to connect to your instance) or click "Change SSH Key Pair" to create an individual key for this instance

    • Select the tier of service you would like, name the instance and then click "Create Instance"


      Example Instance configuration

      An example configuration for a lightsail instance

      NOTE: You may have to wait a few minutes for your instance to spin up before continuing
  3. Configure your Static IP

    Next you must create and link a static IP address to your instance, allowing you to access your instance from the outside world.

    • Click on the "Networking" tab from within the Lightsail console

    • Click "Create Static IP"

    • Under attach to instance, select the instance you just created in the previous step

    •  Name your Static IP and click createExample Static IP config
      Example Static IP Configuration

  4. Connect to your instance & prepare for installation

    Once your Lightsail instance is up and running, connect to the instance either through the integrated, online AWS SSH terminal, or through the terminal on your own machine by following the steps below:

    • Update the permissions of the downloaded key.pem file on your local machine using the following command

      $ sudo chmod 600 /path/to/your/key.pem
    • Connect to the Lightsail instance from your terminal

      $ ssh -i path/to/your/key.pem/file yourLightsailUser@yourLightsailPublicIP
    •  Ensure your instance is up to date by running the following command:

      $ sudo apt-get update -y && sudo apt-get dist-upgrade -y

       

  5. Create a new user

    It is widely considered bad practive to use root accounts and password based authentication when working with remote servers. While Lightsail can save you time by initialising instances with a default user account (usually 'ubuntu') that already uses key based authentication, for the purposes of this tutorial I will cover how to create a new user account with key based authentication for those that require it.

    For more information on alternative methods for setting up key based authentication on AWS instances, visit the Docs HERE

    • Create a new user account on the instance and follow the prompts to complete user creation

      $ sudo adduser youruser
    • Grant sudo permissions to newly created user

      $ sudo usermod -aG sudo youruser
    • Change security context to the newly created user

      $ sudo su - youruser
    • Create Directories for storing your SSH keys and ensure they have correct permissions

      $ mkdir ~/.ssh
      $ chmod 700 ~/.ssh
    • Create a new Public Private key pair on your Local Machine

      $ ssh-keygen -f ~/Desktop/django_server_key -b 4096

      This command will save the Private and public key to your Desktop. Make sure to move the private key to a safe location.

    • Transfer the public key to the Lightsail instance from your local machine

      $ scp ~/Desktop/django_server_key youruser@yourIP:~/.ssh/authorized_keys
    • Ensure correct permissions are set on the authorized_keys file, then restart the SSH service

      $ sudo chmod 600 ~/.ssh/*
      $ sudo service ssh restart
    • Try to log into the instance from a new terminal window on your local machine

      $ ssh -i /path/to/your/new/private/key youruser@yourIP
    • To keep things neat for the following steps, create a downloads or tmp directory in your root user directory with the command:

      $ mkdir ~/tmp
    • Navigate to the newly created directory

      $ cd ~/tmp

       

  6. Install Apache web server

    $ sudo apt-get install apache2 apache2-dev -y

    At this point if you eneter the public IP of your instance into a browser, you should be able to see the default apache landing page!

  7. Install and configure Python 3.8.6 **With shared libraries**

    All Python source code files are available for download HERE. If required, choose the version appropriate for your specific project.

    • Install dependancies required to install python from source

      $ sudo apt-get install build-essential zlib1g-dev libgdbm-dev libncurses5-dev libnss3-dev libssl-dev libreadline-dev libffi-dev libsqlite3-dev wget -y
    • Download Python 3.8.6 using the following command:

      $ wget https://www.python.org/ftp/python/3.8.6/Python-3.8.6.tgz -P ~/tmp
    • Unpack the Python source code files

      $ cd ~/tmp
      $ tar xvfz ~/tmp/Python-3.8.6.tgz
    • Configure python install to enable shared libraries before install

      $ cd ~/tmp/Python-3.8.6
      $ ~/tmp/Python-3.8.6/configure --enable-optimizations --enable-shared --prefix=/opt/python LDFLAGS=-Wl,-rpath=/opt/python/lib

      To provide some context behind this command, mod WSGI requires you install python using shared libraries, however, installing python with shared libraries can cause issues on systems already running the same or similar versions of python (3.8 on ubuntu 20 for instance). Where a new standalone python install from source code will pick up libraries from an existing python installation. And as a result the python3.8 --versioncommand will return an unexpected result even when running directly from the python binary eg. /usr/local/bin/python3.8 --versionwould return 3.8.2 in this case instead of 3.8.6. There are two solutions to this issue, compile your python install with the-rpathflag as we have done here, or alternatively set the "LD_LIBRARY_PATH" environment variable with the appropriate python library path at run time. eg. LD_LIBRARY_PATH=/usr/local/lib/ /usr/local/bin/python3.8

    • Make Python install

      $ sudo make altinstall
    • Optional step: If you are interesting in overriding the default Python 3.8 bash command to use this newly installed version of python, investigate the "update-alternatives" command. Be aware though, updating python alternatives may have adverse effects and can potentially break other system services such as apt.

    • Confirm your python install by running the following command

      $ /opt/python/bin/python3.8 --version
    • Cleanup (optional)

      $ sudo rm -r ~/tmp/Python-3.8.6*

       

  8. Install and configure mod WSGI 4.7.1

    All mod WSGI source code files are available for download HERE. If required, choose the version appropriate for your specific project.

    • Download mod WSGI using the following command:

      $ wget https://github.com/GrahamDumpleton/mod_wsgi/archive/4.7.1.tar.gz -P ~/tmp
    • Unpack the source code

      $ cd ~/tmp
      $ tar xvfz ~/tmp/4.7.1.tar.gz
    • Configure mod WSGI before install

      $ cd ~/tmp/mod_wsgi-4.7.1   
      $ ~/tmp/mod_wsgi-4.7.1/configure --with-python=/opt/python/bin/python3.8
    • Make mod WSGI install

      $ sudo make
    • Install mod WSGI

      $ sudo make install
    • Cleanup (optional)

      $ sudo rm -r ~/tmp/4.7.1.tar.gz && sudo rm -r ~/tmp/mod_wsgi-4.7.1

       

  9. Configure apche to use mod WSGI

    • Add the mod WSGI module to Apache's available modules

      $ sudo nano /etc/apache2/mods-available/wsgi_module.load
    • Add the following line to the wsgi_module.load file

      |||{"file":"wsgi_module.load"}|||
      LoadModule wsgi_module /usr/lib/apache2/modules/mod_wsgi.so
    • Enable the mod WSGI module

      $ sudo a2enmod wsgi_module
    • Restart the apache2 service

      $ sudo systemctl restart apache2

       

  10. Create Python environment to run project

    • Choose a path with which to create tour virtual environment. For the purpose of this tutorial we will place it in the base user directory
      $ cd ~
      $ /opt/python/bin/python3.8 -m venv /srv/www/venv
    • Verify the Python version used by your venv by entering the following commands
      $ source /srv/www/venv/bin/activate
      $ python --version

      If the above command does not return the version of python you installed earlier, you may needto revisit the Python installation steps

  11.  Transfer your code to the server

    In the first step of this tutorial, your project source code, along with a Requirements.txt file should have been moved to a remote Git repository.

    • Run the following commands to initialise a git repo on your Lightsail instance and pull down your source code

      $ mkdir srv/www/venv/src/
      $ cd /srv/www/venv/src/
      $ git init
      $ git remote add origin https://url/to/your/remote/repository
      $ git pull origin master

       

  12. Install required dependancies to your python virtual environment

    Step one of this tutorial also covered the creation of a "requirements.txt" file that should be present within your projects source code that you just loaded onto your Lightsail instance. That file will now be used to install all of your project dependancies for us!

    • Make sure your python environment is active on your Lightsail instance. If not. Run:

      $ source /srv/www/venv/bin/activate
    • Install all required dependancies using:

      $ pip install -r /srv/www/venv/src/requirements.txt

       

  13. Define environment variables for production environment

    Obviously having keys and other sensitive information written in plain text within your project files presents security risks and is very bad practice. Using environment variables to take their place is a suitable solution. The steps below show how to define and access environment variables in a production environment (this approach is suggested by Graham Dumpleton - the author of mod WSGI):

    • Create a new .wsgi file in your project directory (same location as your setings.py)

      $ sudo nano /srv/www/venv/src/django_project/production.wsgi

      MAKE SURE THAT THIS FILE IS NOT INCLUDED IN YOUR GIT REPOSITORY as it will contain sensitive information.

    • Add your required variables to the file in the following format:

      import os
      from django.core.wsgi import get_wsgi_application
      
      os.environ['variable_1_name'] = "variable_1_value"
      os.environ['variable_2_name'] = "variable_2_value"
      ...
      
      os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'django_project.settings')
      application = get_wsgi_application()

       

  14. Run test server (optional)

    At this point you can run the Django development server if you would like to test that your site is functioning correctly before proceeding.

    • Log into lightsail and open up port 8000 to allow the Django test server to serve on this port

    • Activate the python virtual environment on your lightssail instance if it is not already running

      $ source /srv/www/venv/bin/activate
    • Start the Django development server

      $ cd path/to/your/project/files
      $ python manage.py runserver
    • Remember to close off access to port 8000 through the Lightsail console once you have completed testing

  15. Update Apache .conf files

    Now that everything is installed and all of your project files have been loaded onto the lightsail instance. The next step is to update the apache configuration files so that apache knows to serve your project files from your virtual environment using mod WSGI, instead of just serving static files from the usual /var/www/ directory.

    • Copy the default configuration file (usually 000-default.conf) to a new file for your project using the following commands:

      $ sudo cp /etc/apache2/sites-available/000-default.conf django_project.conf
      $ sudo nano /etc/apache2/sites-available/django_project.conf
    • Update the .conf file with the following lines of code:

      If you are serving your static or media files from and external source such as s3, their respective alias definitions will not be required in your apache configuration file
      |||{"file":"django_project.conf"}|||
      <VirtualHost *:80>
      
          ServerName yourDomain.com
          ServerAlias www.yourDomain.com
          ServerAdmin yourEmail@email.com
      
          Alias /static /srv/www/venv/src/static
          <Directory /srv/www/venv/src/static>
              Require all granted
          </Directory>
      
          Alias /media /srv/www/venv/src/media
          <Directory /srv/www/venv/src/media>
              Require all granted
          </Directory>
      
          <Directory /srv/www/venv/src/django_project>
              <Files production.wsgi>
                  Require all granted
              </Files>
          </Directory>
      
          WSGIScriptAlias / /srv/www/venv/src/django_project/production.wsgi
          WSGIDaemonProcess django_project python-path=/srv/www/venv/src python-home=/srv/www/venv
          WSGIProcessGroup django_project
      </VirtualHost>
    • Run the following command to tell apache to enable the 'site' you just configured in your custom configuration file

      $ sudo a2ensite django_project
      $ sudo a2dissite 000-default
      $ sudo systemctl reload apache2
    • If you wish to run more than one Django project on a single server instance you may repeat steps 9 - 15 for each individual project you wish to host on the instance. (This will require further configuration of virtual hosts so that apache knows which site to direct certain traffic to)

  16. Update file permissions

    A cricual step to tightening up the security of your Django project is ensuring all files on the instance have the correct permissions. Run the following commands to update permissions on your project files

    • Grant the apache user (:www-data) permission to access and execute your project files

      $ sudo chown -R :www-data /srv/www/venv/src
      $ sudo chmod -R 750 /srv/www/venv/src
    • If using SQLlite databse

      if using SQL lite, the apache user will need write permissions to the entire containing directory of the db file. This permission is required to create database locking files during operations as explained in step one of this tutorial.

      $ sudo chown -R :www-data /srv/www/venv/src/db
      $ sudo chmod -R 670 /srv/www/venv/src/db
    • If hosting media files on your lightsail instance

      $ sudo chown -R :www-data /srv/www/venv/src/media
      $ sudo chmod -R 660 :www-data /srv/www/venv/src/media
    • Confirm the permissions of your directories and files by using the following command:

      $ ls -la

       

  17. Use CertBot to install SSL certificate and configure server for HTTPS access (Optional) 

    Certbot is a super easy and free way to obtain, install and configure a SSL certificate on your instance all from the command line! The steps below run through how to get your site set up with HTTPS and protect your users data from eavesdropping. Alternatively, you can follow the CertBot instructions HERE

    NOTE: You will need admin access to your Domain name to complete this step. Follow the guide HERE if you wish to purchase and configure a domain through AWS. If using an existing domain, you will need to follow the instructions of your respective provider to update the DNS records required to complete the following steps
    • Log into the AWS Lightsail console and under the instances networking tab, create a new firewall rule to allow HTTPS (port 443) connections

    • Download CertBot using the following command

      $ sudo apt-get remove certbot
      $ sudo snap install core; sudo snap refresh core
    • Install CertBot

      $ sudo snap install --classic certbot
    • Prepare CertBot

      $ sudo ln -s /snap/bin/certbot /usr/bin/certbot
    • Issue certificate and configure your server for HTTPS all in one step

      $ sudo certbot --apache
    • Test CertBots auto-renew feature

      $ sudo certbot renew --dry-runThe Result

If you have followed along with every step here, you should now have a live Django application running on AWS! If you've had any trouble along the way or have encountered errors while trying to adapt this tutorial to different versions of Ubuntu/Python/Django etc. There are some troubleshooting steps and helpful links below to help point you in the right direction.

 

Troubleshooting


When it comes to troubleshooting,, Logs are your friends! If you are recieving 500 errors or having any other trouble getting your app to load correctly, make sure to check the Apache logs to get some meaningful information on the error and help you pin point the exact cause of the problem.

Apache logs are typically found at /var/log/apache2/error.log and can be viewed with the following command:

$ nano /var/log/apache2/error.log

 

Listed below are some helpful links to assist in any further troubleshooting efforts