Build and deployment
Deployment is Bitbucket Pipelines running git ftp push over SFTP to WP Engine.
Three environments map to three branches; production is a manual pipeline off
master. There is no build step in CI — compiled assets are committed to the
repository.
Why it exists
Section titled “Why it exists”The repository contains only the theme (wp-content/themes/bpcollins) plus site
root files, not WordPress core or the database, so deployment is a file sync rather
than a release process. git ftp was chosen because WP Engine exposes SFTP and
Bitbucket Pipelines has no persistent shell on the target.
The consequence to internalise: CI does not compile anything. Whatever is in
assets/ at commit time is what production serves.
Entry points
Section titled “Entry points”| Path | What it does |
|---|---|
bitbucket-pipelines.yml |
The whole deployment definition — three pipelines |
wp-content/themes/bpcollins/gulpfile.js |
Local build: SCSS → assets/css, JS → assets/js |
wp-content/themes/bpcollins/package.json |
Toolchain and the watch / compile scripts |
wp-content/themes/bpcollins/inc/enqueue.php:50 |
get_cache_key() — reads .git-ftp.log on WP Engine for cache busting |
wp-content/themes/bpcollins/inc/required-plugins/required-plugins.php |
TGMPA list of required plugins |
wp-config-sample-base-theme.php |
Sample wp-config for a new local environment |
How it works
Section titled “How it works”Pipelines (bitbucket-pipelines.yml) all run the image
strategiq/deployomatic:curl and share one shape: check out the exact commit,
confirm with git name-rev that it belongs to the expected branch, then
git ftp push --insecure --auto-init with that environment’s credentials.
| Trigger | Branch | Target |
|---|---|---|
Manual (custom pipeline deploy-production) |
must be on master |
production |
| Automatic on push | staging |
staging |
| Automatic on push | development |
development |
git ftp keeps a .git-ftp.log file on the remote containing the deployed commit
SHA and transfers only files that changed since it. --auto-init creates that state
on a first deploy.
Cache busting. get_cache_key() returns the trimmed contents of .git-ftp.log
when $_SERVER['IS_WPE'] is set — so on WP Engine every enqueued asset is versioned
by the deployed commit, and a deploy invalidates asset caches exactly once. Locally
(no IS_WPE) it returns time(), busting on every request. If the log file cannot
be read it returns false, which makes WordPress omit the version entirely.
Local build. From wp-content/themes/bpcollins:
npm installnpx gulp watch— compilesresources/scss/**andblocks/**/*.scsstoassets/css/, rolls upblocks/**/*.jsandresources/js/app.jstoassets/js/**.min.js, and starts BrowserSync proxyinggulpfile.js’surlToPreview.- One-off tasks:
gulp sass,gulp js,gulp compile(via the npm scriptsgulpstyles,gulpscripts,gulpcompile).
abovethefold.scss is compiled without a sourcemap because its output is inlined
into <head> by header.php.
Plugins are not deployed by this repository. TGMPA declares them as required and
force-activates them: Yoast SEO, ACF PRO (bundled as a zip in
inc/required-plugins/), Contact Form 7, Google Apps Login Premium, Velvet Blues
Update URLs, Query Monitor, Redirection, Fly Dynamic Image Resizer, CookieHub and
WP Rocket.
flowchart TD A[developer: npx gulp watch] --> B[assets/css + assets/js updated] B --> C[commit source AND built assets] C --> D{branch} D -->|development| E[auto pipeline → dev SFTP] D -->|staging| F[auto pipeline → staging SFTP] D -->|master| G[manual deploy-production pipeline → prod SFTP] G --> H[git ftp writes .git-ftp.log on the remote] H --> I[get_cache_key reads it → asset version]Configuration
Section titled “Configuration”Bitbucket repository variables, one set per environment (documented at the top of
bitbucket-pipelines.yml) — names only:
DEVELOPMENT_SFTP_USER,DEVELOPMENT_SFTP_PASS,DEVELOPMENT_SFTP_HOST,DEVELOPMENT_SFTP_ROOTSTAGING_SFTP_USER,STAGING_SFTP_PASS,STAGING_SFTP_HOST,STAGING_SFTP_ROOTPRODUCTION_SFTP_USER,PRODUCTION_SFTP_PASS,PRODUCTION_SFTP_HOST,PRODUCTION_SFTP_ROOT
Host values include the port. Set these in Bitbucket → Repository settings → Repository variables, secured.
Environment detection in the theme uses $_SERVER['IS_WPE'] (set by WP Engine) and
$_SERVER['HTTPS']. gulpfile.js’s urlToPreview is per-developer and currently
committed as https://bpcollins.build/.
Invariants and gotchas
Section titled “Invariants and gotchas”- CI runs no build. If you commit SCSS or JS without running gulp, the deploy
succeeds and the change does nothing. This is the single most common deployment
surprise in this repo — always commit
assets/alongside sources. assets/is committed (225 tracked files). Two people building the same branch will produce conflicting minified diffs; resolve by rebuilding, not by merging minified output.--insecuredisables SFTP host-key verification in all three pipelines.- The branch guard is
git name-rev | grep. It checks whether the commit is reachable from a branch of that name, not that you deployed the branch tip. A commit reachable frommasterdeploys to production even when triggered from elsewhere, and the pipeline silently succeeds while doing nothing if the guard fails — no error, no output. git ftpdeletes remote files that were deleted in git, within the deployed root. Anything a client uploaded into a theme directory is at risk.- Deploys are incremental from
.git-ftp.log. If that file is lost or the remote is restored from a backup,git ftpcan refuse to push or push the wrong delta;--auto-initre-establishes state but re-uploads everything. - Losing
.git-ftp.logalso breaks cache busting, becauseget_cache_key()falls back tofalseand assets are then served with no version string — stale CSS/JS for returning visitors. get_cache_key()reads.git-ftp.logby relative path (inc/enqueue.php:55), so it depends on the PHP working directory being the site root.- The toolchain is old: gulp 4,
gulp-sass4 withnode-sass4. It will not install on a current Node — use nvm and an older Node (Node 14/16 era) fornpm install. package.jsonstill describes the base theme ("name": "strategiq","description": "Theme for strategiq.co", a GitLab repository URL). It is not this site’s metadata.- There is no rollback step. Rolling back means deploying an earlier commit through the same pipeline.
- No database or uploads are deployed — content moves between environments by other means (WP Engine copy tools).
Changing it safely
Section titled “Changing it safely”- Before any push: run gulp, confirm
assets/changed as expected, and commit both source and built files. - New environment → copy one of the three pipeline steps, add its four repository
variables, and keep the
git name-revguard pointed at the right branch name. - Changing the deployed root →
*_SFTP_ROOTdecides whatgit ftptreats as the remote root. Getting it wrong can delete files outside the theme. Verify against development first. - Adding a plugin dependency → add it to the TGMPA array in
inc/required-plugins/required-plugins.php. Plugins are installed on the site, not deployed from here. - Deliberately not abstracted: committed build output. It exists because CI has no
build step; if you add one, remove
assets/from version control in the same change or you will ship stale files. - Verify a deploy: check the pipeline actually transferred files (an empty transfer list usually means the branch guard failed), then load a page and confirm the asset version query string matches the new commit SHA.
No tests run in CI — bitbucket-pipelines.yml contains only deploy steps, and the
repository has no test suite. Deploy to development or staging and verify in a
browser before promoting to production.