Automated Deployment of PHP Applications using git

Heroku is sweet hosting platform which offers a pure git deployment workflow. I love using it to deploy my Rails projects. I miss using when I deploy a PHP application.

Lets fix that…. Here is a quick heroku-ish git deployment workflow for your PHP applications. We will be using WordPress for this example. The same steps can be adapted for Drupal or any other PHP application.

Our goal is to development and test new features, themes, and plugins for WordPress locally on our development machine. When changes are working properly, we want to deploy them to our live site with one command:

git push origin master

#1: Set up password-less login to your webserver with SSH keys

To run deployments we will need to access our live webserver without a password. Follow the steps  in Setting up ssh public keys to login to webserver without a password.

For each user or machine from which you want to allow deploys you will need to install its SSH key using the steps above.

#2: Setup Code Repository on the Webserver

I connect to the webserver via SSH. My home directory is /home/saintsjd.

I have a /home/saintsjd/www folder for my website and PHP files. I also have a /home/saintsjd/code folder for my git repositories.

cd into /home/saintsjd/code and create a folder called blog.git

cd /home/saintsjd/code
mkdir blog.git

Next, initialize a bare git repository

cd blog.git
git init --bare blog.git

This gives me a git repository to store my code at /home/saintsjd/code/blog.git. I then disconnect from the SSH connection to the webserver.

#3: Setup Development Box

I have php, mysql, and apache running locally on my laptop. I create my WordPress themes, plugins, and updates on my laptop. After I test them, I deploy them to the live webserver for the world to see.

My local website folder is /home/jon/Website. To get started I clone my git repo locally:

git clone ssh://saintsjd@saintsjd.com/home/saintsjd/code/blog.git ./blogdev

This give me:

/home/jon/Website/blogdev

Now i can start filling in code!

Into the /home/jon/Website/blogdev/ folder I copy in all the files for WordPress and run the installer.

Next, I need to add a gitignore file to the project. I use the one here. https://github.com/github/gitignore/blob/master/Wordpress.gitignore. I copy the contents of that file into a .gitignore file in /home/jon/Website/blogdev/.gitignore. I then:

git add .
git commit -m "inital commit"

To send my changes to the server I type:

git push origin master

#4: Setup Live Webserver

Now I install WordPress on my server. I SSH to my server. There my website directory is /home/saintsjd/www. I need to remove this folder so I backup everything in this directory.  Then I remove it and recreate it using git clone. I type:

rm -rf /home/saintsjd/www
git clone /home/saintsjd/code/blog.git /home/saintsjd/www

I then browse to my website http://www.saintsjd.com and run the wordpress installer so the database gets installed on the server.

IMPORTANT: Protect your .git repository from prying eyes

I don’t really want others reading my source code from the git repository via http://www.saintsjd.com/.git/

I prevent this by running:

chmod -R og-rx /home/saintsjd/www/.git

I also add a .htaccess file to the .git folder containing:

Deny from all

#5: Automated Deployment Script

Now that wordpress is installed on both my local machine and my live webserver, I want to be able to deploy any changes I make on my local copy with one git push command. I can do this using a Git hook.

I SSH to my webserver and create a file /home/saintsjd/code/blog.git/hooks/post-receive

touch /home/saintsjd/code/blog.git/hooks/post-receive

I make the file executable

chmod u+x /home/saintsjd/code/blog.git/hooks/post-receive

Then I edit the file

nano -w /home/saintsjd/code/blog.git/hooks/post-receive

and place this code in it (adjusting the config variables to match your server settings)

#!/bin/bash
#CONFIG
LIVE="/home/saintsjd/www"

read oldrev newrev refname
if [ $refname = "refs/heads/master" ]; then  
  echo "===== DEPLOYING TO LIVE SITE ====="  
  unset GIT_DIR  
  cd $LIVE  
  git pull origin master  
  echo "===== DONE ====="
fi

#6 Let the (heroku-like) Fun Begin: Automated Deployment for WordPress

