This project provides local development environments for fast iteration of existing WordPress websites. This includes pre-configured Docker-based MySQL and PHP servers, our Docker-Build toolchain, Xdebug, ImageMagick and a number of helper scripts.
The project builds on the official WordPress docker image, currently v6.6.2
To update an existing project or start a new one, run the following commands in your working directory.
docker run --rm -it -v ${PWD}:/usr/src/site ideasonpurpose/wordpress:6.6.2 init
Followed by:
npm run bootstrap
NOTE: If ~/.composer doesn't exist, mounting the Docker volume will create the directory with root ownership, likely causing the Composer task to fail. Either create this directory before running npm run bootstrap
or reset it's ownership with sudo chown -R $UID:$GID .composer
and then run bootstrap
again. See #21
docker run --rm -it -v %cd%:/usr/src/site ideasonpurpose/wordpress:6.6.2 init
init
command copies all the necessary tooling files into place and sets up the default theme directory structure.npm run bootstrap
prepares the environment by installing npm and composer dependencies and reloading the database.
Docker and npm (node.js) should be installed. The development server runs from Docker images and all workflow commands are called from npm scripts.
In an empty directory, running init
will prompt for the new project's name and description, then build out a complete environment with a basic theme skeleton.
Recently updated project can run npm run project:refresh
to update tooling to the latest release.
Older projects should run init
manually to update. Projects should be in a clean Git working tree so any undesirable changes can be easily reverted.
- Docker-compose files will be updated to the latest versions.
- Default package.json scripts will be merged onto existing scripts.
- DevDependencies, Scripts and Prettier properties will be copied onto existing package.json values.
- Default composer.json packages and metadata will be copied onto existing composer.json values.
- Update .gitignore from gist
- Basic theme folder-structure will be non-destructively refreshed with missing folders added.
- Missing ideasonpurpose.config.js files will be created.
- Permissions will be reset for the theme directory and known tooling files.
Before calling npm run start
, copy a database snapshot into the top-level _db directory, add any required plugins to Plugins and mirror media files to Uploads.
Plugins and Uploads folders should not be committed to Git, but should be mirrored from production sites so the local environment works as expected.
After configuring your SSH key path in .env, the database, plugins and uploads be can be synced down from a remote server with the npm run pull
command. The .env.sample file documents the required credentials.
All *.sql
files from the top-level _db directory will be in alphabetical order. Later files will overwrite earlier ones.
-
npm run start
Spins up a database and php server, then serves all content through the devServer proxy at http://localhost:8080. Files in the project directory will be watched for changes and trigger reloads when saved. Type control-c to stop the local server. Change the default port with--port=8080
-
npm version [major|minor|patch]
Increments the version then uses version-everything to update project files before callingnpm run build
which generates a production build and compresses all theme files into a versioned, ready-to-deploy zip archive.
bootstrap
A helper script for starting projects. This will install npm and composer dependencies, reload the MySQL database, activate the development theme and sort the package.json file.build
- Generate a production-ready build in a zip archive. Ready-to-deploy.composer
Runscomposer install
from Docker.composer:install
- Installs packages from the composer.lock filecomposer:require
- Add new packages and update composer.jsoncomposer:update
- Updates composer dependencies to their newest allowable version and rewrites the composer.lock file. Opens a mysql shell to the development WordPress database
db:admin
- Starts a phpMyAdmin server at localhost:8002db:dump
- Writes a compressed, timestamped database snapshot into the _db directorydb:pull
- Alias forpull:db
db:reload
- Drops then reloads the database from the most recent dumpfile in _db, then attempts to activate the development themedb:shell
- Opens a shell to the development WordPress databasedev
- Alias forstart
mariadb
,mysql
- Aliases fordb:admin
mariadb-dump
,db:dump
,mysql:dump
,mysqldump
- Aliases fordb:dump
mariadb:reload
,mysql:reload
- Aliases fordb:reload
phpmyadmin
- Alias fordb:admin
project:refresh
- Update the project with the latest tooling.pull
Syncs data from a remote server to the local development environment. The bare command will run these sub-commands:pull:db
- Syncs down the most recent mySQL dumpfile, backs up the current dev DB then reloads the DBpull:plugins
- Syncs down wp-content/plugins from the remotepull:uploads <$YEAR>
- Syncs down the current year's wp-content/uploads/$YEAR from the remote. Sync specific years with the optional year argument.pull:uploads-all
- Syncs down the entire wp-content/uploads directory from the remote
logs:wordpress
- Stream the WordPress debug.logwp-cli
- Runs wp-cli commands. The default command re-activates the development theme.
On macOS hosts, modifying permissions inside a mounted Docker volume will add extended attributes to the shared files on the host instead of modifying their actual mode or ownership. To see these values in the terminal, run ls -la@
or xattr -l <file>
. Extended attribute values are prefixed with com.docker.grpcfuse
regardless of whether Docker is using gRPC FUSE or VirtioFS (they're both FUSE).
The npm run pull
command brings together several sub-commands to sync remote data to the local development environment. Each command can also be called individually. Connection info must be configured in a .env file. Values are documented in the .env.sample file.
Private SSH keys are passed to the image as Docker Secrets, point $SSH_KEY_PATH
to a local private key in .env.
Pulling uploads, plugins and database dumps is currently supported on WP Engine and Kinsta*.
Connections must be configured on a per-machine basis using a .env
file in the project root. For new projects, rename the .env.example
to .env and update the settings.
The important properties are:
-
SSH_KEY_PATH
Local path to your private key. If you uploaded aid_rsa_wpengine.pub
key to your WP Engine account, point this to the pair's matching private key:~/.ssh/id_rsa_wpengine
-
SSH_LOGIN
This is simply the SSH connection string from WP Engine backend, something likeiop001@iop001.ssh.wpengine.net
where the elements are${SSH_USER}@${SSH_HOST}
. Each item can also be entered individually, individual entries take precedence over components extracted from SSH_CONNECT_STRING. -
SSH_USER
The user account which connects to the server. -
SSH_HOST
The server address to connect to. -
SSH_WP_CONTENT_DIR
(default: sites/${SSH_USER}/wp-content) The path to the wordpress wp-content folder. Most likely matches theWP_CONTENT_DIR
WordPress constant. Does not include a trailing slash. Can relative to the SSH user home folder or an absolute path.
Both $SSH_LOGIN
and $SSH_HOST
can be extracted from $SSH_LOGIN
. Specifying either will override the value in $SSH_LOGIN
.
Unlike WP Engine, Kinsta does not store regular database snapshots in a site's wp-content directory, but they do allow cron. Set up a basic crontab task to regularly backup the database so pull scripts will work correctly. Here's an example which backs up hourly at 37 minutes after the hour:
# dump db hourly for dev mirrors
37 * * * * mysqldump --default-character-set=utf8mb4 -udb_user -pdb_password db_name > ~/public/wp-content/mysql.sql
Neither Kinsta's nor WP Engine's servers will not fulfil requests for *.sql files, and the db_user
. db_password
and db_name
values are stored already stored in plaintext in wp-config.php so this isn't a security risk.
WP_DEBUG
is enabled by default, and can be toggled by setting the WORDPRESS_DEBUG
variable in the .env config file.
The base image provides a specific version of WordPress, but once running that version can be upgraded using the wp-admin dashboard, just like any other site.
wp-cli can also be used to update to pre-release version of WordPress. An example command looks like this:
npm run wp-cli wp core update https://wordpress.org/wordpress-6.5-RC3.zip
Versions can be rolled back by removing the docker *_wp
volume.
The npm run bump
script will query the WordPress releases API and DockerHub, then update the docker image and readme to the latest WordPress image.
To update to a pre-release image, enter a valid DockerHub tag into the wp-version.json file.
Projects often rely on plugins which are developed in parallel. A number of placeholder IOP_DEV_PLUGIN_#
environment variables are provided which can be used to directly mount plugins into the WordPress environment. These enable better version control and dependency management since the nested and .gitignored wp-content/plugins directory often conflicts with a parent theme.
To add a development plugin to the WordPress environment, point the plugin's local relative path to an absolute path inside the container. Here's how we would make an example-plugin project being developed in a sibling directory available to the current WordPress development environment:
IOP_DEV_PLUGIN_1="../example-plugin:/var/www/html/wp-content/plugins/example-plugin"
IOP_DEV_PLUGIN_2=
IOP_DEV_PLUGIN_3=
To open a shell on any running Docker container, run docker ps
to retrieve container IDs or Names, then run docker exec -it <name or ID> bash
. Some containers may use sh
instead of bash. To open a shell on the running WordPress instance, run docker-compose exec wordpress bash
.
The Composer image can also run other, more specific commands directly from docker-compose
:
docker-compose run --rm composer update
docker-compose run --rm composer require monolog/monolog
# Open a shell in the composer image
docker-compose run --rm composer bash
All services which provide a server can have their default ports customized with the --port=
flag. This allows for multiple projects to be run simultaneously on the same computer.
# site one
npm run start --port=8080
# site two
npm run start --port=8081
- webpack devserver:
8080
- phpMyAdmin:
8002
- WebGrind:
9004
A PHP Info page is available at localhost:8080/info.php
.
To profile a request with XDebug and WebGrind, add ?XDEBUG_PROFILE=1
to any request. A cachegrind.out.nn file will be created in webpack/xdebug. Running npm run webgrind
will launch a webgrind server for viewing those files. The default address is http://localhost:9004, or change ports with npm run webgrind --port=9123
.
Every profiled run can also be viewed as a call graph. These graphs are documented in the gprof2dot project:
+------------------------------+
| function name |
| total time % ( self time % ) |
| total calls |
+------------------------------+
where:
- total time % is the percentage of the running time spent in this function and all its children;
- self time % is the percentage of the running time spent in this function alone;
- total calls is the total number of times this function was called (including recursive calls).
The default command replaces the wp
prefix, so alternate commands would look like this:
npm run wp-cli transient delete --all
npm run wp-cli user list
To iterate on this project locally, build the image using the same name as the Docker Hub remote. Docker will use the local copy. Specify dev
if you're using using versions.
docker build . --tag ideasonpurpose/wordpress:dev
All shell scripts in bin have been checked with ShellCheck and formatted with shfmt with command: npm run shfmt
While not specific to this project, here are a few useful docker commands for keeping Docker running.
docker-compose down
tears down the containersdocker system prune
Clean up unused containers and imagesdocker system prune -a
Clean everything, will need to download stuff againdocker ps
List running containersdocker exec -it <container> bash
Open a shell on a running container
The docker-entrypoint.sh script in the base WordPress docker image checks for a WordPress installation by checking for index.php and wp-includes/version.php.
This project is actively developed and used in production at Ideas On Purpose.