Plugin & theme provisioning
Because the image is immutable (DISALLOW_FILE_MODS), nothing is installed or updated from
the WordPress admin. Every plugin and theme is put into the image at build time, and a
plugin version is changed by rebuilding the image. This page covers the two provisioning
sources, licensing, and — the part that catches every plugin — how a plugin that needs to
write to disk is accommodated under a read-only root filesystem.
Two sources
| Source | How |
|---|---|
Public (on wordpress.org) |
Declared as a Composer dependency via the WPackagist mirror and
installed in Stage 1 of the build. Versions are pinned in |
Private / commercial / third-party (not on wordpress.org) |
Supplied as an artefact (zip or tarball) and baked into the image in Stage 2 with a |
Public plugins (Composer)
Add the plugin to composer.json under the WPackagist repository and pin it via
composer.lock:
"repositories": [
{ "type": "composer", "url": "https://wpackagist.org", "only": ["wpackagist-plugin/*", "wpackagist-theme/*"] }
],
"require": {
"wpackagist-plugin/<slug>": "*"
}
The <slug> is the plugin’s wordpress.org slug, which equals its wp-content/plugins/
directory name. Confirm a plugin is actually hosted on wordpress.org before assuming it is
public — a 404 here means it is a private/third-party plugin and must be baked instead:
curl -s -o /dev/null -w "%{http_code}" https://api.wordpress.org/plugins/info/1.0/<slug>.json
Private, commercial and third-party artefacts
These are not on WPackagist. The artefact is placed in a build-only, git-ignored directory and baked in:
# theme tarball whose root is wp-content/themes/<Name>
tar -xzf packages/<theme>.tar.gz --strip-components=2 -C web/app/themes wp-content/themes/<Name>
# premium plugin zip whose root is the plugin folder
unzip -q packages/<plugin>.zip -d web/app/plugins
# GPL plugin distributed only from a vendor git repo
git clone --depth 1 --branch <tag> <repo> /tmp/src && cp -r /tmp/src/<subdir> web/app/plugins/
Because the image build is where licensed artefacts are handled, the image is built in a trusted environment (not in shared CI, where large licensed zips cannot be stored as secrets). Only the Helm chart is published from CI.
Licensing
Commercial plugins are activated one of two ways, and neither puts a licence file on the read-only filesystem:
-
Online connect (e.g. UpdraftPlus, Divi) — the plugin code is baked in; activation is a one-time login/connect in the admin that registers the site against the account and stores a token in the database. Because it lives in the DB it survives every pod restart; you do it once after first deploy.
-
Licence key — a key entered in the admin, or injected as an environment variable from the Secret and applied during bootstrap.
Under the immutable model the plugin’s own auto-updater is irrelevant — updates come from rebuilding the image with a newer artefact, not from the admin.
Writable paths under a read-only root filesystem
This is the rule that every plugin is measured against. The root filesystem is read-only, so a
plugin that writes anywhere under wp-content will fail unless that exact path is a mounted,
writable volume. Choosing the wrong kind of volume causes subtler failures, so the decision
matters:
| The path is… | Use | Because |
|---|---|---|
Persistent state the plugin owns as a self-contained store (e.g. backups) |
its own PVC |
It must survive restarts and must not be entangled with other data. Never make it a
|
Persistent, and genuinely part of the media tree |
the uploads PVC (optionally a |
It belongs with uploads and is not something the app deletes wholesale. |
Transient — a cache, or a working dir the app deletes and recreates |
an |
No need to persist, and an |
|
Two traps that cost real debugging time
A Never nest a plugin’s store inside another volume’s tree. Mounting a backup directory as a
|
Adding a plugin — checklist
-
Determine the source:
curlthe wordpress.org API for the slug. -
Public → add
wpackagist-plugin/<slug>tocomposer.json, runcomposer updateto pin it. Private → place the artefact in the build-onlypackages/dir and add theCOPY/extract step to the Dockerfile. -
Add the slug to the bootstrap activation list.
-
Identify any paths the plugin writes to under
wp-content; add a writable volume of the correct type (per the table above) to the Helm chart’s Deployment. -
If it needs a licence, decide connect-vs-key and where the secret lives.
-
Add a page to the plugin catalogue recording the above.