WordPress Plugin Development Workflow Guide
Docker + Local Dev + Codex + GitHub + ISPConfig 3 Deployment
This guide is for future WordPress plugin projects using the same general workflow:
- local development with Docker
- code generation/iteration with Codex
- source control in GitHub
- deployment to a live ISPConfig 3 WordPress server
- plugin-first development rather than theme-first development
This is written as a practical operating manual, not a theory document.
1. Core workflow at a glance
The full loop is:
- Start local WordPress in Docker.
- Mount the plugin repo directly into
wp-content/plugins. - Edit and test locally.
- Use Codex in small chunks, not giant one-shot prompts.
- Commit changes locally.
- Push to GitHub.
- SSH into the live server.
git pullthe plugin repo on the server.- Verify in WordPress admin.
- Only use ZIP upload for emergency/manual fallback or packaged releases.
The most important mindset change is this:
Do not develop by repeatedly downloading ZIPs and re-uploading plugins. That is a fallback, not the primary workflow.
2. Recommended architecture for future WordPress plugin work
Use this default stack unless there is a strong reason not to:
- Editor: VS Code
- AI coding assistant: Codex
- Local environment: Docker Compose
- WordPress runtime: official WordPress Docker image
- Database: MySQL via Docker
- CLI: WP-CLI in Docker
- Source control: Git + GitHub
- Server: ISPConfig 3-hosted WordPress instance
- Deployment:
git pullon the server - Fallback deployment: upload ZIP through WordPress admin
This keeps local dev, version control, and deployment all cleanly separated.
3. Project layout pattern
Keep a reusable local structure like this:
your-dev-root/
wp-local/
docker-compose.yml
wp/
plugins/
your-plugin-repo/
General intent:
wp-local/contains the Dockerized WordPress environmentplugins/your-plugin-repo/contains the actual Git repo- the plugin repo is bind-mounted into the Docker WordPress install
Do not put plugin code inside random ZIP folders or temporary directories if you can avoid it.
4. Local Docker WordPress setup
4.1 Create the local WordPress environment folder
Create a folder for the Dockerized site, for example:
wp-local/
Inside it, create:
docker-compose.yml- a
wp/folder will be created automatically on first boot
4.2 Use Docker Compose with three services
The standard local setup is:
db→ MySQLwordpress→ WordPress + Apache + PHPwpcli→ WordPress CLI container
4.3 Base docker-compose.yml template
Use this as the standard starter:
version: '3.9'
services:
db:
image: mysql:8.0
restart: always
environment:
MYSQL_DATABASE: wordpress
MYSQL_USER: wordpress
MYSQL_PASSWORD: wordpress
MYSQL_RANDOM_ROOT_PASSWORD: "1"
volumes:
- db_data:/var/lib/mysql
wordpress:
image: wordpress:php8.3-apache
restart: always
depends_on:
- db
ports:
- "8080:80"
environment:
WORDPRESS_DB_HOST: db
WORDPRESS_DB_USER: wordpress
WORDPRESS_DB_PASSWORD: wordpress
WORDPRESS_DB_NAME: wordpress
volumes:
- ./wp:/var/www/html
- /absolute/path/to/your/plugin-repo:/var/www/html/wp-content/plugins/your-plugin-slug
wpcli:
image: wordpress:cli
depends_on:
- db
- wordpress
user: "33:33"
working_dir: /var/www/html
environment:
WORDPRESS_DB_HOST: db
WORDPRESS_DB_USER: wordpress
WORDPRESS_DB_PASSWORD: wordpress
WORDPRESS_DB_NAME: wordpress
volumes:
- ./wp:/var/www/html
- /absolute/path/to/your/plugin-repo:/var/www/html/wp-content/plugins/your-plugin-slug
volumes:
db_data:
4.4 Windows path note
On Windows, Docker mount paths should usually use forward slashes:
C:/path/to/plugin-repo
not backslashes.
4.5 Start the environment
From inside wp-local/:
docker compose up -d
Open:
http://localhost:8080
Complete the WordPress installation in the browser.
4.6 Activate the plugin
Use WP-CLI:
docker compose run --rm wpcli wp plugin activate your-plugin-slug
Check installed plugins:
docker compose run --rm wpcli wp plugin list
5. Daily local development workflow
This is the normal loop:
- Open the plugin repo in VS Code.
- Ask Codex for a specific change.
- Review the diff.
- Refresh local WordPress in the browser.
- Test.
- Repeat until the feature works.
- Commit only after the feature is confirmed locally.
This should feel similar to web app development:
- edit code
- refresh
- test
No ZIP rebuild step should be part of the normal loop.
6. How to work with Codex effectively
6.1 Do not give one giant prompt for a large feature
For WordPress plugin changes, use small, bounded chunks.
Best default pattern:
- Prompt 1: inspect only
- Prompt 2: backend/core logic
- Prompt 3: admin/config UI
- Prompt 4: runner UX / integration
- Prompt 5: tests / cleanup / verification
For medium features, 3 chunks is usually enough:
- Chunk 1: backend contracts and apply logic
- Chunk 2: admin configuration
- Chunk 3: frontend/runner integration and cleanup
6.2 Good Codex prompt rules
Always tell Codex to:
- read
AGENTS.mdfirst if the repo has one - preserve existing architecture and naming conventions
- avoid unrelated refactors
- stop after the requested chunk
- summarize changed files
- preserve backward compatibility unless there is a strong reason not to
6.3 Prompt structure that works well
A strong Codex prompt usually includes:
- the exact bug/feature
- the scope boundaries
- files/patterns to inspect
- implementation constraints
- definition of done
- requested output format
7. Recommended local testing workflow
For plugin testing, use a dedicated local WordPress site with safe throwaway data.
7.1 Seed useful test content
Create:
- several posts
- several drafts
- several media items
- at least one admin user
- optionally one lower-permission user
This helps test:
- generation
- apply/update flows
- permissions
- media metadata
- post excerpt/title/content updates
7.2 Test in this order
For any feature:
- Save admin config successfully
- Run the AI tool successfully
- Apply/update the selected target successfully
- Re-open the target in WordPress and confirm persisted data
- Confirm unrelated plugin behavior still works
7.3 Use explicit smoke tests
For example, if testing excerpt updates:
- select a known draft
- generate excerpt
- apply excerpt
- open the post editor
- confirm excerpt is saved
Do not rely only on success toasts. Always verify persisted state in WordPress itself.
8. Logging and debugging workflow
When something fails, debug in this order.
8.1 Browser DevTools
Open:
- Console
- Network tab
Look for:
- failed AJAX requests
- failed REST requests
- request payloads
- response bodies
- JavaScript errors
This is often the fastest way to catch:
- wrong payload shape
- missing field mapping
- failed nonce/auth
- 400 validation errors
8.2 Docker logs
From the local WordPress folder:
docker compose logs -f wordpress
This shows PHP and Apache-side issues.
8.3 Enable WordPress debug logging
In wp-config.php, set:
define('WP_DEBUG', true);
define('WP_DEBUG_LOG', true);
define('WP_DEBUG_DISPLAY', false);
@ini_set('display_errors', 0);
Then inspect:
wp/wp-content/debug.log
8.4 Add temporary plugin-side logs
For targeted debugging, add error_log() statements around:
- route entry
- permission checks
- field mapping logic
wp_update_post()update_post_meta()- final response payload
Use a clear prefix like:
error_log('PLUGIN APPLY: ...');
Then remove or reduce noisy logs after the fix is confirmed.
8.5 Common failure pattern to remember
If:
- generation works
- apply fails with 400
- there is no fatal PHP error
then the issue is often:
- payload mismatch
- unsupported field mapping
- backend validation mismatch
- missing apply contract
Not a WordPress core failure.
9. WordPress REST and Docker self-request caveat
A common Site Health warning in Docker is:
- WordPress tries to make a server-side request to
http://localhost:8080 - inside the container,
localhostrefers to the container itself - Apache inside the container listens on port
80, not8080 - so server-to-self HTTP calls to
localhost:8080may fail
This can produce Site Health warnings like:
- REST API self-test failures
- cURL error 7
- failed loopback requests
Important takeaway:
- browser-to-WordPress requests can still work fine
- this is often a local Docker loopback issue
- do not assume it is the root cause of a plugin bug unless the plugin itself uses server-side HTTP calls back to the site
Prefer direct WordPress function calls over wp_remote_get() / wp_remote_post() back into your own site where possible.
10. Git workflow
10.1 Initialize the plugin repo
Inside the plugin repo:
git init
git add .
git commit -m "Initial commit"
Add GitHub remote:
git remote add origin <your-github-repo-url>
git branch -M main
git push -u origin main
10.2 Normal working loop
For each tested local change:
git add .
git commit -m "Describe the change"
git push
Use small, meaningful commits. Do not wait until the plugin is massively changed.
10.3 Good commit message style
Examples:
fix excerpt apply mappingadd attachment metadata apply routevalidate canonical apply fieldsimprove runner error handling
Keep them specific and readable.
11. Live server deployment with ISPConfig 3
11.1 Recommended deployment approach
For ISPConfig 3-hosted WordPress, the recommended long-term workflow is:
- plugin code lives in a Git repo
- the live plugin folder on the server is a Git clone
- updates are deployed with
git pull
This is much better than repeatedly uploading ZIP files.
11.2 One-time server setup
SSH into the server:
ssh your-user@your-server
Go to the WordPress plugin directory for that site.
The exact path will vary by site, but in ISPConfig 3 it often looks like:
/var/www/clients/clientX/webY/web/...
The exact plugin path will depend on your site layout.
If the plugin was previously uploaded manually, remove or archive the old folder first.
Clone the GitHub repo into the plugin directory:
git clone <your-github-repo-url> your-plugin-slug
Then verify the plugin appears in WordPress admin.
11.3 Normal deployment after setup
After local testing and pushing to GitHub:
ssh your-user@your-server
cd /path/to/live/plugin/folder
git pull
That is the standard deploy.
11.4 Recommended server-side checks after pull
After deployment:
- refresh WordPress admin
- verify plugin loads
- test the changed feature
- review PHP error logs if needed
- reactivate plugin only if required
11.5 File permissions
If server file ownership causes trouble after pull, fix ownership or group permissions according to the server’s web user.
Do not casually run broad permission changes without knowing the host setup.
12. When to use ZIP upload anyway
ZIP upload still has a place, but it should not be the default workflow.
Use ZIP upload for:
- emergency manual installs
- client delivery packages
- one-off installs on servers without Git access
- release artifacts
- backup fallback if server Git workflow is unavailable
Do not use it for ordinary iteration if you control the server.
13. Optional but useful upgrades
13.1 WP-CLI shortcuts
Create shell aliases or helper scripts so you do not keep typing long commands.
Examples:
alias wpup='docker compose up -d'
alias wpdown='docker compose down'
alias wplogs='docker compose logs -f wordpress'
alias wpcli='docker compose run --rm wpcli wp'
Then use:
wpcli plugin list
wpcli plugin activate your-plugin-slug
13.2 Xdebug
If future plugin work gets more complex, add Xdebug to local Docker.
Use it when:
- you need step debugging
- stack traces are not enough
- debugging PHP execution flow becomes painful
13.3 Seed scripts
For plugin-heavy workflows, it is worth adding simple seed scripts for:
- test posts
- test drafts
- test media
- test users
That makes repeated testing much easier.
13.4 CI/CD later
Once the workflow stabilizes, you can automate deployment using:
- GitHub Actions
- deploy over SSH
- or server-side hooks
But do not overcomplicate this too early. Start with:
- local Docker
- GitHub
git pull
That is a very good baseline.
14. How to think about plugin architecture changes
When planning a feature, always ask:
- does this belong in admin configuration?
- does this belong in runtime execution?
- does this belong in an explicit apply/update path?
- should this use direct WordPress functions instead of self-HTTP?
- what is the smallest implementation that fits the current plugin structure?
Good plugin development usually means:
- minimal moving parts
- explicit flows
- strong validation
- clear separation between generation and persistence
15. Practical “golden workflow” for future projects
This is the workflow to follow by default.
New plugin project
- Create plugin repo.
- Create local Docker WordPress environment.
- Mount plugin repo into
wp-content/plugins. - Start Docker.
- Activate plugin with WP-CLI.
- Commit initial scaffold.
- Push to GitHub.
- Clone repo into live server plugin directory.
- Deploy future updates with
git pull.
New feature
- Write or update
AGENTS.md. - Ask Codex to inspect only.
- Implement in 2–4 bounded chunks.
- Test locally after each chunk.
- Commit and push.
- Deploy via
git pull. - Verify in live admin.
New bug
- Reproduce locally first.
- Check browser Network + Console.
- Check Docker logs.
- Check
debug.log. - Add temporary targeted logging.
- Fix locally.
- Commit.
- Push.
- Deploy.
- Verify again on live.
16. Common mistakes to avoid
Avoid these:
- developing by repeated ZIP upload
- changing too much in one Codex prompt
- skipping local testing before pushing
- relying only on success messages instead of verifying persisted data
- letting frontend field names, output keys, and backend target fields drift apart
- using server-to-self HTTP when direct WordPress functions would work
- editing the live server directly instead of through Git
17. Personal checklist before declaring a feature done
Before pushing/deploying, verify:
- local WordPress environment runs
- plugin activates cleanly
- no fatal PHP errors
- browser Network requests look correct
- target data is actually persisted
- debug logging is cleaned up or reduced
- Git status is clean
- commit message is meaningful
- live deploy path is ready
18. Fast reference commands
Docker
docker compose up -d
docker compose down
docker compose logs -f wordpress
docker compose run --rm wpcli wp plugin list
docker compose run --rm wpcli wp plugin activate your-plugin-slug
Git
git status
git add .
git commit -m "your message"
git push
git pull
Server deploy
ssh your-user@your-server
cd /path/to/live/plugin/folder
git pull
19. Final operating principle
For future WordPress plugin projects, the winning system is:
- Codex for scoped implementation
- Docker for safe local WordPress dev
- WP-CLI for operational speed
- GitHub for source control
git pullon ISPConfig for deployment
If a workflow step feels repetitive, manual, or fragile, it should probably be replaced with a cleaner local dev or Git-based approach.
That is the standard to keep.