Now that everything is setup. I am free to develop new themes, install new plugins and update on my local development box. Once everything is working properly locally commit and push. The post-receive hook automatically deploys the code changes to my live webserver for me.

Here are my steps:

  1. Make changes to my local site’s code: new theme, new plug-in, etc
  2. test, test and test so more!
  3. When I am happy I commit to the local git repository:
    git add .
    git commit -m "new theme css for header"
  4. I push and deploy to the server using one command:
    git push origin master
  5. After the post-receive hook moves the new code to the live website. I open http://www.saintsjd.com/wp-admin on the live server to trigger and database updates that need to run

That’s it… heroku-ish git deployment for WordPress websites.

The Magento (rocky road) Upgrade to 1.4

Magento released version 1.4 sometime ago. Before upgrading I watched the forums… sure enough, I saw upgrades failing and sites crashing left and right. Almost one month later, today I tried the upgrade from 1.3 to 1.4 using magento connect on a development installation of our shop.

The first upgrade failed. With errors in the Connect console. I found that I needed to disable the Blank theme. I switched to the default theme and removed the Blank theme using Magento Connect. After that upgrade proceeded without error… almost.

When I logged back into admin after the upgrade I experienced a common problem many other users have had. I saw only the Dashboard and the System menus in the admin section. Fortunately, http://www.magentocommerce.com/boards/viewreply/216653/ helped me solve the problem. After that, I could see all the links in Admin.

Next, an error appeared, saying that indexes where out of date. Clicking the link, I was able to update indexes without trouble.

Next I tried to enable my old custom theme for magento 1.3. The file structure of the new themes were just too different. And I notice that Magento Connect had not copied in all the new theme files compared to a fresh install of 1.4. So, back to square one I went:

  1. Switched to the Default Default theme
  2. Disable cache
  3. Backed up everything!
  4. http://www.magentocommerce.com/boards/viewthread/79499/
  5. Update product indexes System -> Index management

At this point all the store data seemed to be working properly. From what I read in the forum, its best to re-create your old custom theme from scratch. So I did:

  1. Create folder /skin/frontend/myPackage/default
  2. Create folder /app/design/frontend/myPackage/default
  3. Copy /app/design/frontend/default/blank/etc and /app/design/frontend/default/blank/locale to /app/design/frontend/myPackage/default
  4. Copy /skin/frontend/default/blank/css and /skin/frontend/default/blank/images and skin/frontend/default/blank/favicon.ico to /skin/frontend/myPackage/default
  5. Enable my theme and test /admin/system_config/edit/section/design/
  6. From there I slowly copied in the CSS and images from the theme I had designed from magento 1.3

Upgrading Magento to 1.4 was painful and time consuming even with all the help of the poor souls who had crashed their sites before me. That said, using magento still seems to be faster and less time consuming that building my own ecommerce solution from scratch. Given the time it takes to make Magento  perform well and maintain its updates, Shopify’s pricing does look more attractive and reasonable.

Magento Ecommerce, “Please Wait”… and Wait… and Wait

I installed Magento (http://www.magentocommerce.com) on a dedicated server with 2 Xeon processors and 4 GB of RAM.  Often when adding products, editing them, or choosing a category in the admin Magento takes 5-10minutes to complete the Save operation. A “Please Wait” modal dialog appears for long periods of time while Magento is thinking. The operation always completes without error, but It often takes 5-10 minutes just to save a simple product.

asked for a solution in the Magento Forums and found one…

Following the the instructions in the DYI tuning section here
http://www.magentocommerce.com/blog/comments/performance-is-key-notes-on…

I installed each optimization individually to see which made the difference. I found the following:
- Apache KeepAlives enabled. This seems to help the category selection pages load much much faster.
- Modify the configuration for your MySQL server. No noticeable gain on performance. Products still take 5 minutes to save.
- APC or XCache. This one fixed the problem.

Installing xcache seems to have fixed the problem. Products now save in about 2 seconds.

Edit on November 15th, 2009:

I found this in the Magento System Reqs:
“Memory_limit no less than 256Mb (preferably 512)”

Also please see: