Skip to content

Instantly share code, notes, and snippets.

@ahmedsafadii
Forked from bradtraversy/django_deploy.md
Last active October 31, 2023 20:39
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ahmedsafadii/3140b6e2bcfc7ac223fd34eb7241d682 to your computer and use it in GitHub Desktop.
Save ahmedsafadii/3140b6e2bcfc7ac223fd34eb7241d682 to your computer and use it in GitHub Desktop.
Django Deployment - Digital Ocean

Django Deployment to Ubuntu 23.04

In this guide I will go through all the steps to create a VPS, secure it and deploy a Django application.

Any commands with "$" at the beginning run on your local machine and any "#" run when logged into the server

Create A Digital Ocean Droplet

Use this link to start your free trial with a $200 credit for 60 days.

Security & Access

Creating SSH keys

You can choose to create SSH keys to login if you want. If not, you will get the password sent to your email to login via SSH

To generate a key on your local machine

$ ssh-keygen

Hit enter all the way through and it will create a public and private key at

~/.ssh/id_rsa
~/.ssh/id_rsa.pub

You want to copy the public key (.pub file)

$ cat ~/.ssh/id_rsa.pub

Copy the entire output and add as an SSH key for Digital Ocean

Login To Your Server

If you setup SSH keys correctly the command below will let you right in. If you did not use SSH keys, it will ask for a password. This is the one that was mailed to you

$ ssh root@YOUR_SERVER_IP

Create a new user

It will ask for a password, use something secure. You can just hit enter through all the fields. I used the user "django" but you can use anything.

# adduser django

Give root privileges

# usermod -aG sudo django

SSH keys for the new user

Now we need to setup SSH keys for the new user. You will need to get them from your local machine

Exit the server

You need to copy the key from your local machine so either exit or open a new terminal

# exit

You can generate a different key if you want but we will use the same one so lets output it, select it and copy it

$ cat ~/.ssh/id_rsa.pub

Log back into the server

$ ssh root@YOUR_SERVER_IP

Add SSH key for new user

Navigate to the new users home folder and create a file at '.ssh/authorized_keys' and paste in the key

# cd /home/django
# mkdir .ssh
# cd .ssh
# nano authorized_keys
Paste the key and hit "ctrl-x", hit "y" to save and "enter" to exit

Login as new user

You should now get let in as the new user

$ ssh django@YOUR_SERVER_IP

Disable root login

# sudo nano /etc/ssh/sshd_config

Change the following

PermitRootLogin no
PasswordAuthentication no
ChallengeResponseAuthentication no

Reload sshd service

# sudo systemctl reload ssh

Simple Firewall Setup

See which apps are registered with the firewall

# sudo ufw app list

Allow OpenSSH

### sudo ufw allow OpenSSH

Enable firewall

# sudo ufw enable

To check status

# sudo ufw status

We are now done with access and security and will move on to installing software

Software

Update packages

# sudo apt update
# sudo apt upgrade

Install Python 3, Postgres & NGINX

# sudo apt install python3-pip python3.11-dev libpq-dev postgresql postgresql-contrib nginx curl

Postgres Database & User Setup

# sudo -u postgres psql

You should now be logged into the pg shell

Create a database

CREATE DATABASE app_prod;

Create user

CREATE USER forge WITH PASSWORD 'pass2020';

Set default encoding, tansaction isolation scheme (Recommended from Django)

ALTER ROLE forge SET client_encoding TO 'utf8';
ALTER ROLE forge SET default_transaction_isolation TO 'read committed';
ALTER ROLE forge SET timezone TO 'UTC';

Give User access to database

GRANT USAGE ON SCHEMA public TO forge;
GRANT ALL PRIVILEGES ON DATABASE app_prod TO forge;
ALTER DATABASE app_prod OWNER TO forge;

Quit out of Postgres

\q

Make python3 is the default python for your system to avoid use wrong enviroment

sudo apt install python-is-python3
alias python=python3

Vitrual Environment

You need to install the python3.11-venv package

# sudo apt install python3.11-venv

Create project directory

# mkdir dj_apps
# cd dj_apps

Create venv

# python -m venv ./venv

Activate the environment

# source venv/bin/activate

Git & Upload

