At several of the WordCamps I attended earlier this year, the topic of individual WordPress development processes seemed to come up often—whether it was in regards to version control, coding standards, or just getting your feet wet as a new dev. As well, folks like Tom McFarlin, who wrote a great series about Professional WordPress Development, have been contributing to the discussion around this at length. I’ve heard encouragement from other devs several times to share “the way you do things”.

The following is the way I set up, develop, test, and deploy, currently. Two quick notes:

  1. A process like this is never final, and should only get better over time. This is a snapshot of a process I’ve found useful.
  2. I’m not suggesting that this is the right way to do things—it’s just the way I do them. Feel free to leave a comment if you have a suggestion or want to share yours!

Local Development Environment

I’ve been using MAMP/MAMP Pro for quite some time, with very little issues. I like that it’s self-contained, allowing me to use the native MAMP stack on OS X for something completely different, like working on Pardot.

Version Control

Git‘s entered ubiquity within my workflow. Between all of its own features and the availability of needed resources (like WordPress itself on GitHub), it’s quicker and easier for me to let Git do the work. Although I do love GitHub, I use BitBucket for my development because of the pricing structure: GitHub’s is based on private repositories, whereas BitBucket’s is based on users. Since I’m really the only user for now, I can set up unlimited private repos on BitBucket for free.

Put on some Led Zeppelin and let’s dig in by creating a new WordPress site from scratch, using the command line (which is your friend).

Installing WordPress

We’re going to use the official, public Git repository of WordPress to download the files instead of clicking through folders. Why? It’s faster, and you can stay in the command line to get things done. For what it’s worth, I’m sure you could create a sort of shell script from these lines.

 I wrote a shell script that performs these tasks for you (up until initializing the Git repo). It asks for a directory name, creates it, downloads WordPress, sets up your database, and even fills out your configuration options for you (including secret keys). It’s free on GitHub.



Let’s assume you have MAMP/MAMP Pro installed. Hop into that directory via Terminal:

cd /Applications/MAMP/htdocs

Now, we’ll clone WordPress, checkout the latest version, and remove version control from that directory. Note that you should replace items in brackets (i.e. [project directory]) with whatever works for you. Just make sure it stays consistent throughout this process.

git clone https://github.com/WordPress/WordPress.git [project directory]
cd [project directory]
git checkout 3.5.1
rm -rf .git

Now, let’s create the new database and give permissions using the command line as well.

../../Library/bin/mysql -uroot -p
CREATE DATABASE [DB name];
GRANT ALL ON [DB name].* TO 'root'@'localhost';
exit

Now, go create a new repo on BitBucket. It can be private or public, but grab the repo’s URL. We’re going to set up the wp-content folder as our local Git repo, since anything we’re doing to a WordPress installation should be done within this folder. Don’t hack core. Let’s set it up, make our first commit, and push:

cd wp-content
git init
git remote add origin [Bitbucket Repo URL]
git add .
git commit -m "Initial Commit"
git push -u origin master

