41
How to set up and deploy a Django application on a Linux server
29th July 2021
- Install any linux based distribution(Ubuntu, manjaro ...etc). Update and upgrade your software repositories.
// on any debian based distribution
sudo apt update && sudo apt upgrade
- server hostname can be set with the hostnamectl utility.
hostnamectl set-hostname <Host name>
# to verify
hostname
- Map server hostname with the server IP address by adding the following line in /etc/hosts file.
<ip address> <host name>
( Running commands as the root user is not safe, it's better to login as a regular user with sudo permission)
- Add a new user and add him to the sudoers list.
adduser <username>
adduser <username> sudo
# Login to the system as the new user
(Password authentication is not secure, generate and use ssh keys instead)
ssh-keygen for key generation (use ssh-copy-id if it is available in your system ).
generate ssh keys in local machine with
ssh-keygen -b 4096
command.create an empty .ssh directory inside home directory of your server . Provide the read,write and execution permissions.
Transfer the locally generated ssh keys to the server with scp
scp <location of the generated key> <username>@ipaddress:~/.ssh/<name given to the keys>
- provide read and write permissions for the key.
- For disallowing the root Login and password logins, add the following lines to the
/etc/ssh/sshd_config
file.
PermitRootLogin no
PasswordAuthentication no
Restart the sshd
service.
sudo apt install ufw
# Block all incoming and outgoing traffic except ssh
sudo ufw default allow outgoing
sudo ufw default deny incoming
sudo ufw allow ssh
# allow the port 8000 (Django uses this port run the development server)
sudo ufw allow 8000
# make sure to enable ssh before enabling ufw
sudo ufw enable
# after testing
sudo ufw delete allow 8000 && sudo ufw allow http/tcp
#for status
sudo ufw status
# in the root directory
pip freeze > requirements.txt
# Django==3.2.5
# psycopg2==2.9.1
# pytz==2021.1
# sqlparse==0.4.1
3.2 Transfer the project from the local machine to the server.It can be done by using any of the following tools.
- An ftp client like FileZilla
- scp to transfer files via ssh.
# -r for recursive file transfer.
scp -r <local project path> <username>@<ip address>:~/
- git
git clone <remote repository url>
sudo apt install python3-pip
sudo apt install python3-venv
# Creating a virtual environment inside the project directory
python3 -m venv <projectDirectory>/<virtualEnvName>
# activation
source <projectDirectory>/<virtualEnvName>/bin/activate
pip install -r requirements.txt
DEBUG = False
ALLOWED_HOSTS = ['<ipAddress/domainName>']
# Static handled differently in production.
# third party storages or whitenoise requires additional settings
STATIC_ROOT = os.path.join(BASE_DIR,'static')
python manage.py collectstatic
sudo apt install postgresql postgresql-contrib
sudo -u postgres psql
CREATE DATABASE databasename;
CREATE USER username WITH PASSWORD 'password';
ALTER ROLE username SET client_encoding TO 'utf8';
ALTER ROLE username SET default_transaction_isolation TO 'read committed';
ALTER ROLE username SET timezone TO 'Asia/Kolkata';
GRANT ALL PRIVILEGES ON DATABASE databasename TO username;
# we have opened the port 8000 with ufw
python manage.py runserver 0.0.0.0:8000
# and test the application on <ipAddress>:8000
Django server is only for development purposes, you should not use it for production. We can use reliable servers like apache with mode_wsgi or Nginx with gunicorn
- install apache
sudo apt install apache-2
install mod_wsgi
sudo apt install libapache2-mod-wsgi-py3
configure apache
cd /etc/apache2/sites-available
# duplicate any of the default configuration file to get started.
cp 000-default.conf <name_given_to_your_config>.conf
<!-- NB: all paths needs to be absolute -->
<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
<!-- right before the closing of </VirtualHost> we need to map Aliases for static and media -->
Alias /static /home/YOURUSER/YOURPROJECT/static
<Directory /home/YOURUSER/YOURPROJECT/static>
Require all granted
</Directory>
Alias /media /home/YOURUSER/YOURPROJECT/media
<Directory /home/YOURUSER/YOURPROJECT/media>
Require all granted
</Directory>
<Directory /home/YOURUSER/YOURPROJECT/YOURPROJECT>
<Files wsgi.py>
Require all granted
</Files>
</Directory>
WSGIScriptAlias / /home/YOURUSER/YOURPROJECT/YOURPROJECT/wsgi.py
WSGIDaemonProcess django_app python-path=/home/YOURUSER/YOURPROJECT python-home=/home/YOURUSER/YOURPROJECT/<VIRTUALENV>
WSGIProcessGroup django_app
</VirtualHost>
- enable the configuration
sudo a2ensite <YourConfiguration>
- disable the default configuration
sudo a2dissite <Default configuration>
- reload apache service
systemctl reload apache2
// Give database access to apache
$ For sqlite database
sudo chown :www-data <Path to your sqlite file>
sudo chmod 664 <Path to your sqlite file>
sudo chown :www-data <Path to your Django project>
sudo chown -R :www-data <Path to your media directory>
sudo chmod -R 774 <Path to your media directory>
- Install nginx and add this to
/etc/nginx/sites-enabled/default
server {
server_name 127.0.0.1 yourhost@example.com;
access_log /var/log/nginx/domain-access.log;
location / {
proxy_pass_header Server;
proxy_set_header Host $http_host;
proxy_redirect off;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header X-Scheme $scheme;
proxy_connect_timeout 10;
proxy_read_timeout 10;
# This line is important as it tells nginx to channel all requests to port 8000.
# We will later run our wsgi application on this port using gunicorn.
proxy_pass http://127.0.0.1:8000/;
}
}
- Install gunicorn
pip install gunicorn
- Start your django project using gunicorn and the wsgi.py file
$ cd </path/to/djangoproject_subdirectory_with_wsgi.py>
$ gunicorn wsgi -b 127.0.0.1:8000 --pid /tmp/gunicorn.pid --daemon
# --daemon parameter tells gunicorn to run in the background
# So that gunicorn continues to run even if you close your ssh session
# (You cannot remain ssh-ed into your server all the time right!)
Please do not use "wsgi.py"; you just have to use wsgi without the ".py" extension when calling gunicorn. This will start your wsgi application in the background.
--Visit "[email protected]" in your browser
Now your application must be up and running on your instance. Visit:
and see if your application is running. Do not forget to replce [email protected] in the above and in the nginx configuration file before.
--(Optional) Additional Notes
In Step 1, if confused; remove all existing lines from the
/etc/nginx/sites-enabled/default
file and put the above code inside it. (Or delete and create a new blank file and add the code)If you are using virtualenv, and you did a
pip install gunicorn
inside the virtualenv in Step 2, then run the Step 3 command with respective virtualenv activated.The pid of the gunicorn process is stored in /tmp/gunicorn.pid; incase you want to kill the existing gunicorn process and restart it.
supervisord
might be used in conjunction, which helps in restarting the gunicorn daemon automatically in case it dies due to some reason. This is useful in production environments.
#add the env variables to the apache configuration
SetEnv DB_NAME mydatabase
SetEnv DB_USER mydbuser
SetEnv DB_PASSWD sekrit
SetEnv DB_HOST localhost
# reload and restart apache secrvice
//
edit the wsgi file so that mode_wsgi can pass vars to the application.
import os, site, sys
site.addsitedir('/usr/local/virtualenvs/MYAPP-VIRTUALENV/lib/python2.7/site-packages')
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
sys.path.append(os.path.join(BASE_DIR, '..'))
os.environ["DJANGO_SETTINGS_MODULE"] = "myapp.settings" # see footnote [2]
from django.core.wsgi import get_wsgi_application
_application = get_wsgi_application()
env_variables_to_pass = ['DB_NAME', 'DB_USER', 'DB_PASSWD', 'DB_HOST', ]
def application(environ, start_response):
# pass the WSGI environment variables on through to os.environ
for var in env_variables_to_pass:
os.environ[var] = environ.get(var, '')
return _application(environ, start_response)
sudo touch /etc/config.json
config.json
{
"SECRET_KEY":"sncalsjkn@#545jnsjkcn",
#other keys goes here
}
- update the settings.py file
import json
with open('/etc/config.json') as configFile:
config = json.loads(configFile)
SECRET_KEY = config.get("SECRET_KEY")
# OTHER SETTINGS....
sudo systemctl restart apache2
41