Deployment Instructions
Backend
Ubuntu Server using Nginx as a Reverse Proxy and Gunicorn as WSGI server
- First, download required packages (if you have some alreadt installed, you can omit them):
$ sudo apt update
$ sudo apt install python3-venv python3-dev libpq-dev postgresql postgresql-contrib nginx curl git
- Next, we will create the PostgreSQL database and user.
- Log into an interactive Postgres session, which we will use to set up our requirements, by typing:
$ sudo -u postgres psql
- Create the database for the app (I will call it ilmhub_db):
postgres=# CREATE DATABASE ilmhub_db;
- Next, create a database user. I will call mine ilmhub_admin with password 'password'. Make sure you use a secure password.
postgres=# CREATE USER ilmhub_admin WITH PASSWORD 'password';
- Next, set the default connection parameters for this user to values Django uses. This will speed up database operations.
postgres=# ALTER ROLE myprojectuser SET client_encoding TO 'utf8';
postgres=# ALTER ROLE myprojectuser SET default_transaction_isolation TO 'read committed';
postgres=# ALTER ROLE myprojectuser SET timezone TO 'UTC';
- Now, we give the new user database administrator privileges:
postgres=# GRANT ALL PRIVILEGES ON DATABASE ilmhub_db TO ilmhub_admin;
- Exit out of the PostgreSQL prompt by typing:
postgres=# \q
- Now, we clone the repository.
$ git clone git@github.com:UAlberta-CMPUT401/pronunciation-practice
- Create a virtual environment.
$ python3 -m venv venv
- Activate the virtual environment.
$ source venv/bin/activate
- Change directory to the repository.
$ cd pronunciation-practice
- Install the requirements.
$ pip3 install -r requirements.txt && pip3 install gunicorn
- Change directory to the django project's folder.
$ cd backend/IlmHub/
- Create a .env file in this directory.
$ nano .env
- Paste the following, replacing the {} with the info of the database and user created earlier:
SECRET_KEY={YOUR_SECRET_KEY}
DB_NAME={DATABASE_NAME}
DB_USER={DATABASE_USER}
DB_PASSWORD={DATABASE_USER_PASSWORD}
DB_HOST=localhost
DB_PORT=5432
DEBUG=FALSE
- To save the file, press Ctrl+S, and to exit press Ctrl+X.
- To generate a value for secret key, run the following command:
$ python3 -c "from django.core.management.utils import get_random_secret_key as grsk; print(grsk())"
Here is my output (yours will probably be different):
je8@jwb(^vc1=6j6%clyl^4x^cn5q(6ku&0cbd4+46&fp3@bt=
- If you use the same values I've used, your .env file should look something like this:
SECRET_KEY=je8@jwb(^vc1=6j6%clyl^4x^cn5q(6ku&0cbd4+46&fp3@bt=
DB_NAME=ilmhub_db
DB_USER=ilmhub_admin
DB_PASSWORD=password
DB_HOST=localhost
DB_PORT=5432
DEBUG=FALSE
- Now, change directory back to the backend folder.
$ cd ..
- We need to migrate our Django models to the PostgreSQL database. We can do so by running the following commands:
$ python3 manage.py makemigrations
$ python3 manage.py migrate
- We also need to create a superuser so we can manage the database from the admin page. To do so, run the following command and enter the required information
$ python manage.py createsuperuser
- Run the following command to collect static files:
$ python3 manage.py collectstatic
- Now, we set up the systemd Socket and Service Files for Gunicorn. Start by creating and opening a systemd socket file for Gunicorn with sudo privileges:
$ sudo nano /etc/systemd/system/gunicorn.socket
- Paste the following into the gunicorn.socket file.
[Unit]
Description=gunicorn socket
[Socket]
ListenStream=/run/gunicorn.sock
[Install]
WantedBy=sockets.target
- Now, we create and open a systemd service file for Gunicorn.
sudo nano /etc/systemd/system/gunicorn.service
- Paste the following into the gunicorn.service file. Make sure you change the {} below to reflect your system.
[Unit]
Description=gunicorn daemon
Requires=gunicorn.socket
After=network.target
[Service]
User={LINUX_USER}
Group=www-data
WorkingDirectory={ABSOLUTE_PATH_TO_REPOSITORY}/backend
ExecStart={ABSOLUTE_PATH_TO_VIRTUAL_ENVIRONMENT}/bin/gunicorn \
--access-logfile - \
--workers 3 \
--bind unix:/run/gunicorn.sock \
IlmHub.wsgi:application
[Install]
WantedBy=multi-user.target
- In my case, this is how my file looks like:
[Unit]
Description=gunicorn daemon
Requires=gunicorn.socket
After=network.target
[Service]
User=ubuntu
Group=www-data
WorkingDirectory=/home/ubuntu/pronunciation-practice/backend/
ExecStart=/home/ubuntu/venv/bin/gunicorn \
--access-logfile - \
--workers 3 \
--bind unix:/run/gunicorn.sock \
IlmHub.wsgi:application
[Install]
WantedBy=multi-user.target
- Now, we start and enable the Gunicorn socket.
$ sudo systemctl start gunicorn.socket
$ sudo systemctl enable gunicorn.socket
- Now, we check the status of our Gunicorn socket. We first run the following command:
$ sudo systemctl status gunicorn.socket
You should get an output similar to:
Output
● gunicorn.socket - gunicorn socket
Loaded: loaded (/etc/systemd/system/gunicorn.socket; enabled; vendor preset: enabled)
Active: active (listening) since Mon 2023-04-03 17:53:25 UTC; 5s ago
Triggers: ● gunicorn.service
Listen: /run/gunicorn.sock (Stream)
CGroup: /system.slice/gunicorn.socket
Apr 03 17:53:25 django systemd[1]: Listening on gunicorn socket.
- Now, check for the existence of the gunicorn.sock file within the /run directory:
$ file /run/gunicorn.sock
Output:
/run/gunicorn.sock: socket
- If you encountered an error, run the following command to check the logs:
$ sudo journalctl -u gunicorn.socket
- Let's check out the Gunicorn service.
$ sudo systemctl status gunicorn
Output:
○ gunicorn.service - gunicorn daemon
Loaded: loaded (/etc/systemd/system/gunicorn.service; disabled; vendor preset: enabled)
Active: inactive (dead)
TriggeredBy: ● gunicorn.socket
- Test the socket by typing in the following command:
$ curl --unix-socket /run/gunicorn.sock localhost
- If you receive some HTML output, then that means Gunicorn was started and is able to serve the application.
$ sudo systemctl status gunicorn
Output:
● gunicorn.service - gunicorn daemon
Loaded: loaded (/etc/systemd/system/gunicorn.service; disabled; vendor preset: enabled)
Active: active (running) since Mon 2022-04-18 17:54:49 UTC; 5s ago
TriggeredBy: ● gunicorn.socket
Main PID: 102674 (gunicorn)
Tasks: 4 (limit: 4665)
Memory: 94.2M
CPU: 885ms
CGroup: /system.slice/gunicorn.service
├─102674 /home/sammy/myprojectdir/myprojectenv/bin/python3 /home/sammy/myprojectdir/myprojectenv/bin/gunicorn --access-logfile - --workers 3 --bind unix:/run/gunicorn.sock myproject.wsgi:application
├─102675 /home/sammy/myprojectdir/myprojectenv/bin/python3 /home/sammy/myprojectdir/myprojectenv/bin/gunicorn --access-logfile - --workers 3 --bind unix:/run/gunicorn.sock myproject.wsgi:application
├─102676 /home/sammy/myprojectdir/myprojectenv/bin/python3 /home/sammy/myprojectdir/myprojectenv/bin/gunicorn --access-logfile - --workers 3 --bind unix:/run/gunicorn.sock myproject.wsgi:application
└─102677 /home/sammy/myprojectdir/myprojectenv/bin/python3 /home/sammy/myprojectdir/myprojectenv/bin/gunicorn --access-logfile - --workers 3 --bind unix:/run/gunicorn.sock myproject.wsgi:application
Apr 18 17:54:49 django systemd[1]: Started gunicorn daemon.
Apr 18 17:54:49 django gunicorn[102674]: [2022-04-18 17:54:49 +0000] [102674] [INFO] Starting gunicorn 20.1.0
Apr 18 17:54:49 django gunicorn[102674]: [2022-04-18 17:54:49 +0000] [102674] [INFO] Listening at: unix:/run/gunicorn.sock (102674)
Apr 18 17:54:49 django gunicorn[102674]: [2022-04-18 17:54:49 +0000] [102674] [INFO] Using worker: sync
Apr 18 17:54:49 django gunicorn[102675]: [2022-04-18 17:54:49 +0000] [102675] [INFO] Booting worker with pid: 102675
Apr 18 17:54:49 django gunicorn[102676]: [2022-04-18 17:54:49 +0000] [102676] [INFO] Booting worker with pid: 102676
Apr 18 17:54:50 django gunicorn[102677]: [2022-04-18 17:54:50 +0000] [102677] [INFO] Booting worker with pid: 102677
Apr 18 17:54:50 django gunicorn[102675]: - - [18/Apr/2022:17:54:50 +0000] "GET / HTTP/1.1" 200 10697 "-" "curl/7.81.0"
- If the output from
curl
orsystemctl status
indicate errors, check the logs by running:
$ sudo journalctl -u gunicorn
- If there are any problems and you edit your
/etc/systemd/system/gunicorn.service
, reload the daemon and restart the process to apply changes.
$ sudo systemctl daemon-reload
$ sudo systemctl restart gunicorn
- Now we configure Nginx. We first start by creating a new server block in Nginx's
sites-available
directory:
$ sudo nano /etc/nginx/sites-available/IlmHub
- Enter the following, changing the info as required:
server {
listen 80;
listen [::]:80;
server_name {SERVER_DOMAIN_OR_IP};
client_max_body_size 10G;
location = /favicon.ico { access_log off; log_not_found off; }
location /static/ {
root {PATH_TO_REPO}/backend;
}
location /media/ {
root {PATH_TO_REPO}/backend;
}
location / {
include proxy_params;
proxy_pass http://unix:/run/gunicorn.sock;
}
}
- This is how it looks for me (I am using an IPv6 address, hence the [] in the server IP):
$ server {
listen [::]:80;
server_name [2605:fd00:4:1001:f816:3eff:fec8:27a8];
client_max_body_size 10G;
location = /favicon.ico { access_log off; log_not_found off; }
location /static/ {
root /home/ubuntu/pronunciation-practice/backend;
}
location /media/ {
root /home/ubuntu/pronunciation-practice/backend;
}
location / {
include proxy_params;
proxy_pass http://unix:/run/gunicorn.sock;
}
}
- Save and close the file. Now, you can enable the file by linking it to the sites-enabled directory:
$ sudo ln -s /etc/nginx/sites-available/myproject /etc/nginx/sites-enabled
- Test your Nginx configuration for syntax errors:
$ sudo nginx -t
- If no errors are reported, restart Nginx by typing:
$ sudo systemctl restart nginx
- Finally, you need to open up your firewall to traffic on port 80:
$ sudo ufw allow 'Nginx Full'
Front-end
Heroku (Paid)
- To deploy on Heroku, first create a verified Heroku account from here.
- Install the Heroku CLI from Heroku CLI and login to your account and verify installation.
- Create a new app on the Heroku Dashboard from here
- Once you are done with the above steps, register the newly created app from heroku dashboard in the existing project github repo by using the command
heroku git:remote -a app-name
and then set buildpacks usingheroku buildpacks:set heroku/nodejs
- Commit and push your changes using
git commit -am "my commit"
andgit subtree push --prefix path/to/subdirectory heroku main
Bonus
: To setup auto deploy using github actions and heroku, checkout this.
Github Pages (Free)
- Install Github pages in
frontend
directory bynpm install gh-pages — save-dev
. - Add homepage property to package.json file, for github user site,
"homepage": "https://{organization}.github.io"
. Custom domains can also be added by"homepage": "https://testwebsite.com"
. - Add deploy scripts to package.json file.
"predeploy": "npm run build", "deploy": "gh-pages -d build"
npm run deploy
- Check Pages on github settings to see the published url. Remember to change source branch to gh-pages.
- To follow subdirectory troubleshooting, checkout this Stackoverflow answer
Bonus
- An alternate choice can be Netlify which is also free for public organization repos.