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
Use this link to start your free trial with a $200 credit for 60 days.
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
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
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
# usermod -aG sudo django
Now we need to setup SSH keys for the new user. You will need to get them from your local machine
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
$ ssh root@YOUR_SERVER_IP
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
You should now get let in as the new user
$ ssh django@YOUR_SERVER_IP
# sudo nano /etc/ssh/sshd_config
PermitRootLogin no
PasswordAuthentication no
ChallengeResponseAuthentication no
# sudo systemctl reload ssh
See which apps are registered with the firewall
# sudo ufw app list
Allow OpenSSH
### sudo ufw allow OpenSSH
# sudo ufw enable
# sudo ufw status
We are now done with access and security and will move on to installing software
# sudo apt update
# sudo apt upgrade
# sudo apt install python3-pip python3.11-dev libpq-dev postgresql postgresql-contrib nginx curl
# sudo -u postgres psql
You should now be logged into the pg shell
CREATE DATABASE app_prod;
CREATE USER forge WITH PASSWORD 'pass2020';
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';
GRANT USAGE ON SCHEMA public TO forge;
GRANT ALL PRIVILEGES ON DATABASE app_prod TO forge;
ALTER DATABASE app_prod OWNER TO forge;
\q
sudo apt install python-is-python3
alias python=python3
You need to install the python3.11-venv package
# sudo apt install python3.11-venv
# mkdir dj_apps
# cd dj_apps
# python -m venv ./venv
# source venv/bin/activate
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
# git clone https://github.com/yourgithubname/app_prod.git
You could manually install each one as well
# pip install -r requirements.txt
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
# python manage.py makemigrations
# python manage.py migrate
# python manage.py createsuperuser
python manage.py collectstatic
# sudo ufw allow 8000
# python manage.py runserver 0.0.0.0:8000
Add some data in the admin area
Install gunicorn
# pip install gunicorn
Add to requirements.txt
# pip freeze > requirements.txt
# gunicorn --bind 0.0.0.0:8000 app.wsgi
Your images, etc will be gone
ctrl-c
# deactivate
# sudo nano /etc/systemd/system/app1-gunicorn.socket
[Unit]
Description=gunicorn socket
[Socket]
ListenStream=/run/app1-gunicorn.sock
[Install]
WantedBy=sockets.target
# sudo nano /etc/systemd/system/app1-gunicorn.service
[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
# sudo systemctl start app1-gunicorn.socket
# sudo systemctl enable app1-gunicorn.socket
# sudo systemctl status app1-gunicorn.socket
# file /run/app1-gunicorn.sock
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-----
# sudo nano /etc/nginx/sites-available/app1_project
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;
}
}
# sudo ln -s /etc/nginx/sites-available/app_project /etc/nginx/sites-enabled
# sudo nginx -t
# sudo systemctl restart nginx
# sudo ufw delete allow 8000
# sudo ufw allow 'Nginx Full'
Open up the nginx conf file
# sudo nano /etc/nginx/nginx.conf
client_max_body_size 20M;
# sudo systemctl restart nginx
Go to your domain registrar and create the following a record
@ A Record YOUR_IP_ADDRESS
www CNAME example.com
ALLOWED_HOSTS = ['IP_ADDRESS', 'example.com', 'www.example.com']
# sudo systemctl restart nginx
# sudo systemctl restart gunicorn
Django==4.2.6
psycopg2==2.9.9
psycopg2-binary==2.9.9
Pillow==10.0.1
django-environ==0.11.2
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(),
}
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=*
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)
$ 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
$ 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
Use this link to install cookiecutter.
Use this link to install Docker and docker-compose installed
docker-compose -f local.yml up --build -d
docker-compose -f production.yml up --build -d
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
docker-compose -f production.yml logs
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.