• Posted on January 14, 2018

How to Separately Handle Multiple Website Deployments

Uploading website files routinely can become a chore, and no one wants that. Queue a deployment script, at the very least, it’s a script that uploads files to the server, but can be as advanced as verifying files once uploaded, run code tests, and update database structures along. There are two different methods to run a deployment script. First being automated deployment with continuous integration systems to a staging or production servers. The second method is to manually run the deployment script when you’re ready to upload changes onto the servers. It’s not uncommon to see both methods used, with automated deployment to a staging server and manual deployment to the production server.

This post I’m going to show you the very basic deployment script I use for my static websites and simple PHP websites that don’t have a database or configuration files. I’m also going to show you how I keep all of my website credentials separate by creating multiple user accounts on Linux. The reason to separate each website into a different user is to easily allow each site to have its own SSH key pair and environment variables.

Multiple Linux Accounts

When you have more than one website, you need a method to store different login credentials, ports, server address, and destination paths for each website. It’s bad form to store this information inside of scripts themselves and really bad practice to commit credentials into code repositories. These problems can be solved by storing credentials inside environment variables.

For my setup, all of my websites are in a development directory and each website has its own repository and Linux account. Each account uses their ~/.profile file to store and set environment variables for the build script along with their own SSH key for logging into the remote server. When I’m ready to upload a website, I run the command su - <username> to sign into the website’s account, then I pull the changes from my code repository, and finally run the build and deployment script.

$ useradd --create-home --home-dir /home/dev_users/website_name --shell /bin/bash website_name

If you don’t want to create a lot of Linux accounts or if are using a different operating system, you could create a bash file that is ignored by your repository that contains the variables for your server. The deployment script would then use source <filename> to run the bash file to set new the variables. As for SSH keys, you can create a SSH folder one directory up from your code repository to isolate SSH authorization keys. You will then need to add additional parameters to your SSH call to set the IdentityFile and UserKnownHostsFile configuration variables.

Creating SSH Keys for Password-less Logins

Having to enter passwords to run your deployment script is bothersome. The most secure method is to generate a SSH key pair and upload the public key to your web server. When generating the key, you can decide if you want the private key to be encrypted with a password or you can choose to keep the private key password free.

$ ssh-keygen -t rsa

You can now use ssh-copy-id or manually upload the public key to your server’s control panel or to the server itself. The key now allows you to run ssh without having to enter the servers password. If you opted to set a password when generating the key, you will still be asked to enter a password.

Rsync Deployment Script

As for the basic deployment script I use for my static websites, the script uses rsync and its support for SSH to connect and upload all changed files to the server. Rsync is also setup to delete files from the server that the source directory no longer has. This optimizes the time it takes to upload files by only uploading files that have moved or changed.

The deployment script requires four environment variables to function properly. To ensure the four required environment variables are set (SSH_HOST, SSH_USER, SSH_DIR, and SSH_PORT), the script loops through the required variables and checks to see if the variable length is zero. These variables in my situation are configured in the ~/.profile.

# Configures environment variables for automatic deployment

export LANGUAGE="en"
export LANG="C"
export LC_MESSAGES="C"

export SSH_HOST="example.com"
export SSH_PORT=2222
export SSH_USER="website"
export SSH_DIR="/home/${SSH_USER}/${SSH_HOST}/public_html/"

if [ -n "$BASH_VERSION" ]; then
    if [ -f "$HOME/.bashrc" ]; then
	. "$HOME/.bashrc"
    fi
fi

Below is the actual deployment script that is placed inside of the root of my websites code repository. It’s a very basic script to only upload changed files from the directory public into the directory defined by the variable SSH_DIR.

#!/bin/bash

# Requires the environment variables SSH_HOST, SSH_USER, SSH_DIR and
# SSH_PORT. If any variables are missing, the script will terminate
# with a failure code.

ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
SOURCE_DIR="${ROOT_DIR}/public"

REQUIRED_VARIABLES=(
	"SSH_HOST"
	"SSH_USER"
	"SSH_DIR"
	"SSH_PORT"
)

# Verifies that all of the variables defined in REQUIRED_VARIABLES are
# set to a value
for var in "${REQUIRED_VARIABLES[@]}"; do
	if [[ -z "${!var}" ]]; then
		echo "The environment variable \"${var}\" isn't set."
		exit 1
	fi
done

rsync -az --delete --stats -e "ssh -p ${SSH_PORT}" "${SOURCE_DIR}/" "${SSH_USER}@${SSH_HOST}:${SSH_DIR}"

Sample of Deploying a Website

Below is a sample of what it looks like when I make a change to a website and then want to deploy the changes to the server. After committing changes, I change over to the user account associated with the website. Within the user account, I pull the changes from the code repository that I modified, then run the build and deployment scripts.

It may seem like a lot of additional commands having to switch users and pull changes from the same device, but with this setup, I’ve been able to reduce the time it takes me to upload websites and don’t have to worry about uploading files to incorrect servers.

david:/dev/geekthis.net/site/tip$ hg commit -m "New Changes"
david:/dev/geekthis.net/site/tip$ su - geekthis
geekthis:~$ cd geekthis.net
geekthis:~/geekthis.net$ hg pull -u
geekthis:~/geekthis.net$ ./build.sh && ./deploy.sh
geekthis:~/geekthis.net$ exit

The reason I run su - geekthis with the hyphen is to run the login shell. This allows the ~/.profile file to actually be used and set the required environment variables. If you run su without the hyphen, the environment variables will not be set.