Bitbucket for WordPress: Implementing Continuous Deployment and Version Control

Alright everyone, it’s confession time!

If your experience mirrors mine, the early years of your WordPress development journey involved a hefty dose of “cowboy coding.” This entailed making direct changes on live websites, hastily testing and uploading them via FTP, frequently leading to dreaded 500 Internal Server Error messages and site-wide crashes—all on full display for your valued visitors.

While the adrenaline rush of frantically searching for that missing semicolon was exhilarating, it became a serious issue for sites with an audience larger than zero (who actually noticed the downtime). As the saying goes, if a tree falls in a forest and no one is around to hear it, does it make a sound? The answer depends on your philosophical stance on the nature of reality.

However, if a website crashes and someone witnesses it, they will definitely make their displeasure known.

The Perils of Poorly Executed WordPress Continuous Deployment

Enter the concept of staging sites: duplicate WordPress installations (at least in theory) where changes could be tested before being replicated on the live site once deemed operational. While this approach mitigated visitor complaints, it shifted the burden to us developers. We were now tasked with managing two separate websites, manually syncing code between them, and conducting redundant tests to ensure functionality on the live site. This led to anxiety-inducing to-do lists filled with reminders like “don’t forget to update this on the live site” and “remember to toggle this on staging before copying the code.” Backing up this convoluted system was an absolute nightmare, resulting in a chaotic jumble of folders named “my-theme-staging-1,” “my-theme-live-before-menu-restyle-3,” and so on.

There had to be a more efficient solution, and indeed there was.

Git, a powerful version control system, emerged as the savior. Integrating version control into WordPress development instantly streamlined and organized the process. Countless hours previously wasted on manual backups were now dedicated to actual coding. Changes were tracked, and I could finally provide meaningful commit messages instead of resorting to generic labels like “my-theme-4-v2.”

While the codebase was now significantly cleaner, the challenge of deployment and ensuring the website was running the latest code persisted, introducing yet another avenue for human error. Relying on manual FTP uploads to overwrite existing code felt archaic and risky. Although CI/CD services offered a potential solution, many came with prohibitive costs and complex setup procedures. These often involved server reconfigurations, dependence on external services even for basic websites, and the need to master entirely new systems and their quirks.

While setups similar to the one described in this tutorial are achievable using GitHub/GitLab and other services, my early adoption of Atlassian’s ecosystem was driven by their free private repositories (a feature only recently introduced by GitHub). The arrival of Bitbucket’s Pipelines and Deployments services was a game-changer. It enabled automatic deployment of new code to staging or production environments (or any other intermediary stage) without the need for manual FTP uploads or third-party services. Developers could now leverage the full potential of version control within their WordPress development workflow, seamlessly pushing changes to the appropriate destinations with minimal effort, all while monitoring the status through a centralized dashboard. This approach ensures consistent synchronization and provides a clear overview of the codebase deployed on each environment. Moreover, pricing for Bitbucket’s build minutes offers exceptional value—50 minutes free per month with the option of a “Free with Overages” plan.

Initially, it took some time to adapt to this new model, particularly in terms of effectively utilizing branches and other source control features, as well as configuring the specifics of Bitbucket Pipelines. Here’s the process I’ve established for initiating new WordPress projects. I won’t delve into the intricacies of setting up Git and WordPress installations, as abundant resources are readily available online. In essence, the workflow follows this sequence:

Wordpress Bitbucket screenshot 1

My Streamlined WordPress Deployment Workflow

The steps outlined below should be executed as required:

Client Server Setup

  1. Establish a domain for the live website (e.g., clientsite.com) and a subdomain for staging (e.g., staging.clientsite.com).
  2. Install WordPress on both the live and staging environments. This can be achieved through cPanel/Softaculous (if supported by the client’s hosting provider) or by downloading the latest version from wordpress.org. Ensure separate databases are configured for each environment.

