WordPress Development
Staging to Production: A Safe Deployment Workflow for Agency Teams
Code flows up, content flows down. A repeatable staging-to-production deployment sequence for WordPress agency teams, including database changes and rollback.
Deployment day on a WordPress project is usually the least engineered part of an otherwise disciplined build. Someone pushes theme files over SFTP, someone else exports the database, and a search-and-replace runs against production while the client’s team is still editing content. If it works, nobody writes down why. If it breaks, everybody is on a call.
A repeatable WordPress staging deployment workflow solves this, and it does not require a platform team or a month of tooling work. This post lays out the environments to run, what moves between them and what must not, a deployment sequence you can hand to any developer, and the rollback plan you need before you touch production.
Why WordPress deployment is awkward
In most application stacks, code is the only thing that ships. WordPress mixes code, configuration and content in the same database, which is why the usual advice from other ecosystems does not translate cleanly.
- Plugin and theme settings live in
wp_options, next to the client’s content. - Serialised data in the database means a naive find-and-replace on URLs corrupts arrays.
- Uploads are files on disk, not in version control, and they grow constantly on production.
- Page builders store layout as post content, so a “layout change” is a content change.
The consequence is a simple rule that governs everything below: code flows up, content flows down. Code moves from local to staging to production. Content and uploads move from production back down to staging. If you ever find yourself pushing a database upward to a live site with real users, stop and reconsider.
The three environments worth running
| Environment | Purpose | Database source | Who touches it |
|---|---|---|---|
| Local | Development, experiments, debugging | Sanitised copy of production | Developers only |
| Staging | Integration, QA, client review | Refreshed from production | Developers, PM, client reviewers |
| Production | The live site | Source of truth for content | Deploy process and content editors |
Staging is only useful if it genuinely resembles production. Same PHP version, same plugin set including licensed ones, same server software, comparable content volume. A staging site running on different PHP with half the plugins disabled tells you nothing except that the site loads.
Three things must always differ, and they should be enforced in configuration rather than remembered: staging carries noindex headers and HTTP authentication, transactional email is intercepted or redirected to a catch-all, and payment gateways run in test mode. A staging site that emails real customers is a career-defining mistake and it happens most weeks somewhere.
What goes in version control
Keep the repository narrow and deliberate. The standard we use on client builds:
- In the repo: custom theme, custom plugins, must-use plugins, build tooling, deployment scripts, environment config templates.
- Managed by a dependency file: third-party plugins and WordPress core, pinned to versions, installed at build time rather than committed.
- Never in the repo:
wp-config.phpwith real credentials, the uploads directory, cache directories, database dumps, API keys.
Secrets belong in environment variables or in your host’s secret store. If a licence key or database password has ever been committed, rotate it rather than deleting the commit, because the history remains readable in every clone. That is the same discipline that keeps sites out of the incident queue described in our agency malware response playbook.
A nine-step deployment sequence
Write this down once, put it in your project management tool as a template checklist, and run it identically every time. Predictability is the whole product here.
- Freeze content. Tell the client’s editors to hold publishing for the deployment window. Thirty minutes is usually enough.
- Refresh staging from production. Pull the current database and uploads down so you are testing against today’s reality, not last month’s.
- Merge and deploy to staging. Run the build, apply any database migrations, and confirm the site boots without notices.
- Run the QA pass. Critical paths first: forms, checkout, login, search. Then the specific changes in this release.
- Take a production backup. Files and database, verified as complete, stored somewhere you can reach if the host is the problem.
- Deploy code to production. Atomic release if your host supports it, so the switch is a symlink change rather than files appearing one by one.
- Run migrations and flush. Apply structural database changes, flush permalinks and object cache, purge the CDN.
- Smoke test live. The same critical paths, on the real site, on a real phone. Check error logs for the first few minutes.
- Unfreeze and record. Tell editors they are clear, note what shipped and when, and log anything that surprised you.
Steps five and eight are the two people skip when they are in a hurry, and they are the two that decide how bad a bad deployment gets. A verified backup you never use costs ten minutes. An unverified backup you do need costs a weekend.
Handling database changes without breaking content
This is where WordPress deployments actually go wrong. Content on production keeps moving while your staging database sits still, so you cannot simply push the whole database up. Options, roughly in order of preference:
- Configuration as code. Register post types, taxonomies, field groups and settings in code so they deploy with the theme and need no database sync at all. This is the right answer whenever it is available.
- Selective table or row sync. Move only the specific options rows a release needs, scripted and reviewed, rather than the whole table.
- Migration scripts. A versioned script that makes the change idempotently on whatever database it runs against. Slightly more work, entirely repeatable.
- Manual re-entry. For a handful of settings, doing it by hand on production with a written checklist beats an automated sync you do not fully trust.
- Full database push. Only acceptable on a pre-launch site with no real content or users, or during a planned full migration.
When URLs change, always use a serialisation-aware search-and-replace tool rather than SQL. A raw UPDATE against post content will break every serialised array whose string length no longer matches, and the damage often does not appear until a widget or field silently returns nothing. Full-site moves follow their own sequence, which we cover in the zero-downtime WordPress migration checklist; if the move is complex or the stakes are high, WordPress migration services exist precisely so you are not learning DNS TTLs on the night.
Rollback and release cadence
Decide your rollback plan before you deploy, not while the site is down. In practice there are three levels.
| Failure | Response | Typical time |
|---|---|---|
| Code bug, no data change | Revert to the previous release | Under 5 minutes with atomic deploys |
| Code plus a reversible migration | Revert code, run the down migration | 10-20 minutes |
| Data corruption or destructive change | Restore the pre-deploy database backup, accept content loss in the window | 30-90 minutes |
The third row is why the content freeze matters. If editors were publishing during your deployment, restoring the backup destroys their work, and now you have two problems. On cadence: deploy small and often rather than monthly. Ten small releases are far easier to debug than one large one, because when something breaks you already know which change caused it.
Agencies without an in-house DevOps habit often get this process set up once by an outside team and then run it themselves. That fits the model described in our guide to white label WordPress development for agencies, and ongoing release management sits comfortably inside a WordPress maintenance and support arrangement.
Frequently asked questions
Is a host’s one-click staging good enough?
For content review and small fixes, usually yes. The limitation is the push-back-to-production step, which often overwrites the whole database. Use one-click staging as a review environment and keep code deployments in version control.
Do we need CI/CD for a small client site?
Not always. A brochure site updated twice a year does not justify a pipeline. Anything with a store, a membership, or more than one developer touching it does. The break-even point is roughly when two people can deploy the same site.
Should third-party plugins be committed to the repository?
Prefer a dependency manager with pinned versions, so upgrades are explicit and reviewable. Committing them works and is simpler, but it makes the repo large and hides update decisions inside noisy diffs. Either way, pin versions rather than letting production auto-update silently.
How do we deploy when the client edits content daily?
Keep configuration in code so you never need to push a database upward, and use short scheduled windows with a content freeze. Agree the window in advance as part of the maintenance agreement rather than negotiating it each time.
Who should be allowed to deploy to production?
A named, small set of people, with the process documented so any of them can run it identically. Deployment ability should not depend on one person’s local machine or one person’s memory of the steps.
If you want this workflow set up across a portfolio of client sites without pulling your own developers off billable work, our dedicated monthly plans cover exactly that kind of ongoing engineering, month to month with no lock-in.
// related