Deploying Django application with Gunicorn, Apache, and MySQL on Ubuntu Server
Deploying a Django application is a crucial step in taking your web project from development to production. While Django’s built-in development server is great for testing and debugging, it’s not suitable for handling the demands of a live production environment. To ensure your Django application can handle real-world traffic and serve as a reliable, performant, and secure web application, you’ll need to set up a production-ready server stack.
In this guide, we’ll walk you through the process of deploying a Django application on an Ubuntu server using Gunicorn as the application server, Apache as the reverse proxy server, and MySQL as the database management system. This stack is a popular choice for deploying Django applications due to its stability, scalability, and security features.
Setting up the software
1. Update Ubuntu software repository
# sudo apt update
2. Install
i. apache2 – Serve our website
# sudo apt install apache2
ii. mysql-server and libmysqlclient-dev – For database
# sudo apt install mysql-server libmysqlclient-dev pkg-config
iii. Python 3
# sudo apt install python3-pip python3-dev
iv. ufw – Firewall for our system
# sudo apt install ufw
v. virtualenv – Virtual environment for our django application
# sudo apt install virtualenv
Clone your django repo
$ git clone <link to the repo>
$ cd <project folder>
$ git checkout <active branch>
Installing Python libraries
1. Create virtual env (e.g env)
$ python3 -m venv env
2. Activate virtualenv
$ source env/bin/activate
3. Install project dependencies and packages
$ pip install -r requirements.txt
4. Install mysql client for python
$ pip install mysqlclient
5. Install gunicorn to interact with our python code
$ pip install gunicorn
6. Install white noise to serve our static files
$ pip install whitenoise
Setting up the firewall
We’ll disable access to the server on all ports except 8000 and OpenSSH for now. Later on, we’ll remove this and give access to all the ports that Apache needs.
$ sudo ufw default deny
$ sudo ufw enable
$ sudo ufw allow openssh
$ sudo ufw allow 8000
Setting up database
1. Install mysql and setup the root password
$ sudo apt install mysql-server
$ sudo mysql -u root -p
2. Create a database and a user for the application.
mysql> CREATE DATABASE '<app database name>' CHARACTER SET 'utf8';
mysql> CREATE USER '<app user name>'@'localhost' IDENTIFIED BY '<app user password>';
mysql> GRANT ALL PRIVILEGES ON <app database name>.* TO '<app user name>'@'localhost';
mysql> quit;
Setting up the Django project
In your project folder, modify the settings.py file
1. Include the newly created database access configurations.
2. Include the IP address of your server and domain name (if any) in the allowed hosts.
3. Include CORS_ALLOWED_ORIGINS, CSRF_TRUSTED_ORIGINS, and CORS_ORIGIN_WHITELIST if you have a domain name set already.
4. In your installed apps, include ‘whitenoise.runserver_nostatic’ just above ‘django.contrib.staticfiles’.
5. In your MIDDLEWARE, include ‘whitenoise.middleware.WhiteNoiseMiddleware’ just below, ‘django.middleware.security.SecurityMiddleware’.
DEBUG = False # We have turned DeBUG to False since we’re in production
ALLOWED_HOSTS = ['127.0.0.1', 'domain name', 'ip-address'] # ip address and domain name
INSTALLED_APPS = [
'whitenoise.runserver_nostatic',
'django.contrib.staticfiles', ]
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
"whitenoise.middleware.WhiteNoiseMiddleware", ]
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'OPTIONS': {
'sql_mode': 'traditional',
},
'NAME': '<app database name>',
'USER': '<app username>',
'PASSWORD': '<app user password>',
'HOST': 'localhost',
'PORT': '3306',
}
}
CORS_ALLOWED_ORIGINS = [
"http://localhost:8000",
'domain name' ]
CSRF_TRUSTED_ORIGINS = [
'http://localhost:8000',
'domain name' ]
CORS_ORIGIN_WHITELIST = [
'http://localhost:8000',
'domain name' ]
STATIC_URL = '/static/'
STATIC_ROOT=os.path.join(BASE_DIR, 'static/')
MEDIA_URL='/media/'
MEDIA_ROOT=os.path.join(BASE_DIR, 'media/')
6. Make database migrations to create all the required tables in the new database and collect all static files to a static folder under the django_project directory.
(env) $ python manage.py makemigrations
(env) $ python manage.py migrate
(env) $ python manage.py collectstatic
7. Run the development server to check whether everything is working fine.
(env) $ python manage.py runserver
Setting up Gunicorn
1. Test gunicorn with django by running the following command inside the django project folder
(env) $ gunicorn --bind 0.0.0.0:8000 <django_project>.wsgi:application
2. Test the app in your browser on the server ip while mapping the port <server ip>:8000 e.g http://137.63.128.239:8000 . (Notice that you can access static files because we set up whotenoise.)
3. Kill gunicorn and exit the virtual environment.
4. Lets daemonize the gunicorn
i. Open a new gunicorn.service file using any text editor your comfortable with.
#vim /etc/systemd/system/gunicorn.service
[Unit]
Description=Gunicorn instance to serve the django app
After=network.target
[Service]
# Replace with your system user
User=root
# Replace with your system group
Group=root
WorkingDirectory=/home/<user>/<project folder>/
#ExecStart=path to gunicorn_config.py your_app_module.wsgi:application
ExecStart=/home/<user>/<project folder>/<virtual env>/bin/gunicorn_start.sh
[Install]
WantedBy=multi-user.target
#This is the systemd file, which can be called: gunicorn.service
5. Create a script to start the server
i. Create gunicorn_start.sh in your virtualenv bin directory
ii. Edit the file as follows:
vim /home/<user>/<project folder>/<virtualenv>/bin/gunicorn_start.sh
#!/bin/bash
#This is the Gunicorn script used to automatically launch the application through Gunicorn
NAME="django-application"
#path to the folder containing the manage.py file
DIR=/home/<user>/<project folder>
# Replace with your system user
USER=root
# Replace with your system group
GROUP=root
WORKERS=3
#bind to port 8000
BIND=<server ip>:8000
# Put your project name
DJANGO_SETTINGS_MODULE=<Project name>.settings
DJANGO_WSGI_MODULE=<Project name>.wsgi
LOG_LEVEL=error
cd $DIR
#activating the virtual environment
source /home/<user>/<project folder>/<virtualenv>/bin/activate
export DJANGO_SETTINGS_MODULE=$DJANGO_SETTINGS_MODULE
export PYTHONPATH=$DIR:$PYTHONPATH
exec gunicorn ${DJANGO_WSGI_MODULE}:application \
--name $NAME \
--workers $WORKERS \
--user=$USER \
--group=$GROUP \
--bind=$BIND \
--log-level=$LOG_LEVEL \
--log-file=-
iii. Make the file executable
$ sudo chmod 777 /home/<user>/<project folder>/<virtual env>/bin/gunicorn_start.sh
iv. Enable the daemon
$ sudo systemctl enable gunicorn.service
$ sudo systemctl start gunicorn.service
$ sudo systemctl status gunicorn.service
vi. If something goes wrong, run
$ journalctl -u gunicorn
If you make any changes to your Django application, reload the gunicorn service
$ sudo systemctl daemon-reload
$ sudo systemctl restart gunicorn
Setting up the Apache server
1. Create a new site file for apache
$ sudo vim /etc/apache2/sites-available/newsite.conf
2. Copy the following lines with appropriate modifications
<VirtualHost *:80>
ServerName <server name> #e.g uc23.devops.renu.ac.ug
ServerAlias <Server alias> #e.g www.uc23.devops.renu.ac.ug
ServerAdmin <server admin> #e.g devops@renu.ac.ug
DocumentRoot /home/<user>/<project folder>
ProxyPass / http://localhost:8000/
ProxyPassReverse / http://localhost:8000/
ProxyPass /login http://localhost:8000/login
ProxyPassReverse /login http://localhost:8000/login
ProxyPass /admin http://localhost:8000/admin
ProxyPassReverse /admin http://localhost:8000/admin
#Path to static files
Alias /static /home/<user>/<project folder>/static
<Directory /home/<user>/<project folder>/static>
Require all granted
</Directory>
#Path to static files
Alias /media /home/<user>/<project folder>/media/
<Directory /home/<user>/<project folder>/media/>
Require all granted
</Directory>
# AddType directive for JavaScript files
AddType application/javascript .js
# Replace newsite with site name
ErrorLog ${APACHE_LOG_DIR}/<newsite>-error.log
CustomLog ${APACHE_LOG_DIR}/<newsite>-access.log combined
</VirtualHost>
3. Enable the necessary Apache modules.
$ sudo a2enmod proxy proxy_http proxy_balancer lbmethod_byrequests
4. Enable the newly registered site.
$ sudo a2ensite <sitename.conf>
5. Restart apache
$ sudo systemctl restart apache2
6. allow access to apache ports through ufw
$ sudo ufw allow "Apache Full"
If everything works fine, you should be able to access your Django application on <server ip> or <domain name (if you registered one)>
Setting up SSLs for our domain
1. To set up SSL/TLS certificates for a domain (e.g., uc23.devops.renu.ac.ug), We’re going to use Certbot (a tool for obtaining and renewing Let’s Encrypt SSL certificates)
$ sudo apt install certbot python3-certbot-apache
$ sudo a2enmod ssl # Enable ssl since we’re using apache
$ sudo certbot --apache -d uc23.devops.renu.ac.ug
2. Register a new apache site to enable SSLs.
$ vim /etc/apache2/sites-available/sitename-ssl.conf
<IfModule mod_ssl.c>
<VirtualHost *:443>
ServerName <domain name> #e.g uc23.devops.renu.ac.ug
ServerAdmin <server admin> # e.gdevops@renu.ac.ug
DocumentRoot /home/<user>/<project folder>
ProxyPass / http://localhost:8000/
ProxyPassReverse / http://localhost:8000/
ProxyPass /admin http://localhost:8000/admin
ProxyPassReverse /admin http://localhost:8000/admin
Alias /static /home/<user>/<project folder>/static/
<Directory /home/<user>/<project folder>/static/>
Require all granted
</Directory>
# AddType directive for JavaScript files
AddType application/javascript .js
ErrorLog ${APACHE_LOG_DIR}/uc23-error.log
CustomLog ${APACHE_LOG_DIR}/uc23-access.log combined
SSLCertificateFile /etc/letsencrypt/live/<domain name>/fullchain.pem
SSLCertificateKeyFile /etc/letsencrypt/live/<domain name>/fullchain.pem/privkey.pem
Include /etc/letsencrypt/options-ssl-apache.conf
</VirtualHost>
</IfModule>
3. Enable new site
$sudo a2ensite <sitename-ssl.conf>
4. Reload apache
$ sudo systemctl reload apache2
Congratulations, You can now access your site with SSL enabled in your browser.