As a PHP application, WordPress is usually deployed by a very old method: uploading files via FTP.
We have some deployment tools, but they often requires some type of Ruby skill. For example, one popular, powerful tool is Capistrano, but it's also very heavy with many Ruby/Rails related features. I also think that it's little bit tricky to install Capistrano for a PHP developer without any Ruby knowledge.
So what options do we have as WordPress developers?
In this tutorial, I will introduce you Mina: A small, light tool aims to fast deployment and server automation.
Why Do We Need Automated Deployment?
Automated deployment saves us the time of doing repeat tasks every time we start to deploy our WordPress project. It also help to minimize down time during deployment and eliminate human mistakes such as missing files, uploading wrong files, and so on.
This automation process can be shared between multiple developers in the team thus creating an unique method for deployment in whole team. One company almost went bankrupt because of their lack of a good deployment process. An automated deployment method is usually tied with a source code control system: Git, SVN, Mercurial, and so on.
In the scope of this tutorial we will be taking a look at Git. If you had a Git repository, feel free to use it; otherwise, grab a free one at BitBucket or GitLab. These services allow you to create private Git repositories.
Before we go further, let make sure we meet the requirements:
- an SSH connection
- a Git repository
- oermission to edit and change web server configuration.
[note] I assume that you are running WordPress on Apache with PHP installing as Apache PHP module. If you are using PHP with FPM or CGI, please be flexible when we're talking about our web server or our PHP process. [/note]
Deployment With Git Hooks
The general idea is that when we push to the server, Git will invoke a URL which could reference a PHP script to perform deploying by pulling or merging the newest code from repository.
While this works perfectly, and is very useful, it exposes a hole: We opened a secret door on the server. If someone knows that URL, they can trigger a deployment manually. Another danger is doing this is that we must build features for clean up, for rollback, for lock (to make sure only a single deployment process is running), and so on.
If we start to code these features, we are reinventing the wheel, so why not use an existing tool?
What is Mina?
Mina is a deployment tool aim to be very fast; You will be surprised at how fast it is when try it. According to Mina website:
Really fast deployer and server automation tool. Really bloody fast.
Simply put, here' how to think of Mina: Instead of logging into server and type a sequence of commands to deploy. Mina generate a shell script that is a set of these commands.
- Create a new directory,
- Pull latest code into this directory,
- Make symbolic point to common resources,
- Point the public directory to this new directory,
- Clean up the old data.
Mina uploads this shell script to server and executes it. This generation process is run on your local machine.
All Mina tasks, are just a sequence of shell commands. Because it's just shell commands, you won't have access to fantastic Ruby helper as in Capistrano or Vlad.
Another way to think of Mina is this: Mina organizes your shell commands that you need to run on remote machine into code blocks (which are called Mina tasks).
Step 1. Prepare Server Layout for Mina
Mina requires a directory layout for your website. You will need to change the public directory of your web server. To make it easier, let assume that your current server directory structure as following.
/var/www/yourdomain.com/ # The public directory where your WordPress sits |- index.php |- wp-admin |- wp-content |- wp-includes |- wp-login.php |- wp-activate.php |- ….
yourdomain.com points to /var/www/yourdomain.com/
, and index.php
file, which sits inside this directory, is executed and responds to your request. With Mina, you have to change directory layout a little bit.
Mina expects this structure:
/var/www/yourdomain.com/ # The deploy_to path |- releases/ # Holds releases, one subdir per release | |- 1/ # Each of WordPress deployment sit here | |- 2/ # Each of WordPress deployment sit here | |- 3/ # Each of WordPress deployment sit here | |- ... |- shared/ # Holds files shared between releases: such as log file, config,… | |- logs/ # Log files are usually stored here | - ... |- current/ # A symlink to the current release in releases, this will be new public folder
Mina adds three directories:
-
releases
: each deployment will be stored in separate directories inside this folder which allows us to maintain version 1, version 2, version 3 and so on. -
shared
: contains common file/folders which is shared between multiple deployment. An example iswp-content/uploads
. At each of deployment, we will have a new directorywp-content/uploads
that is different from previouswp-content/uploads
directory. And content inside is gone. Therefore, we will use a common directory:shared/wp-content/uploads
. We will have:/var/www/yourdomain.com/releases/7/wp-contents/uploads
is a symlink points to/var/www/yourdomain.com/shared/wp-content/uploads
. -
current
: points to current release. Example:/var/www/yourdomain.com/current
points to/var/www/yourdomain.com/releases/7.
According to Wikipedia:
A symbolic link (also symlink or soft link) is a special type of file that contains a reference to another file or directory in the form of an absolute or relative path and that affects pathname resolution. Symbolic links were already present by 1978 in mini-computer operating systems from DEC and Data General's RDOS. Today they are supported by the POSIX operating-system standard, most Unix-like operating systems such as FreeBSD, GNU/Linux, and Mac OS X, and also Windows operating systems such as Windows Vista, Windows 7 and to some degree in Windows 2000 and Windows XP in the form of Shortcut files.
Yourdomain.com
now must points to /var/www/yourdomain.com/current
instead of /var/www/yourdomain.com
. Web server will run the file /var/www/yourdomain.com/current/index.php
when you visit http://yourdomain.com
. We have to re-config web server (Apache, Nginx) to point docroot
(or public directory
) to this current
directory.
Change DocumentRoot for Apache
Open your /etc/httpd/conf/httpd.conf
, find the DocumentRoot
line and change it to.
DocumentRoot /var/www/yourdomain.com/current
Change Document Root for Nginx
Edit your /etc/nginx/nginx.conf
and update your root
definition.
server { server_name log.axcoto.com www.log.axcoto.com; root /srv/http/domain/log.axcoto.com/current/; # … }
Step 2. Installing Mina
Mina is a Ruby gem so you need to install Ruby. Installing is quite esy. If you are running OS X or Linux, chances is you already have Ruby installed; otherwise, you can follow these tutorials to install Ruby:
- http://net.tutsplus.com/tutorials/why-you-should-use-rvm/
- http://net.tutsplus.com/tutorials/ruby/how-to-install-ruby-on-a-mac/
Once, you have Ruby, proceed to install gem. Just run:
$ gem install mina
Confirm that Mina is running properly.
$ mina -V
You should see something similar.
Mina, version v0.3.0
WordPress Deployment with Mina
One of the things that make PHP applications less secure is setting the incorrect permissions.
Take WordPress for an example: I may want to upload plugin, or upload theme to try it. To do this, I end up uploading them in WordPress dashboard. If I or one of the site administrator loses the admin password, someone get in and they can upload a PHP file and run it, as a plug in, and not only hack my site, but my entire server, as well.
Case in point: they can read a file in /etc, learn the server configuration, and then begin running shell commands via PHP.
I think we should treat a WordPress instance as a read-only installation. For theme and plugin installation, we can install via adding files locally, then re-deploy via Mina. Deploying with Mina is cheap and very fast, on my 512MB RAM DigitalOcean server, it takes under 30 seconds to deploy.
For user uploaded content (such as pictures and audio files), we will symlink them to outside of the source code directory, and it will be best if we can configure the server to prevent executing of any PHP file inside that user uploaded content folder, but that's out of scope of this tutorial.
For now, we will try to use Mina for fast deployment, and adding theme or a plugin and we'll do so locally so we can avoid giving web server the permission to write into these directories.
Step 1. Setup Mina with WordPress
As stated in beginning of tutorial, you must have a Git repository for your WordPress site. Let recap what we need here:
Let's assume that...
- Your WordPress project will be in ~/Site/wordpress.
- Your WordPress git repository is [email protected]/yourname/wordpress
- You can access your server via ssh with an account call yourname at yourdomain.com
If you are not familiar with Git, please read the following tutorials:
- http://net.tutsplus.com/sessions/git-succinctly/
- http://net.tutsplus.com/tutorials/other/easy-version-control-with-git/
- http://net.tutsplus.com/tag/git/
If your WordPress source code isn't a Git repository, let's do it now; otherwise, jump to next step.
$ cd ~/Site/wordpress $ git init $ git remote add origin [email protected]:kureikain/wordpress.git $ git add . $ git push origin master
Run below command to start setting up Mina for your project.
$ mina init
This will create you a folder config
with a single file deploy.rb inside it.
$ ls config wp-blog-header.php wp-load.php index.php wp-comments-post.php wp-login.php latest.tar.gz wp-config-sample.php wp-mail.php license.txt wp-content wp-settings.php readme.html wp-cron.php wp-signup.php wp-activate.php wp-includes wp-trackback.php wp-admin wp-links-opml.php xmlrpc.php $ ls config deploy.rb
Step 2. Configure config/deploy.rb
Now, open config/deploy.rb in previous step and let's define some configuration.
This deploy.rb file contains deployment configuration, and a set of Mina task. Each task is wrapped inside task :taskname
block. Inside each task, we can invoke another task with invoke
To run a comand on server, you specify it with queue
command.
For example, a task may look like this:
task :down do invoke :maintenance_on invoke :restart queue 'rm -rf /tmp/cache' end
With that in mind, let's begin editting this deploy.rb file.
Remove Unused Lines
Comment out these line since we won't use them.
#require 'mina/bundler' #require 'mina/rails'
Configure Server Definition
The default code look like this
set :user, 'username' set :domain, 'foobar.com' set :deploy_to, '/var/www/foobar.com' set :repository, 'git://...' set :branch, 'master'
-
domain
is domain to the domain name of your WordPress site. -
deploy_to
is where you want your WordPress project locate. -
repository
is your Git repository address. -
branch
your deployment branch. Git support many branches, and some pepple want to have adeploy
branch for deployment purpose.
Let's update it with our server configuration:
set :domain, 'yourdomain.com' set :deploy_to, '/var/www/yourdomain.com' set :repository, 'https://[email protected]/kureikain/wordpress.git' set :branch, 'master' set :user, 'your_username' # Username in the server to SSH to.
Make sure that you can reach the repository
on your remote machine.
Next, we will handle the wp-content/uploads
folder. This folder contains user uploaded content. Every time we deploy, it will be a different folder from the one that we're currently using because the recently deployed code sits in different folder inside releases
.
If we keep this new folder, we will lose all of the old content. As such, we have to use a symlink to point it to somewhere else. Once deployed, we will update it to point to correct one.
Find this line:
set :shared_paths, ['config/database.yml', 'log']
And change it to:
set :shared_paths, [ 'wp-content/uploads']
shared_paths
is a collection of common resources(file/folder) between the various releases and can be different between release. Like log files, user uploaded content should be put in shared_paths. If we don't do that, then we will lose the data with each new release.
For example: When an user upload a file, WordPress store it in /var/www/yourdomain.com/current/wp-content/upload/2014/01/29/picture.png
. But /var/www/yourdomain.com/current
is a symlink point to /var/www/yourdomain.com/releases/4/
which is our current release. Therefore, the actual file is located at: /var/www/yourdomain.com/releases/4/wp-content/upload/2014/01/29/picture.png
.
Now, if you made a new release, current
will point to /var/www/yourdomain.com/releases/5
. The file /var/www/yourdomain.com/current/wp-content/upload/2014/01/29/picture.png
is no longer there, because /var/www/yourdomain.com/releases/5/wp-content/uploads
is a new folder, with no content.
As such, we must put it into shared_paths, and Mina will create a symlink to point /www/yourdomain.com/releases/5/wp-content/uploads
to /www/yourdomain.com/shared/wp-content/uploads.
Configure Our Setup Task
This is task we run on first time to prepare our server environment. The default one looks like this:
task :setup => :environment do queue! %[mkdir -p "#{deploy_to}/shared/log"] queue! %[chmod g+rx,u+rwx "#{deploy_to}/shared/log"] queue! %[mkdir -p "#{deploy_to}/shared/config"] queue! %[chmod g+rx,u+rwx "#{deploy_to}/shared/config"] queue! %[touch "#{deploy_to}/shared/config/database.yml"] queue %[echo "-----> Be sure to edit 'shared/config/database.yml'."] end
Let's change it to:
task :setup => :environment do queue! %[mkdir -p "#{deploy_to}/shared/wp-content/uploads"] end
Our setup task simply creates a wp-content/uploads
directory.
Configure Our Deploy task
The default task looks like this.
task :deploy => :environment do deploy do # Put things that will set up an empty directory into a fully set-up # instance of your project. invoke :'git:clone' invoke :'deploy:link_shared_paths' invoke :'bundle:install' invoke :'rails:db_migrate' invoke :'rails:assets_precompile' to :launch do queue "touch #{deploy_to}/tmp/restart.txt" end end end
We don't need anything that has to do with Rails. Also, since the deployment of WordPress didn't require a web server or PHP process restart so we only need to invoke git:clone and deploy:link_shared_paths task.
Let change them to:
task :deploy => :environment do deploy do # Put things that will set up an empty directory into a fully set-up # instance of your project. invoke :'git:clone' invoke :'deploy:link_shared_paths' end end
Configure Our Rollback Task
Sadly Mina doesn't have an official method for rollback, so I created a task for our own rollback process. What the rollback task will do do is that it will remove current release, and re point current
symlink to previous release. Append this task to end of your deploy.rb
desc "Rollback to previous verison." task :rollback => :environment do queue %[echo "----> Start to rollback"] queue %[if [ $(ls #{deploy_to}/releases | wc -l) -gt 1 ]; then echo "---->Relink to previos release" && unlink #{deploy_to}/current && ln -s #{deploy_to}/releases/"$(ls #{deploy_to}/releases | tail -2 | head -1)" #{deploy_to}/current && echo "Remove old releases" && rm -rf #{deploy_to}/releases/"$(ls #{deploy_to}/releases | tail -1)" && echo "$(ls #{deploy_to}/releases | tail -1)" > #{deploy_to}/last_version && echo "Done. Rollback to v$(cat #{deploy_to}/last_version)" ; else echo "No more release to rollback" ; fi] end
The above code looks complex but it actually is a single line version of following code. I will break it down and put an annotation on top of each commands.
# Check whether we have than 1 releases if [ $(ls /var/www/yourdomain.com/releases | wc -l) -gt 1 ] then echo "---->Relink to previos release" # Remove current symlink unlink /var/www/yourdomain.com/current # Point it to the previous release ln -s /var/www/yourdomain.com/releases/"$(ls /var/www/yourdomain.com/releases | tail -2 | head -1)" /var/www/yourdomain.com/current echo "Remove old releases" # Remove latest release which we already rollback rm -rf /var/www/yourdomain.com/releases/"$(ls /var/www/yourdomain.com/releases | tail -1)" # Log current version to last_version file echo "$(ls /var/www/yourdomain.com/releases | tail -1)" > /var/www/yourdomain.com/last_version # Out put some info to user echo "Done. Rollback to v$(cat /var/www/yourdomain.com/last_version)" else # If we don't have more than 1 releases, then we cannot rollback. echo "No more release to rollback" fi
At this point, we config the server, edit setup task, deploy task and rollback task. Let start to actually using Mina now.
Deployment With Mina
In this part, I will show you how to run Mina to setup server, deploy to server and perform a rollback. Mina is a command line utility. You need to have access to a terminal. Every Mina task will be invoked from terminal.
Step 1. Prepare
We only run this the first time we prepare to deploy to a server. SSH to server and create the deploy_to
directory. It's /var/www/yourdomain.com
in our case.
$ ssh [email protected] # on your remote machine (after you connected via SSH), run this $ sudo mkdir -p /var/www/yourdomain.com $ sudo chown -R your_name /var/www/yourdomain.com
Once we run the chown
command, we will change the owner of /var/www/yourdomain.com to user your_name
. Therefore, web server cannot write anything to this directory. Our WordPress site will be more secured by this way. Next, on your local, run mina setup
. It output something like this:
$ mina setup -----> Setting up /var/www/yourdomain.com total 16 drwxr-xr-x 4 kurei root 4096 Jan 27 22:51 . drwxr-xr-x 3 root root 4096 Jan 27 00:16 .. drwxr-xr-x 2 kurei users 4096 Jan 27 22:51 releases drwxr-xr-x 2 kurei users 4096 Jan 27 22:51 shared -----> Done. Elapsed time: 1.00 seconds
If you want to confirm what Mina created for us on server, let's check it on remote machine:
$ cd /var/www/yourdomain.com $ ls releases shared $ ls shared/wp-content uploads
At this point, everything is almost ready. Remember, the upload directory stores user uploaded content. Therefore, the web server must be able to write to it. We have to determine which user your Apache/Nginx (or PHP FPM Process, depend on how you config your server) is running on, and change the owner of uploads folder to it, to allow web server write to this folder. Usually, if you open your Apache config file, or Nginx config file, you can find this user, something similar:
[sourecode]
# For Apache, open /etc/httpd/conf/httpd.conf
User www Group www
# For Nginx, open /etc/nginx/nginx.conf
user http http; worker_processes 2;
[/sourecode]
Let assume that Apache is running on www
user.
[sourecode] ssh [email protected] sudo chown -R www /var/www/yourdomain.com/wp-content/uploads [/sourecode]
Step 2. Deploy
Every time we want to deploy the code, we use this task.
The process is:
- update the code
- commit the change.
- push to the Git server.
Once your code was up on the Git server.
Let's deploy
$ mina deploy
In several seconds, it should finish. Here is an example output of deploy result:
$ mina deploy -----> Creating a temporary build path -----> Fetching new git commits -----> Using git branch 'master' Cloning into '.'... done. -----> Using this git commit kureikain (347d9b3): > Update new config/deploy -----> Symlinking shared paths -----> Build finished -----> Moving build to releases/2 -----> Updating the current symlink -----> Launching -----> Done. Deployed v2 Elapsed time: 1.00 seconds
Step 3. Rollback
If a release has a critical bugs, or simply we want to rollback the code to previous release for one reason or another, we do this:
$ mina rollback
Its output something like
----> Start to rollback ----> Relink to previous release Remove old releases Done. Rollback to v2 Connection to axcoto.com closed. Elapsed time: 0.00 seconds
Conclusion
You learned how to deploy with Mina. The deployment process is quite fast now. Your website will experience zero downtime. But don't stop at that, go to the Mina website and lean more about it. I shared an example WordPress project that uses Mina on GitHub.
In next part, we will learn about WP-CLI. Specifically, we will learn how to utilize it and perform common admin tasks such as updating WordPress, installing themes, plugins, and so on all via WP-CLI on top of a Mina task. We will also look at how we use it to make our WordPress installation more secure.
Until then, leave a comment what you do to make your WordPress deployment a breeze.
Comments