Dynamic applications require a better deployment process. Let's use Python's Fabric to handle deployment over SSH.
This SSH task runner can handle running commands locally and remotely to make a killer deployment process.### Setup I have a PHP (laravel) application to use, running on a local vagrant server.
We have a:
- Vagrant Server
- "Production" remote server
The production server has the deployed static site from previous videos.
A new application to deploy is on GitHub. This is a PHP application based on the framework Laravel. We want:
- Vagrant server to SSH into production server
- Production server updates from GitHub - it needs to connect to GitHub
Local Vagrant server has a ~/.ssh/config
file setup (along with ssh keys) to connect to the remote server:
Host deploy-ex
HostName 104.236.85.162
User deployer
IdentityFile ~/.ssh/id_deployex
IdentitiesOnly yes
The remote server user deployer
has the id_deployex.pub
key in the ~/.ssh/authorized_keys
file.
The remote server contains a public/private key pair. The public key is copied into GitHub as a deploy key for our application repository. This lets our server push/pull to/from our application GitHub repository.
Install Fabric and Dependencies
sudo apt-get install -y python-pip python-dev
sudo pip install virtualenv
Create a Python virtual environment to run/install Fabric, named "dep-env".
cd /vagrant
virtualenv dep-env
cd dep-env
source bin/activate
Install fabric into this environment:
pip install fabric
Create the fab file to define our tasks:
cd /vagrant/application
vim fabfile.py
Here is the fabfile.py
file:
from __future__ import with_statement
from fabric.api import local, env, settings, abort, run, cd
from fabric.contrib.console import confirm
import time
env.use_ssh_config = True
env.hosts = ['deploy-ex']
# env.hosts = ['104.236.85.162']
# env.user = 'deployer'
# env.key_filename = '~/.ssh/id_deployex'
code_dir='/var/www/deploy-stage'
app_dir='/var/www/application'
repo='git@github.com:Servers-for-Hackers/deploy-ex.git'
timestamp="release_%s" % int(time.time() * 1000)
def deploy():
fetch_repo()
run_composer()
update_permissions()
update_symlinks()
def fetch_repo():
with cd(code_dir):
with settings(warn_only=True):
run("mkdir releases")
with cd("%s/releases" % code_dir):
run("git clone %s %s" % (repo, timestamp))
def run_composer():
with cd("%s/releases/%s" % (code_dir, timestamp)):
run("composer install --prefer-dist")
def update_permissions():
with cd("%s/releases/%s" % (code_dir, timestamp)):
run("chgrp -R www-data .")
run("chmod -R ug+rwx .")
def update_symlinks():
with cd(code_dir):
run("ln -nfs %s %s" % (code_dir+'/releases/'+timestamp, app_dir))
run("chgrp -h www-data %s" % app_dir)
We include needed Python libraries, and enviroment/variables.
The env.use_ssh_config
directive will tell Python to use the ~/.ssh/config
file in conjunction with the env.hosts
variable (which is an array of hosts).
Each function here is a Fabric task we can run. The deploy
task (function) will use each of the other functions in order to deploy the site.
The with settings(warn_only=True):
tells Fabric to warn on an error, but don't stop execution. We want this when it creates the directory releases
, which will already exist after the first run of this task.
This deployment will clone the repository newly on each deployment. It will then switch out the newly pulled repository with the older site files. It rotates the old files out and the new files in using Symlinks.
We can run this using the fab
command:
# From our local computer
# to run the deploy task from the fabfile
# found in this current directory
fab deploy
Test Deploying Changes
After our initial deployment, we test add a route to the Laravel application in the app/routes.php
file.
# Add this route
Route::get('/test', function()
{
Return Request::header();
});
This shows all the headers of the HTTP request made to this URL. We can see it work locally, but then we can simply deploy this new update:
# Update Git repository
git commit --all
git commit -m "new route shows http request headers"
git push origin master
# Deploy using fabric:
fab deploy
Resources
If you're curious, this is the Nginx configuration used to host the PHP application:
server {
listen 80;
root /var/www/application/public;
index index.html index.htm index.php;
# Make site accessible from ...
server_name 104.236.85.162.xip.io;
access_log /var/log/nginx/app.com-access.log;
error_log /var/log/nginx/app.com-error.log error;
charset utf-8;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location = /favicon.ico { log_not_found off; access_log off; }
location = /robots.txt { access_log off; log_not_found off; }
error_page 404 /index.php;
# pass the PHP scripts to php5-fpm
# Note: .php$ is susceptible to file upload attacks
# Consider using: "location ~ ^/(index|app|app_dev|config).php(/|$) {"
location ~ .php$ {
try_files $uri =404;
fastcgi_split_path_info ^(.+.php)(/.+)$;
# With php5-fpm:
fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
include fastcgi_params;
fastcgi_param LARA_ENV production; # Environment variable for Laravel
}
# Deny .htaccess file access
location ~ /\.ht {
deny all;
}
}