Pip dependencies

From your local machine, create a requirements.txt with your app dependencies. Make sure you push this to your repo

$ pip freeze > requirements.txt

Create a new repo and push to it

Clone the project into the app folder on your server (Either HTTPS or setup SSH keys)

# git clone https://github.com/yourgithubname/app_prod.git

Install pip modules from requirements

You could manually install each one as well

# pip install -r requirements.txt

Local Settings Setup

Add code to your settings.py file and push to server

try:
    from .local_settings import *
except ImportError:
    pass

Create a file called local_settings.py on your server along side of settings.py and add the following

  • SECRET_KEY
  • ALLOWED_HOSTS
  • DATABASES
  • DEBUG

Run Migrations

# python manage.py makemigrations
# python manage.py migrate

Create super user

# python manage.py createsuperuser

Create static files

python manage.py collectstatic

Create exception for port 8000

# sudo ufw allow 8000

Run Server

# python manage.py runserver 0.0.0.0:8000

Test the site at YOUR_SERVER_IP:8000

Add some data in the admin area

Gunicorn Setup

Install gunicorn

# pip install gunicorn

Add to requirements.txt

# pip freeze > requirements.txt

Test Gunicorn serve

# gunicorn --bind 0.0.0.0:8000 app.wsgi

Your images, etc will be gone

Stop server & deactivate virtual env

ctrl-c
# deactivate

Open gunicorn.socket file

# sudo nano /etc/systemd/system/app1-gunicorn.socket

Copy this code, paste it in and save

[Unit]
Description=gunicorn socket

[Socket]
ListenStream=/run/app1-gunicorn.sock

[Install]
WantedBy=sockets.target

Open gunicorn.service file

# sudo nano /etc/systemd/system/app1-gunicorn.service

Copy this code, paste it in and save

[Unit]
Description=gunicorn daemon
Requires=app1-gunicorn.socket
After=network.target

[Service]
User=django
Group=www-data
WorkingDirectory=/home/django/pyapps/app_prod
ExecStart=/home/django/pyapps/venv/bin/gunicorn \
          --access-logfile - \
          --workers 3 \
          --bind unix:/run/app1-gunicorn.sock \
          app.wsgi:application

[Install]
WantedBy=multi-user.target

Start and enable Gunicorn socket

# sudo systemctl start app1-gunicorn.socket
# sudo systemctl enable app1-gunicorn.socket

Check status of guinicorn

# sudo systemctl status app1-gunicorn.socket

Check the existence of gunicorn.sock

# file /run/app1-gunicorn.sock

NGINX Setup

Origin pull ca

sudo nano /etc/ssl/certs/origin-pull-ca.pem 

And then paste the following cert