Bitbucket Repository Configuration

  1. Create a new repository and populate it with a .README file to initiate version control.

    Wordpress Bitbucket screenshot 2
  2. Navigate to Settings > Pipelines > Settings and enable Enable Pipelines.

    Wordpress Bitbucket screenshot 2
    Wordpress Bitbucket screenshot 3
    Wordpress Bitbucket screenshot 4
  3. Under Settings > Pipelines > Repository variables, define the following variables:

Name: FTP_username Value: The client FTP username

1
2
3
4

    ```
Name: FTP_password
Value: The client FTP password

Wordpress Bitbucket screenshot 5
  1. Return to Pipelines > Settings and select Configure bitbucket-pipelines.yml. Choose PHP as the language on the subsequent page. Utilize the following code snippet as a template, ensuring to replace the PHP version with the one used on the client’s server, and update the URLs/FTP servers with the actual client website (production and staging) URLs/FTP servers.

image: php:7.1.29

pipelines: branches: master: - step: name: Deploy to production deployment: production script: - apt-get update - apt-get -qq install git-ftp - git ftp init –user $FTP_username –passwd $FTP_password ftp://ftp.clientsite.com main-dev: - step: name: Deploy to staging deployment: staging script: - apt-get update - apt-get -qq install git-ftp - git ftp init –user $FTP_username –passwd $FTP_password ftp://ftp.clientsite.com/staging.clientsite.com

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

    
Wordpress Bitbucket screenshot 6
5. Click **Commit file** to commit the Pipelines configuration and trigger its execution. 6. Upon successful deployment, revisit the bitbucket-pipelines.yml file (accessible via **Pipelines > Settings** and **View bitbucket-pipelines.yml**). Replace both instances of `git ftp init` with `git ftp push` and save the changes. This modification optimizes the deployment process by only uploading modified files, conserving valuable build minutes. Your updated bitbucket-pipelines.yml file should resemble the following: ```php image: php:7.1.29 pipelines: branches: master: - step: name: Deploy to production deployment: production script: - apt-get update - apt-get -qq install git-ftp - git ftp push --user $FTP_username --passwd $FTP_password ftp://ftp.clientsite.com main-dev: - step: name: Deploy to staging deployment: staging script: - apt-get update - apt-get -qq install git-ftp - git ftp push --user $FTP_username --passwd $FTP_password ftp://ftp.clientsite.com/staging.clientsite.com

Wordpress Bitbucket screenshot 7
  1. Create a new branch named main-dev.

Local Development Environment Setup

  1. Clone the repository into an empty directory designated for local development. Switch to the main-dev branch.

  2. Set up a local WordPress installation within the designated directory. Numerous tools are available for this purpose, including Local by Flywheel, MAMP, Docker, and others. Ensure that the local environment matches the client’s server configuration (WordPress version, PHP version, Apache/Nginx, etc.).

  3. Create a .gitignore file with the following content to instruct Git to exclude unnecessary files from version control, preventing potential conflicts between installations. The primary goal is to track only the wp-content directory while ignoring other files. You can further customize this file to exclude large backup files, system-generated icons, and DS_Store files based on your preferences.

Ignore everything

But not .gitignore

!*.gitignore

And not the readme

!README.md

But descend into directories

!*/

Recursively allow files under subtree

