Skip to content

Instantly share code, notes, and snippets.

@josephmancuso
Last active April 21, 2020 06:28
Show Gist options
  • Save josephmancuso/08c15c863707bee670744d47d183df43 to your computer and use it in GitHub Desktop.
Save josephmancuso/08c15c863707bee670744d47d183df43 to your computer and use it in GitHub Desktop.
Deploy a Masonite application through a GitHub action

Instructions

This is a gist containing several files needed to get Masonite automatically deploying to your servers via GitHub pushes (or releases)

NOTE: This script will have a downtime of a few seconds between deployments. If your application requires no downtime you can see this GIST here for a bit more complex GIST for getting to zero downtime deployments through the use of uWSGI, unix sockets and managed config files

Requirements

  • NGINX installed (may or may not be fully configured)
  • Python 3 installed and everything needed to run a Masonite application (see Masonite documentation for requirements)
  • Linux packages installed required to install Python packages (see Masonite documentation again)
  • Git installed
  • SSH credentials to connect to the server you are deploying to
  • Need to have the gunicorn requirement in your requirements.txt file

Instructions

First things first. The first thing you need to do is have NGINX installed. This Gist assumes you have that installed already.

At high level there are 3 things we have to do:

  1. We need to make sure our NGINX config is setup.
  2. We need to make sure we have a config file for our actual application
  3. We need to get the workflow in our application so GitHub will know to run it on certain actions (pushing or releasing)

Step 1 - Setting up NGINX config file

First we need to locate where our NGINX config file lives. Simply run this:

$ nginx -t
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful

When you get the output, you are looking for the configuration file. Both directories should be the same.

We can view and edit this file by doing:

$ nano /etc/nginx/nginx.conf