-----BEGIN CERTIFICATE-----
MIIGCjCCA/KgAwIBAgIIV5G6lVbCLmEwDQYJKoZIhvcNAQENBQAwgZAxCzAJBgNV
BAYTAlVTMRkwFwYDVQQKExBDbG91ZEZsYXJlLCBJbmMuMRQwEgYDVQQLEwtPcmln
aW4gUHVsbDEWMBQGA1UEBxMNU2FuIEZyYW5jaXNjbzETMBEGA1UECBMKQ2FsaWZv
cm5pYTEjMCEGA1UEAxMab3JpZ2luLXB1bGwuY2xvdWRmbGFyZS5uZXQwHhcNMTkx
MDEwMTg0NTAwWhcNMjkxMTAxMTcwMDAwWjCBkDELMAkGA1UEBhMCVVMxGTAXBgNV
BAoTEENsb3VkRmxhcmUsIEluYy4xFDASBgNVBAsTC09yaWdpbiBQdWxsMRYwFAYD
VQQHEw1TYW4gRnJhbmNpc2NvMRMwEQYDVQQIEwpDYWxpZm9ybmlhMSMwIQYDVQQD
ExpvcmlnaW4tcHVsbC5jbG91ZGZsYXJlLm5ldDCCAiIwDQYJKoZIhvcNAQEBBQAD
ggIPADCCAgoCggIBAN2y2zojYfl0bKfhp0AJBFeV+jQqbCw3sHmvEPwLmqDLqynI
42tZXR5y914ZB9ZrwbL/K5O46exd/LujJnV2b3dzcx5rtiQzso0xzljqbnbQT20e
ihx/WrF4OkZKydZzsdaJsWAPuplDH5P7J82q3re88jQdgE5hqjqFZ3clCG7lxoBw
hLaazm3NJJlUfzdk97ouRvnFGAuXd5cQVx8jYOOeU60sWqmMe4QHdOvpqB91bJoY
QSKVFjUgHeTpN8tNpKJfb9LIn3pun3bC9NKNHtRKMNX3Kl/sAPq7q/AlndvA2Kw3
Dkum2mHQUGdzVHqcOgea9BGjLK2h7SuX93zTWL02u799dr6Xkrad/WShHchfjjRn
aL35niJUDr02YJtPgxWObsrfOU63B8juLUphW/4BOjjJyAG5l9j1//aUGEi/sEe5
lqVv0P78QrxoxR+MMXiJwQab5FB8TG/ac6mRHgF9CmkX90uaRh+OC07XjTdfSKGR
PpM9hB2ZhLol/nf8qmoLdoD5HvODZuKu2+muKeVHXgw2/A6wM7OwrinxZiyBk5Hh
CvaADH7PZpU6z/zv5NU5HSvXiKtCzFuDu4/Zfi34RfHXeCUfHAb4KfNRXJwMsxUa
+4ZpSAX2G6RnGU5meuXpU5/V+DQJp/e69XyyY6RXDoMywaEFlIlXBqjRRA2pAgMB
AAGjZjBkMA4GA1UdDwEB/wQEAwIBBjASBgNVHRMBAf8ECDAGAQH/AgECMB0GA1Ud
DgQWBBRDWUsraYuA4REzalfNVzjann3F6zAfBgNVHSMEGDAWgBRDWUsraYuA4REz
alfNVzjann3F6zANBgkqhkiG9w0BAQ0FAAOCAgEAkQ+T9nqcSlAuW/90DeYmQOW1
QhqOor5psBEGvxbNGV2hdLJY8h6QUq48BCevcMChg/L1CkznBNI40i3/6heDn3IS
zVEwXKf34pPFCACWVMZxbQjkNRTiH8iRur9EsaNQ5oXCPJkhwg2+IFyoPAAYURoX
VcI9SCDUa45clmYHJ/XYwV1icGVI8/9b2JUqklnOTa5tugwIUi5sTfipNcJXHhgz
6BKYDl0/UP0lLKbsUETXeTGDiDpxZYIgbcFrRDDkHC6BSvdWVEiH5b9mH2BON60z
0O0j8EEKTwi9jnafVtZQXP/D8yoVowdFDjXcKkOPF/1gIh9qrFR6GdoPVgB3SkLc
5ulBqZaCHm563jsvWb/kXJnlFxW+1bsO9BDD6DweBcGdNurgmH625wBXksSdD7y/
fakk8DagjbjKShYlPEFOAqEcliwjF45eabL0t27MJV61O/jHzHL3dknXeE4BDa2j
bA+JbyJeUMtU7KMsxvx82RmhqBEJJDBCJ3scVptvhDMRrtqDBW5JShxoAOcpFQGm
iYWicn46nPDjgTU0bX1ZPpTpryXbvciVL5RkVBuyX2ntcOLDPlZWgxZCBp96x07F
AnOzKgZk4RzZPNAxCXERVxajn/FLcOhglVAKo5H0ac+AitlQ0ip55D2/mf8o72tM
fVQ6VpyjEXdiIXWUq/o=
-----END CERTIFICATE-----

Create project folder

# sudo nano /etc/nginx/sites-available/app1_project

Copy this code and paste into the file

server {
        listen 80;
        server_name www.domainname.com domainname.com;
        return 301 https://domainname.com$request_uri;
}

