How to set up and deploy a Django application on a Linux server

29th July 2021
Step 1. Server configuration.
  • 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.
    Step2. Setting up FireWall
    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
    Step 3. Set up the project for production.
    3.1 Generate the requirements.txt file
    # 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>
    step 4. Set up the python virtual environment
    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
    step 5. install project dependencies
    pip install -r requirements.txt
    step 6. make changes to the settings.py.
    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
    Configuring Postgres Database
    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;
    step 7. Test the application with the dev server
    # we have opened the port 8000 with ufw
    python manage.py runserver 0.0.0.0:8000
    # and test the application on <ipAddress>:8000
    step 8. Configuring production server
    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
    8.1 apache with mod_wsgi
  • 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
    - edit your config file and add the followings
    <!-- 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>
    8.2 Nginx with gunicorn
  • 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 "yourhost@example.com" 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 yourhost@example.com 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 apip 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.

  • step 9. Environment variables
    9.1 Passing Apache Environment Variables to Django via mod_wsgi.
    #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)
    9.2 we can use a config file.
    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....
    Restart and run the server
    sudo systemctl restart apache2

    64

    This website collects cookies to deliver better user experience

    How to set up and deploy a Django application on a Linux server