If you already have an NGINX config file then you may not want to override the existing file. The real important part is are these 2 lines in this block:

    ##
    # Virtual Host Configs
    ##

    include /etc/nginx/conf.d/*.conf;
    include /etc/nginx/sites-enabled/*;

This will tell NGINX to additionally load config files from these directories. We will use the bottom directory to put the app-config.conf file in this gist. If you already have these 2 lines (or lines similiar to these) then you are good. You can exit out by hitting CTRL+X. If you get some kind of prompt that says "Save modified buffer? (Y/n)" just hit "Y" and hit enter. This just tells nano to save the file. If you don't want to save it then enter n and hit enter.

Step 2 - Setting up the application config

Once done we need to then go to one of those directories. It really doesn't matter which one but let's pick the bottom one

$ cd /etc/nginx/sites-enabled

We can then make our config file. You can name this whatever you want. Maybe its the name of your application:

$ nano exampleapp.conf

This should open an empty editor. You can then paste in app-config.conf you see in this Gist.

Modifying the file

You may need to make some modifications to it.

Going from top to bottom, we should be listening on port 80.

The next line says server_name. If you put your IP address then it will work when you go to http://17.62.11.87. What you most likely want is a domain name so you can change it to a domain name instead.

The next is location /. We can leave that as is

The next line is include uwsgi_params;. leave that as well.

The next line says proxy_pass http://127.0.0.1:8000;. everything here can stay but you may want to modify the port. You will be running your application on a specific port with gunicorn (or a related wsgi server) and NGINX needs to know which port you are going to do that on.

All said and done you might have something like looks like this:

## /etc/nginx/sites-enabled/app-config.conf
## Name this file whatever you want. Just make sure it goes in the /etc/nginx/sites-enabled/ directory

server {
    listen 80;
-    server_name 17.62.11.87;
+    server_name subdomain.example.com;


    location / {
         include uwsgi_params;
-        proxy_pass http://127.0.0.1:8000;
+        proxy_pass http://127.0.0.1:8001;

    }
}

Once done you can back out of nano again.

Reload NGINX

At this point you may need to reload nginx. To do that just run:

nginx -s reload

Step 3 - Adding the workflow to your project

The GitHub workflow action

The last stop now that the server is up and running is you will need to get the workflow into your project so GitHub will pick up on it and run it on various actions like pushing or releasing.

You can do this by starting in your project (or GitHub repo) and create a .github/workflows/deploy.yml file. Then copy and paste the workflow to that file (the masonite-deploy.yml file in this Gist). Commit the change and push it up to the origin repository on GitHub.

The secrets

Next we need to configure some github secrets. You can go to your repo and look in the top right of the page. You should see "Setting". Then go to "Secrets" on the left vertical navigation bar near the bottom.

You'll need to add these secrets:

HOST: the server IP address you want to deploy to

USERNAME: the username of the ssh user

PASSWORD: the password of the ssh user

PORT: The port for ssh (usually 22)

ENV: A list of environment variables for your project. This should just be a list of rows with key value pairs like this:

KEY=123
APP_DEBUG=True
ENV3=value

Done

Once done the GitHub action will run when you push (or release depending on what you set at the top, see the comment there). Feel free to modify the action if you want. There are comments next to each line to explain what they are doing.

## /etc/nginx/sites-enabled/app-config.conf
## Name this file whatever you want. Just make sure it goes in the /etc/nginx/sites-enabled/ directory
server {
listen 80;
server_name 17.62.11.87;
location / {
include uwsgi_params;
proxy_pass http://127.0.0.1:8000;
}
}
name: Deployment
on: [push] # Change this line to "on: [release]" if you just want it on GitHub releases
jobs:
build:
name: Deploy
runs-on: ubuntu-latest
steps:
- name: executing remote ssh commands using password
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.HOST }}
username: ${{ secrets.USERNAME }}
password: ${{ secrets.PASSWORD }}
port: ${{ secrets.PORT }}
script: |
mkdir -p /srv/sites/basic # Make the directory if it doesnt exist
cd /srv/sites/basic # Go to that directory
git clone https://github.com/${{ github.repository }}.git ${{ github.sha }} # Clone the current repo
fuser -k 8001/tcp # Kill the application already running on 8001 (current production app)
shopt -s extglob # enable the command below this one
rm -rfv !("${{ github.sha }}") # Delete all directories except for the one we just created (old deployments)
cd ${{ github.sha }} # Go to the directory we just made (current deployment)
python3 -m venv /venvs/basic # Make our virtual environment if it doesn't exist
source /venvs/basic/bin/activate # Activate our virtual Environment
pip install -r requirements.txt # Install our dependencies
echo "${{ secrets.ENV }}" >> .env
set -m; nohup gunicorn wsgi:application -b 127.0.0.1:8001 &> /dev/null & # run gunicorn as a daemon
## /etc/nginx/nginx.conf
user root;
worker_processes auto;
pid /run/nginx.pid;
include /etc/nginx/modules-enabled/*.conf;
events {
worker_connections 768;
# multi_accept on;
}
http {
##
# Basic Settings
##
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
types_hash_max_size 2048;
server_tokens on;
# server_names_hash_bucket_size 64;
# server_name_in_redirect off;
include /etc/nginx/mime.types;
default_type application/octet-stream;
##
# SSL Settings
##
ssl_protocols TLSv1 TLSv1.1 TLSv1.2; # Dropping SSLv3, ref: POODLE
ssl_prefer_server_ciphers on;
##
# Logging Settings
##
access_log /var/log/nginx/access.log;
error_log /var/log/nginx/error.log;
##
# Gzip Settings
##
gzip on;
# gzip_vary on;
# gzip_proxied any;
# gzip_comp_level 6;
# gzip_buffers 16 8k;
# gzip_http_version 1.1;
# gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss;
##
# Virtual Host Configs
##
include /etc/nginx/conf.d/*.conf;
include /etc/nginx/sites-enabled/*;
}
#mail {
# # See sample authentication script at:
# # http://wiki.nginx.org/ImapAuthenticateWithApachePhpScript
#
# # auth_http localhost/auth.php;
# # pop3_capabilities "TOP" "USER";
# # imap_capabilities "IMAP4rev1" "UIDPLUS";
#
# server {
# listen localhost:110;
# protocol pop3;
# proxy on;
# }
#
# server {
# listen localhost:143;
# protocol imap;
# proxy on;
# }
#}
@mitchdennett
Copy link

project.ini

[uwsgi]
module = wsgi
callable = application

master = true
processes = 5

socket = project.sock
chmod-socket = 660
vacuum = true

die-on-term = true

@mitchdennett
Copy link

project.service - placed in /etc/systemd/system/project.service

[Unit]
Description=uWSGI instance to server mylodocs
After=network.target

[Service]
User=username
Group=www-data
WorkingDirectory=/home/username/project
Environment="PATH=/home/username/project/venv/bin"
ExecStart=/home/username/project/venv/bin/uwsgi --ini project.ini

[Install]
WantedBy=multi-user.target

@mitchdennett
Copy link

sudo systemctl start project
sudo systemctl enable project

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