!/wp-content/**

Ignore backup files

YOUR BACKUP FILE RULES HERE

Ignore system-created Icon and DS_Store files

Icon? .DS_Store

Ignore recommended WordPress files

*.log .htaccess sitemap.xml sitemap.xml.gz wp-config.php wp-content/advanced-cache.php wp-content/backup-db/ wp-content/backups/ wp-content/blogs.dir/ wp-content/cache/ wp-content/upgrade/ wp-content/uploads/ wp-content/wflogs/ wp-content/wp-cache-config.php

If you’re using something like underscores or another builder:

Ignore node_modules

node_modules/

Don’t ignore package.json and package-lock.json

!package.json !package-lock.json

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35

4.  Save and commit the `.gitignore` file.
5.  Implement changes and commit them with descriptive messages.

With this workflow, any commits pushed to the main-dev branch will trigger an automatic FTP upload to the staging site. Similarly, commits pushed to the master branch will initiate deployment to the production site. Be mindful that these actions consume build minutes, so it's advisable to perform most local development on a separate branch derived from main-dev and merge changes to main-dev at the end of each day.

Merging the main-dev branch into the master branch will propagate all staging changes to the live environment. You can monitor the status of Pipelines and Deployments on Bitbucket.org.


Wordpress Bitbucket screenshot 8
Wordpress Bitbucket screenshot 9
## Synchronizing WordPress Databases Across Environments It's important to note that the aforementioned steps only address file synchronization (themes, plugins, etc.). Database synchronization between production and staging environments requires a different approach, as clients often introduce changes directly on the live site that aren't reflected in staging, and vice versa. Several methods exist for synchronizing databases across WordPress installations. Traditionally, you could import/export databases using _phpmyadmin_. However, this method has drawbacks as it may not handle the update of specific elements like permalinks within post content. A popular tool for this purpose is the [Velvet Blues Update URLs plugin](https://wordpress.org/plugins/velvet-blues-update-urls/), which allows you to perform search/replace operations on the database, replacing instances of the old site URL (e.g., https://staging.clientsite.com) with the new one (e.g., [https://clientsite.com](https://clientsite.com)). This tool can also be used with relative paths and custom strings. However, this manual process leaves considerable room for human error, as incorrectly replacing a string can render the entire website inoperable. Plugins like [All-in-One WP Migration](https://wordpress.org/plugins/all-in-one-wp-migration/) provide a user-friendly search/replace feature out of the box but come with the caveat of transferring files, undermining the benefits of our Pipelines workflow. Furthermore, since it reimports the entire wp-uploads directory, it can lead to excessively large files and slow loading times, making it unsuitable for migrating changes between installations. This type of plugin is better reserved for creating comprehensive backups of the website for archival and security purposes. [VersionPress](https://versionpress.com/) presents an intriguing solution, but its reliability in production environments hasn't been extensively proven yet. At present, plugins such as [WP Sync DB](https://wp-sync-db.github.io/) or [WP Migrate DB Pro](https://deliciousbrains.com/wp-migrate-db-pro/) appear to be the most viable options for managing WordPress databases. They facilitate pulling and pushing databases between installations while providing the option to automatically update URLs and paths. These plugins allow for the migration of specific tables, such as wp\_posts for post content only, eliminating the need to waste time reimporting users and site-wide settings. My preferred approach is to always pull from the live environment to prevent the accidental overwriting of production data. Here's an example setup using WP Sync DB (detailed walkthroughs can be found on [the WP Sync DB github](https://wp-sync-db.github.io/)): 1. **On the live site:** Install and configure WP Sync DB with the "Allow Pull from this repository" setting enabled. 2. **On the staging site:** Install and configure WP Sync DB with Pull from Live settings. Name this profile "live-to-staging." 3. **On your local development setup:** Install and configure WP Sync DB with Pull from Live settings. Name this profile "live-to-dev." Additionally, you might consider setting up a "dev-to-staging" push rule and configuring the staging site to allow database overwrites. ## Conclusion In my experience, these methods have proven effective for a wide range of use cases, including developing new WordPress websites and redesigning/restructuring existing live sites. This approach enables code deployments that keep all site versions current without adding unnecessary development overhead. It also promotes a deliberate and secure database migration strategy for seamless collaboration between environments. Plugin updates are managed within the version control system, allowing for thorough testing on the staging environment before deployment to the live site, minimizing the risk of production disruptions. While there's certainly room for improvement in integrating a more robust source control workflow for database management, until tools like VersionPress gain wider adoption in production environments, selective database pulls/pushes using WP Sync DB or WP Migrate DB Pro appear to be the most reliable approach. I'm eager to hear about the strategies that work best for your WordPress workflow, or perhaps, after reading this, you're feeling inspired to embrace the wild west and revert to your cowboy coding ways!
Licensed under CC BY-NC-SA 4.0