CI/CD on WordPress

Vitalii Kaplia Articles 22 February 6 min 23

«When someone tells me that WordPress is only for websites, I show them my CI/CD»

WordPress as CI/CD: yes, I’m serious

Late 2014, winter. I came as a WordPress developer to a volunteer project — and there I first encountered the use of Beanstalk. Push to repo — files on the server. No FTP, no manual copying. For me back then, it was magic.

From that moment on, Beanstalk became part of my workflow. More than ten years — without complaints. It just worked.

But when the number of projects grew — client sites, my own products, pet projects — I started counting. Another monthly subscription to an external service — all for one simple operation: transfer files from the repository to the hosting. It’s like keeping a full-time courier who moves one box across the street every day.

This made me look at WordPress differently. Essentially, everything needed to build my own Beanstalk — already exists in WordPress.

Why “proper” CI/CD tools don’t fit

The first thing any DevOps engineer will say: “GitHub Actions + Docker + rsync”. A beautiful scheme. On paper.

Now reality: shared hosting. Hosting Ukraine. There’s no Docker here. No way to install packages. No systemd for services. SSH exists, but it’s limited — no root. GitHub Actions can build a project, but how do you deliver files to the server without full rsync or SCP?

Jenkins? A separate server that needs to be rented, configured, and maintained. Deployer? Requires SSH with keys and a full CLI environment. All of this — additional infrastructure and additional costs for one task.

I could have gotten a VPS for CI/CD. But that’s like renting a garage to store one wrench.

WordPress — not a CMS, but an application framework

This idea took shape over time. But when you’ve been working with WordPress for seventeen years — you start seeing it not as a CMS for websites, but as a platform with a full set of tools:

  • Custom Post Types — data structures for anything
  • ACF PRO — building interfaces without writing HTML forms
  • REST API — integration with any external service
  • WP-Cron — deferred and regular tasks
  • Admin panel — ready UI with authorization, search, filtering

The idea crystallized simply: every deploy is a record in WordPress. Custom Post Type with fields: from where (repository + branch), to where (site on hosting). Create a record — the system automatically registers a webhook on GitHub. Push — files on the server. Delete the record — webhook disappears. Zero manual work.

The system is connected via API to GitHub and Hosting Ukraine — repositories, branches and sites are pulled automatically.

What’s under the hood

The stack is minimal, but each element is in its place:

  • WordPress — system core, data storage, UI
  • ACF PRO + Timber/Twig — custom fields and admin panel templates
  • GitHub API — webhook management, list of repositories and branches
  • Hosting Ukraine API (adm.tools) — list of sites with paths to directories
  • Telegram Bot API — deploy status notifications

Two custom ACF fields that solve UX

The most interesting part — two field types written from scratch for ACF.

GitHub Repository — cascading select. The first list pulls repositories of the authorized user through the GitHub API with 15-minute caching. Select a repository — the second list loads branches via AJAX, 10-minute cache. No manual input, no naming errors.

Hosting Ukraine Sites — similar logic for hosting. Domain → subdomain. Each option automatically stores the path to the site’s home directory on the server. All through the adm.tools API.

Create a deploy — just select from the lists. Like a constructor: from where → to where. Two selects on the left, two on the right. Done.

One push — code on the server

When saving a record, the system generates a unique token — an encrypted ID via AES-256-CBC — and forms the webhook URL. This URL is automatically registered on GitHub. When you delete the record, the webhook is deleted. Everything automatically.

When GitHub sends a push event, a chain begins:

  1. Token decryption, configuration search
  2. Message in Telegram: “Deploy started”
  3. Download ZIP archive from GitHub repository (authorization via Personal Access Token)
  4. Unpacking, clearing the target directory, moving files
  5. Deleting unnecessary files: .md, .gitignore, prepros.config
  6. Message in Telegram: “Done” or “Error”

For webhook deploys, the process runs asynchronously — PHP script in the background via CLI. GitHub gets a response immediately, doesn’t wait for completion. For manual deploys via a button in the admin — synchronous mode with the result straight in the browser.

Push from your phone, message in Telegram within a minute — code is already on the server. This is the level of automation it was all built for.

Bonuses I didn’t plan

When you have a working infrastructure with UI and a list of all sites — the next ideas come naturally.

HTTP Authentication manager

The second Custom Post Type — “Authentication”. Select a site from the list, enter login and password — the system generates .htpasswd and adds the appropriate block in .htaccess. Delete the record — protection is removed automatically.

Close staging with a password, open it for the client — two clicks instead of an SSH session. There’s even an API endpoint for getting authorization data — I use it in my own Chrome extension to open password-protected sites in one click. But I’ll tell you about that later — it’s a separate big and very interesting topic.

Honest about limitations

The system isn’t perfect. I know this and I’m not pretending it’s an enterprise solution.

  • No rollback — broke the deploy — either push a fix or restore from hosting backup. Storing the previous version before clearing — on the roadmap, but in half a year of operation, the need never came up
  • ZIP instead of git clone — each deploy downloads the full repository archive. For WordPress themes and plugins (a few megabytes) this is non-critical, but for a large monorepo it would be inefficient
  • WordPress as a single point — if WordPress on this server goes down, the deploy goes down too. Hasn’t happened in six months, but the risk exists
  • One hosting provider — integration with Hosting Ukraine API. For another provider, it would need to be adapted

This is a tool built for the specific conditions of a specific developer. In those conditions — thirty-plus projects on shared hosting — it works flawlessly.

Summary

More than thirty deploy configurations, client projects and pet projects — everything runs through one WordPress. Not a single failure in six months. Not a single unnecessary subscription.

The main takeaway: the best solution isn’t the one recommended at conferences. It’s the one built for your actual needs, using tools you master virtuosically.

If you have a business and feel like your infrastructure is held together with tape and manual work — write to me. I build things that work. For your tasks, your stack and your needs.

Vitalii Kaplia

Founder, Web Developer & WordPress Expert

I became interested in programming back in 1997. The first acquaintance with a future profession was using Visual Basic. In…

More about author

Custom WordPress development expert

Free consultation + cost calculation

More interesting articles

Start typing to search
Customer login

This site uses cookies

We use cookies to personalize content and ads, provide social media features, and analyze our traffic. We also share information about your use of our website with our social media, advertising, and analytics partners, who may combine it with other information you have provided to them or collected when you use their services. By continuing to use our site, you consent to our use of cookies and accept our Privacy Policy and Terms of Use.

Any questions?