Well done, friends! Now, go to the URL of your [project directory] (i.e. http://localhost:8888/[project directory]), and run the install. With a few clicks, you should be off to the races.

Now, let’s do a couple of useful things before proceeding.

Automated DB Backups

Git hooks are fantastic tools and can be used for nearly anything. The most vital use in this environment, to me, is an automated DB backup that runs before a commit and adds the ‘dump’ file to a directory within wp-content. This keeps our local DB backed up (which is always useful), but also ensures easy access to that file for initial deployment.

Let’s make this pre-commit script, based on Ben Kulbertis’s:

touch .git/hooks/pre-commit
sudo nano .git/hooks/pre-commit

Now, copy and paste the below into the pre-commit file that just opened, replacing the items in brackets with the appropriate string. If using Nano in Terminal really freaks you out, type in open .git/hooks, right-click the pre-commit file, and open it with TextEdit,

echo "Pre-commit started."
#!/bin/sh
/Applications/MAMP/Library/bin/mysqldump -u [MySQL user] -p[MySQL password] --skip-extended-insert [DB name] | gzip -9 > /Applications/MAMP/htdocs/[project directory]/wp-content/db-backups/[DB name].sql.gz
cd /Applications/MAMP/htdocs/[project directory]/wp-content
git add db-backups/[DB name].sql.gz
echo "Pre-commit finished"

Then, correct the permissions, add the new directory for your backups, add, and commit:

sudo chmod +x .git/hooks/pre-commit
mkdir db-backups
git add .
git commit -m "Add DB Backups"
git push -u origin master

Minimalism

This is certainly not required, but I don’t use the Hello Dolly plugin or the TwentyTen theme that comes with WordPress, so I remove them. Make sure if you do this, though, that you keep the TwentyEleven (or TwentyTwelve with 3.5) theme—the best way to debug a massive issue is always to disable all plugins and use the default theme.

git rm -rf plugins/hello.php
git rm -rf themes/twentyten
git add -u
git commit -m "Remove Hello Dolly and TwentyTen"
git push -u origin master

Themes & Child Themes

This part of the process depends entirely on what theme you want to use and how you can get to it. You’ll likely have to download, unzip, and copy the theme over to your wp-content/themes folder.

As of late, I’ve fallen in love with the Standard Theme. It’s epically well-written, fast, SEO-friendly, and has a Bootstrap base. It makes child theming a breeze.

Speaking of child theming, for the sake of your sanity, make sure you’re using this capability instead of hacking existing themes.

If you’re using Standard and would like to roll my child theme, it’s publicly accessible via GitHub. I keep it updated, and it’s got some great extensions like Bootstrap shortcodes and Font Awesome. If you’d like to roll it and forget it using the command line, make sure you’re in your wp-content folder and run:

git clone https://github.com/logoscreative/logos-child themes/logos-child
rm -rf themes/logos-child/.git
rm themes/logos-child/.gitignore

Make sure you commit after adding your theme(s)!

You could also roll this as a Git Submodule if you like the child theme as-is and want to be able to pull updates:

git submodule add https://github.com/logoscreative/logos-child themes/logos-child

Sweet! You’re all set to develop locally. Add plugins and such as you see fit.

Testing Locally

With BrowserStack, you can test your local sites in pretty much any conceivable browser. There’s really no explaining to do here: pay and use it. I don’t know of any better option whatsoever, short of actually having those machines and environments present physically.

Deploying

Depending on your live hosting environment, you can often set up a place to git push your files. In a shared hosting environment, you may be able to follow Arlo Carreon’s instructions with some tweaks, or you may be using WPEngine (which I highly recommend, therefore, that’s an affiliate link), and they have their own awesome Git setup. Either way, if you can get this set up, you can push directly to the server with:

git push [remote URL]

It will change your life if you’ve solely been using FTP to move things to a live server.

Once our files are uploaded, we’ll need to get the database updated. The way to do this varies wildly depending on your hosting, but you want to import the latest file in your wp-content/db-backups folder.

See how useful that is? It’s already there and ready for you.

Once you import, you’ll need to make sure all the URL’s in the database get changed from your local URL to your live one, or else your site will, um, not work at all. This is not as easy as a simple search and replace query because of serialized arrays and such, so I use the amazing Search Replace DB script, an open source piece of magic that does this for you. Upload it to your main parent directory (the one with wp-config.php) on the live server and follow the instructions. Remember to delete it once you’re done! Otherwise, you’re giving someone pretty terrible access to wreak havoc on your DB.

Keep Working Locally

The next time you need to fix a bug or add a feature for the client, just overwrite your local files with what’s on the live server (if they’ve updated any of them) and do the same database-search-and-replace as above, but replacing the live URL with your local one. Do the work locally, test, and re-deploy. This keeps useful backups of everything for you and the client on a third-party server, as well being a useful emergency tool. Did a client accidentally break something editing a file? Push your most recent working commit from Git to get the site back up!

Feedback

Do you have something you’d like to add? Have a question for me? Did I miss anything, or type anything incorrectly?

Let me know in the comments. Happy developing!

Want blog posts sent directly to your email?

Sign up to get emails from Logos Creative! You'll get the latest blog posts and an occasional extra email. Unsubscribe (easily) at any time.

October 21, 2012 — 57 Comments — In Blog, Development, Git, Tutorial, WordPress
  • Digging it, dude – and thanks for the shoutout. Love seeing what other developers are doing in this space :).

  • Awesome stuff Cliff, one trick that speeds up my live restore to dev environments is configuring the siteurls and homeurls in the dev wp-config.php.

    http://codex.wordpress.org/Editing_wp-config.php#WordPress_address_.28URL.29

    define(‘WP_SITEURL’, ‘http://example.com/wordpress’);

    Will override the values from wp_options.

    • Cliff Seal

      Absolutely, Paul. Thanks for the tip!

      I use the script (instead of just the constant) to ensure that any other values within the database are updated properly—for instance, in case a widget or a theme’s option isn’t coded to use the site’s URL dynamically.

  • Pingback: Development process example http logos creative com my... « WordPress Pushing the Limits - notes()

  • Came across your post in my search for leveling up my dev process. Nice work.
    Question though. You clone WP and checkout the version, but then you remove the versioning. I’m wondering what do you then do when Core updates. How do you upgrade your WordPress core at that point?

    • Cliff Seal

      Hey Jason, good question! I just use the standard core update procedure if a newer version is pushed while I’m in development. If I want to run nightlies, I use the WordPress Beta Tester plugin.

      • Oh ok — I found David Winters post but I was wondering if there was a work flow out there that didn’t “bust up” the structure of WP and managed the Core and themes separate.

        I suppose you could’ve just left your core under versioning and then .gitignored the theme and plugins. And then even made the theme and plugins a separate repo under it, right?

        • Cliff Seal

          In that case, I’d probably roll the entire WordPress install as my main repo and run the `wp-content` folder (or whatever) as a submodule.

          • Yes I can see that, but that is if the theme already has a repo, correct? From what I understand submodules are great for bringing a repo into another repo. But if I want to create a repo within a repo (such as a theme within WP Core), I wouldn’t be able to push my changes back up into the submodule’s repo, correct?

            Not sure if I’m making any sense, but if I am, I’d love to hear your thoughts

            • Cliff Seal

              Haha, words fail sometimes. 🙂

              Submodules allow you to have a repo within a repo—you’d be able to keep both updates separately, because the remote branches (i.e. GitHub) for the main and the submodule would be separate, though they’d be together locally. So, you’d use the WordPress repo on GitHub to keep the main folder updated, and commit changes inside your submodule separately. Here’re some more details that might help: http://stackoverflow.com/questions/5542910/how-do-i-commit-changes-in-a-git-submodule

  • Raphael Essoo-Snowdon

    Fantastic insight into your workflow. Though, I’m having a problem with the backing up the database on commit.

    When I commit changes, nothing appears in the new db-backups directory. Am I doing something wrong?


    echo "Pre-commit started."
    #!/bin/sh
    /Applications/MAMP/Library/bin/mysqldump -u root -proot --skip-extended-insert bench | gzip -9 > ../../../Volumes/Boba/Raph/Sites/bench.bitthirsty.com/html/wp-content/db-backups/bench.sql.gz
    cd ../../../Volumes/Boba/Raph/Sites/bench.bitthirsty.com/html/wp-content
    git add db-backups/bench.sql.gz
    echo "Pre-commit finished"

    • Cliff Seal

      Hey Raphael,

      Let’s try a few troubleshooting steps, though I haven’t tested it when housing the files outside of MAMP as you have them.

      1. Check and make sure you do, indeed, have a pre-commit file in the .git/hooks directory of your wp-content folder. Run `sudo chmod +x` on it again to be sure you’ve got the right permissions.

      2. If all that’s set, are you seeing “Pre-commit started.” and “Pre-commit finished” when you run git commit … ?

      3. If it’s your first commit, try changing a file so you have something to commit other than this DB.

      If all else fails, try running each line in the script independently, starting with the mysqldump one, and see if you can trace down the error.

      • Raphael Essoo-Snowdon

        Sorry, I posted that reply below before I received this reply. Ok, so I think I ran ‘sudo chmod +x’ successfully, as this time it asked me for my password. Now, when I commit changes I am seeing “Pre-commit started/finished”, but something seems to be going wrong.

        Pre-commit started.
        .git/hooks/pre-commit: line 3: ../../../Volumes/Boba/Raph/Sites/bench.bitthirsty.com/html/wp-content/db-backups/bench.sql.gz: No such file or directory
        mysqldump: Got errno 32 on write
        .git/hooks/pre-commit: line 4: cd: ../../../Volumes/Boba/Raph/Sites/bench.bitthirsty.com/html/wp-content: No such file or directory
        fatal: pathspec 'db-backups/bench.sql.gz' did not match any files
        Pre-commit finished
        [master 6621d86] Changed body font to Georgia
        1 files changed, 1 insertions(+), 1 deletions(-)

        • Cliff Seal

          I’d go back and change all your file paths to not be relative to where it’s running from—that might very well be the issue, since it doesn’t seem to be finding `wp-content`. Drag that directory into the terminal window to get the “absolute” path for it, and try using that, instead.

          • Raphael Essoo-Snowdon

            Thanks Cliff! That seems to have worked a treat.

            One more question, if you don’t mind. Couldn’t there be a more “dynamic” way to write paths for the pre-commit file? If I always know that my mySQL dumps are going to appear in wp-content/db-backups, surely I can backup to the root directory (../../) from that pre-commit file and drill down a bit to find that db-backups folder. Will save time, if it’s possible, right?

            • Cliff Seal

              You can do that, you’ll just have to make it relative to the location of mysqldump (in this case, in /Applications/MAMP/Library/bin), not relative to the folder you’re already in. It might save you some time in your case because you’re not keeping your actual files inside MAMP as most folks would. But, you’re already going to have to edit the path each time to dump to the proper directory, so you might as well just find the absolute path and plug that in.

            • Raphael Essoo-Snowdon

              Is there any way to make it relative to the folder you’re already in?

            • Cliff Seal

              Actually, I just found out I’m very wrong, and that my test case just fit my assumption. It’s relative to the folder you’re in when you run the command. Apologies!

  • Raphael Essoo-Snowdon

    Ok, so it doesn’t seem like there’s a problem with my code above, as when I double click the “pre-commit” file in Finder, it successfully runs and dumps a current copy of my database in the db-backups folder. It must have something to do with the “sudo chmod +x” line. The only thing I can think of is that it’s not changing the permissions and therefore isn’t running that “pre-commit” script on/before commit.

    Know how to fix this Chris?

  • They do fail sometimes 🙂 – you’re right.

    I see what you mean, if I have a blank theme sitting in Github, but I do have a hiccup here. I’ll create the WP Core repo and pull it down from Github locally, so I can begin a new project. Then I create a directory in wp-content/themes for my new theme. Since it’s a blank slate, it’s not a repo elsewhere at that point to pull down with git submodule add. Is there a way to create the theme at that point as a submodule?

    • Cliff Seal

      If you just create the blank repo somewhere first and then run a git submodule add of it, you should be all set up to add your files and push your first change. Does that make sense?

      • Yeah that’s what I’m thinking — Hmmm….might give it a try on my next project and see how it goes.

        The reason I’m asking this is because it’s great that there are tuts out there that pull things like wp-config.php and wp-content out of core and then set everything up to make it work (and it does work), I’d like to get going where I don’t have to rip apart the directory structure. This way when I bring other people into the mix that understand the structure of the wordpress files, they don’t look at it like it’s something completely new.

        • Cliff Seal

          I’ve seen some good examples of pulling wp-config.php out, but not much regarding moving wp-content around. I just try to keep my repos as light and quick as possible, and all I need for WordPress is certain items within that folder, and I don’t need core clogging things up for me.

          Anyway, please feel free to ask more questions here or by email. I’m happy to help.

          • True, that’s how I am too. In that respect, I’d love to develop all my themes or plugins on one single core instance, but also be able to upgrade it nicely.

            And in the same regards, be able to dev on a client site (obviously needing it’s own WP instance) and then being able to manage it fairly quickly and efficiently without having to log in and click on the button to upgrade.

            Thanks for all the information!

  • Raphael Essoo-Snowdon

    Hi Cliff, me again.

    So I’ve spent like the last two weeks trying to wrap my head around Git, and your article has been great in helping me craft out my own WordPress + Git workflow. As this is all still quite new to me, could you possibly take a look my workflow, I’ve written it up here

    I want to put it up online but I want to make sure I’m doing things right and the language is correct, forgive me, the command line was foreign to me a month ago.

    Thanks again! Appreciations in advance.

    • Cliff Seal

      Sure thing, Raphael. How would you like me to give feedback? I can either leave notes on your doc (if you give me the proper permissions) or I can email you.

      • Raphael Essoo-Snowdon

        Thanks Cliff. Feel free to email me!

  • Christoph

    Hi,

    first of all thanks a lot. I have been starting with Git and i was looking for a good workflow and i took many advices from you. What i changed is that i am using Github in order to share my code with outhers. Therefor i had to put the DB backups out of wp-content which is my repo, in order not to share the DB too 😉 But i am still using your DB backup script. I like the thought of backing up every time when i commit, but as i said i am putting the DBs out of the repo. I also changed the script to save the file with the current time in order not to replace the file every time. Guess i am getting this way a lot of files. Need to check how that works for me longtime.
    I have tried you recommended DB replace-script but it did not work for me. I do not know why. I then tried to replace the URLs directly in PHPmyAdmin on the live-server with the queries provided by http://wp.smashingmagazine.com/2011/09/28/developing-wordpress-locally-with-mamp/. This worked for me.
    One thing im am thinking about is how to keep my local wp and my live one up to date. With the files in the repo it is obvious. I test things local and when its fine i push the to the live server where i pull my repo. But what about changes like adding a WP post? Theses changes take only place in the db. So i have to load new db to the live server and replace urls every time. Kind of not satisfying. Another thought of me was to changes things like posts only on the live server, but then the wp sites (local and live) do not match anymore and i think it will get a mess. How do you do that?
    Another thing i am interested in is, if you are using http://wp-cli.org/ and if yes how in your workflow?
    Oh wow this answer was getting longer than i thought. I am sorry 😉

    Thanks again for your article and sharing!

    • Cliff Seal

      Awesome, Christoph!

      Personally, I use my local installs for development purposes only, so content updates happen on the live server, and I’ll pull the database back down to local as needed.

      I’m a fan of WP-CLI, but I don’t use it much right now. I may do so much more in the future.

  • Thanks for the shout out!!

  • Yep, this post is awesome. I can’t wait to give it a try. Thanks, cliff!

    • Cliff Seal

      Sure thing, Josh!

  • Perjan Duro

    Man, you are awesome. Thanks for all this great info. Probably the best post on integrating wp with git.

  • Pingback: Wordpress and Git | Duuro App StudioDuuro App Studio()

  • Pingback: Wordpress | Pearltrees()

  • JT Grauke

    Thanks for this post! I’m running into some trouble with the mysql section — it’s asking me for a password (not sure what it would be), and I have tried everything I can think of, including just leaving it blank. I’ve searched around for a while but can’t seem to get past that part. Giving me this error: ERROR 1045 (28000): Access denied for user ‘root’@’localhost’ (using password: YES). Any ideas?

    • JT,

      “Attention: The password of the factory settings is also ‘root’. If your MAMP PRO is accessible from the internet, we recommend the use of an individual password, which only you know.”

      So, it’s that unless you’ve changed it, which you should. 🙂

      • jtgrauke

        Thanks — I was able to sort that out. But now it’s giving me this error: Can’t connect to local MySQL server through socket ‘/Applications/MAMP/tmp/mysql/mysql.sock’. Can you point me to a good resource for debugging, and making sure I have everything set up right?

  • Pingback: WordPress Development with Git version control workflow | WordPress Development()

  • Dustin

    This post has been really helpful thank you! I have set up a bare repo on the live server to allow me to push changes to it from my local machine. However, It doesn’t seem to be deleting files when I push them to the live server. I have removed the extra themes from my local copy so that it only has twentyfourteen and the theme I’m working on. I tried using git add –all and git add -u and then commited and pushed to the live server, but for some reason the deleted themes are still present on the live server. Any idea why that would be happening?

    • Two possibilities:

      1. Are your changes being pushed up correctly? Might be an issue with your post-receive hook.

      2. You might’ve removed the files from Git before you deleted the files themselves. If so, Git’s doing what you told it to do. 🙂

      • Dustin

        My post-receive hook has:
        #!/bin/sh
        git –work-tree=/public_html/dev/DIRECTORY/wp-content –git dir=/repo/DIRECTORY.git checkout -f

        If I add files it works fine, it’s just removing them that isn’t working. When I deleted them I trashed the directories first and then used git add –all, then git commit, then git push

        Thanks!

          • Dustin

            I keep getting errors when I try and run any git commands. If I run it in the bare repo i get ‘fatal: This operation must be run in a work tree’ and if I run it where all the files are located I get ‘fatal: Not a git repository’ Thanks for all the help!

            • Try adding –work-tree=./ (assuming you’re in the root directory of git).

            • Dustin

              Still getting ‘fatal: This operation must be run in a work tree’. I’m not sure what is going on at this point. I amy try and recreate the bare repo and see if that helps at all. I really appreciate you taking the time to try and help me out!

  • Kampiamo DiKuesto

    WP Plugin Revisr….opinions? i’m test and it would be potentially great but it doesn’t work without git on remote server (staging – production) so for shared hosting is a problem:(

    • Personally, I wouldn’t use something like this. It’s asking WordPress to deal with things it probably doesn’t need to deal with, and I think you’d be better off with a Git GUI like SourceTree or Git Tower.

      Also, on many shared hosts, you can simply ask for Git to be activated or for you to be given access—almost all of them have it now, and I’ve never had trouble out of any shared host I’ve used.

  • Kampiamo DiKuesto

    Whit this workflow is db version controlled? in other words can we return back to old db version?

  • Kampiamo DiKuesto

    Another question. To evitate to use search and replace script we can integrate this workflow with this: http://roybarber.com/version-controlling-wordpress/ (the part of two config files)?

    • I’m not sure what you’re asking.

      • Kampiamo DiKuesto

        in your workflow we have to import the database and execute search&replace script manually. In this link (another workflow) http://roybarber.com/version-controlling-wordpress/ the author use a modified wp-config.php file and a wp-config-local.php. in this way he haven’t to search&replace after deployment.

        but i think that this Roy Barber’s workflow work if we don’t use the url of site into database.

        what do you think?

        • You should still do a search and replace after deployment. The wp-config.php override will work in most cases, but you don’t want old URLs pointing to your local/staging installation sitting in the database.

          • Kampiamo DiKuesto

            thanks. so the good way is to have local and staging with same site url (so i would like to learn “vagrant universe tools”). do you use amp tools or virtual machine + vagrant?

            • It depends. I’ve mostly switched over to Vagrant, but it comes with its own set of problems and it’s pretty complicated to set up for most people.