server {

        listen 443 ssl http2;
        server_name domainname.com;

        if ($host = www.domainname.com) {
                return 301 https://domainname.com$request_uri;
        }

        ssl_verify_client on;
        ssl_certificate  /etc/ssl/certs/cert-name.pem;
        ssl_certificate_key  /etc/ssl/private/key-name.pem;
        ssl_client_certificate  /etc/ssl/certs/origin-pull-ca.pem;

        access_log /home/django/app/logs/nginx/access.log;
        error_log /home/django/app/logs/nginx/error.log;

        location = /favicon.ico { access_log off; log_not_found off; }

        location /static/ {
                root /home/django/apps/app_prod;
        }

        location /content/ {
                root /home/django/apps/app_prod;
        } 

        location / {
                include proxy_params;
                proxy_pass http://unix:/run/app1-gunicorn.sock;
                proxy_read_timeout 60s;
                proxy_connect_timeout 60s;
                proxy_send_timeout 60s;
        }
        
}

Enable the file by linking to the sites-enabled dir

# sudo ln -s /etc/nginx/sites-available/app_project /etc/nginx/sites-enabled

Test NGINX config

# sudo nginx -t

Restart NGINX

# sudo systemctl restart nginx

Remove port 8000 from firewall and open up our firewall to allow normal traffic on port 80

# sudo ufw delete allow 8000
# sudo ufw allow 'Nginx Full'

You will probably need to up the max upload size to be able to create listings with images

Open up the nginx conf file

# sudo nano /etc/nginx/nginx.conf

Add this to the http{} area

client_max_body_size 20M;

Reload NGINX

# sudo systemctl restart nginx

Domain Setup

Go to your domain registrar and create the following a record

@  A Record  YOUR_IP_ADDRESS
www  CNAME  example.com

Go to local_settings.py on the server and change "ALLOWED_HOSTS" to include the domain

ALLOWED_HOSTS = ['IP_ADDRESS', 'example.com', 'www.example.com']

Reload NGINX & Gunicorn

# sudo systemctl restart nginx
# sudo systemctl restart gunicorn

Django Settings

requirements.txt

Django==4.2.6
psycopg2==2.9.9
psycopg2-binary==2.9.9
Pillow==10.0.1
django-environ==0.11.2

local_settings.py

import environ

env = environ.Env(
    DEBUG=(bool, False),
    ALLOWED_HOSTS=(list, []),
)

environ.Env.read_env()


DEBUG = env("DEBUG")

SECRET_KEY = env("SECRET_KEY")


ALLOWED_HOSTS = env("ALLOWED_HOSTS")


# DATABASE
DATABASES = {
    "default": env.db(),
}

.env

DATABASE_URL=psql://username:password@127.0.0.1:5432/databasename

DEBUG=True
SECRET_KEY='emz$+i(b!)9@^o#%y8gl6jbzmbxm_zo4qmo13^28l(0ma^gl+='

ALLOWED_HOSTS=*

static settings

settings.py

STATIC_ROOT = os.path.join(BASE_DIR, "static")
STATIC_URL = "/static/"

MEDIA_ROOT = os.path.join(BASE_DIR, "content")
MEDIA_URL = "/content/"

urls.py

if DEBUG:
    urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

Docker Commands

Install Docker

$ sudo apt update
$ sudo apt install apt-transport-https ca-certificates curl software-properties-common
$ curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
$ sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu bionic stable"
$ sudo apt update
$ apt-cache policy docker-ce # shows versions
$ sudo apt install docker-ce # checks status

Install Docker-Compose

$ sudo curl -L https://github.com/docker/compose/releases/download/1.21.2/docker-compose-`uname -s`-`uname -m` -o /usr/local/bin/docker-compose
$ sudo chmod +x /usr/local/bin/docker-compose
$ docker-compose --version # check version

Cookiecutter

Install

Use this link to install cookiecutter.

Use this link to install Docker and docker-compose installed

Commands

Deploy django using docker

docker-compose -f local.yml up --build -d
docker-compose -f production.yml up --build -d

Django commands

docker-compose -f local.yml run --rm django python manage.py createsuperuser
docker-compose -f production.yml run --rm django python manage.py makemigrations
docker-compose -f production.yml run --rm django python manage.py migrate
docker-compose -f production.yml run --rm django python manage.py collectstatic

For logs

docker-compose -f production.yml logs

To apply changes and pull it again

docker-compose -f production.yml down
git pull # pull your changes
docker-compose -f production.yml up --build -d

To use AWS S3 for serving media files, use this guide and place your AWS keys into the .env/.production/.django file.

Thank you.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment