motorscript.com

Simple Django Deployment Cheat-sheet

Published:
Updated:
    Download form data
  • Domain Name:
  • User on Server :
  • User Password : RegenerateCopy
  • SSH Port :
  • Django Project Name :
  • Database Name :
  • Database User :
  • Database Password : RegenerateCopy

Create a sudo user

useradd -m user
echo user:Uq4KAxFZ1qhCk1h| chpasswd
usermod -aG sudo user
su user
Add user to sshd_configAllowUsers configuration if AllowUsers is used to allow specific user logins via SSH.

Setup Database

Install Postgresql:
apt-get install postgresql postgresql-contrib
Enable and start Postgresql
systemctl enable postgresql
systemctl start postgresql
Create database and role:
su - postgres
createdb db_name
echo "CREATE ROLE db_user WITH PASSWORD 'lyzuOaNAdgKfc4S';" | psql
echo "GRANT ALL PRIVILEGES ON DATABASE "db_name" to db_user;" | psql

Setup pushing via Git

cd
mkdir repo.git app conf logs media static
cd repo.git
git init --bare
git --bare update-server-info
git config core.bare false
git config receive.denycurrentbranch ignore
git config core.worktree /home/user/app/

cat > hooks/post-receive <<EOF
#!/bin/sh
git checkout -f
EOF

chmod +x hooks/post-receive

exit
Add this bare repo as a remote on local.
git remote add server user@awecode.com:/home/user/repo.git/
ssh-copy-id user@awecode.com
git push server --all

You may want to modify your git post-receive hook to run custom commands on the server, like running database migrations, notifying services of new deployments, pushing static files to CDN, etc.

Setup the Project

cd
virtualenv env -p python3
source env/bin/activate
cd app
pip install -r requirements/production.txt
./manage.py migrate
./manage.py collectstatic
Also, try running ./manage.py runserver to see if everything is all right.

Install Circus

apt install circus
                
 cat <<EOT >> /etc/systemd/system/circus.service
[Unit]
Description=Circus process manager
After=syslog.target network.target nss-lookup.target
[Service]
Type=simple
ExecReload=/usr/bin/circusctl reload
ExecStart=/usr/bin/circusd /etc/circus/circusd.ini
Restart=always
RestartSec=5
[Install]
WantedBy=default.target
EOT
                
systemctl --system daemon-reload
systemctl enable circus
systemctl start circus

Create circus configuration

cd
vim conf/circus.ini
[watcher:django_project]
cmd=chaussette --fd $(circus.sockets.esx) django_project.wsgi.application
#cmd=chaussette --fd $(circus.sockets.esx) --backend gevent django_project.wsgi.application
#cmd=chaussette --fd $(circus.sockets.esx) --backend meinheld django_project.wsgi.application
uid = user
endpoint_owner = user
use_sockets = True
numprocesses = 2 
virtualenv = /home/user/env/
copy_env = True
copy_path = True
stdout_stream.class = FileStream
stdout_stream.filename = /home/user/logs/django.log
stderr_stream.class = FileStream
stderr_stream.filename = /home/user/logs/django_err.log
stdout_stream.max_bytes = 1073741824
stdout_stream.backup_count = 3
stderr_stream.max_bytes = 1073741824
stderr_stream.backup_count = 3
working_dir = /home/user/app/

[socket:django_project]
path = /tmp/django_project.sock
family = AF_UNIX

[env:django_project]
PYTHONPATH=/home/user/app/

# For django-q
[watcher:django_project_q]
cmd = python manage.py qcluster
numprocesses = 1
working_dir = /home/user/app/
virtualenv = /home/user/env/
copy_env = True
copy_path = True
stdout_stream.class = FileStream
stdout_stream.filename = /home/user/logs/webapp_q.log
stdout_stream.max_bytes = 1073741824
stdout_stream.backup_count = 3
stderr_stream.class = FileStream
stderr_stream.filename = /home/user/logs/webapp_q_err.log
stderr_stream.max_bytes = 1073741824
stderr_stream.backup_count = 3

[env:django_project_q]
PYTHONPATH=/home/user/app/

Soft-link our configuration to circus conf.d directory

sudo ln -s /home/user/conf/circus.ini /etc/circus/conf.d/
circusctl reloadconfig

Install redis

apt install redis-server
systemctl enable redis
systemctl start redis

Install nginx

apt install nginx
systemctl enable nginx

Configure nginx with security headers

upstream django {
    server unix:/tmp/django_project;
}

# Redirect www.awecode.com to awecode.com
server {
    listen 80;
    server_name  www.awecode.com;
    return       301 https://awecode.com$request_uri;
}

server {
    listen 80;
    server_name awecode.com;

    #access_log /home/user/logs/nginx.access.log;
    error_log /home/user/logs/nginx.error.log;

    limit_conn conn_limit_per_ip 100;
    limit_req zone=req_limit_per_ip burst=100 nodelay;

    location /robots.txt {
        alias /home/user/static/robots.txt;
    }
    
    location /favicon.ico {
        alias /home/user/static/img/favicon.ico;
    }
    
    location ~ ^/(media|static)/  {
        root    /home/user/;
        expires 30d;
    }

    location / {
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Host $http_host;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_redirect off;
        proxy_pass http://django;
        client_max_body_size 50m;
        add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
        add_header X-Content-Type-Options nosniff;
        add_header X-XSS-Protection "1; mode=block";
        add_header X-Frame-Options SAMEORIGIN;
        # CSP allowing popular third party integrations
        add_header Content-Security-Policy "default-src 'self' ; script-src 'self' 'unsafe-inline' 'unsafe-eval' adservice.google.com adservice.google.com.np pagead2.googlesyndication.com cdn.awecode.com d31qbv1cthcecs.cloudfront.net www.google-analytics.com cdn.ravenjs.com connect.facebook.net platform.twitter.com apis.google.com www.google.com www.gstatic.com maps.googleapis.com; connect-src 'self' cdn.awecode.com googleads.g.doubleclick.net fonts.gstatic.com wss: cdn.awecode.com securepubads.g.doubleclick.net d5nxst8fruw4z.cloudfront.net sentry.io maps.gstatic.com www.google-analytics.com certify.alexametrics.com; img-src 'self' data: certify.alexametrics.com maps.gstatic.com maps.googleapis.com d5nxst8fruw4z.cloudfront.net cdn.awecode.com www.google-analytics.com stats.g.doubleclick.net ssl.gstatic.com csi.gstatic.com www.facebook.com syndication.twitter.com www.gravatar.com pagead2.googlesyndication.com; style-src 'self' 'unsafe-inline' fonts.googleapis.com cdn.awecode.com; font-src 'self' data: cdn.awecode.com fonts.gstatic.com; frame-src googleads.g.doubleclick.net www.youtube.com accounts.google.com content.googleapis.com www.facebook.com staticxx.facebook.com platform.twitter.com; manifest-src 'self'; worker-src 'self' fonts.gstatic.com";
    }
    
    # Prevent hidden files (beginning with a period) from being served
    location ~ /\. { access_log off; log_not_found off; deny all; }
}

Obtain SSL certificate with Certbot

apt-get install software-properties-common
add-apt-repository ppa:certbot/certbot
apt-get update
apt-get install python-certbot-nginx
apt-get install python-certbot-nginx
certbot --nginx

Check configuration and restart nginx

nginx -t
systemctl restart nginx