Bash script to quickly and easily create local WordPress sites.
mkwp is a single file. Download it from the latest release into a directory on your PATH, such as ~/bin, and make it executable:
mkdir -p ~/bin
curl -fsSL https://github.com/Kntnt/mkwp/releases/latest/download/mkwp -o ~/bin/mkwp
chmod +x ~/bin/mkwpTo update mkwp, run the same commands again. To install a particular version, put download/v<version> in place of latest/download in the link, as in https://github.com/Kntnt/mkwp/releases/download/v1.9.0/mkwp. The first line of mkwp --help shows the installed version.
Install the file from a release, not from a clone of the repository. The version is written into the file when a release is built, so a copy of mkwp taken straight from the repository has none and calls itself unreleased. To build the file from a clone, run bin/build-release-assets: it writes release-assets/mkwp, with the version taken from git.
mkwp needs a running Docker provider and DDEV on your PATH. To build a site from a blueprint, it also needs jq, which current macOS comes with.
NAME
mkwp <version> - make wordpress
SYNOPSIS
mkwp [OPTION]... NAME
mkwp --destroy NAME
DESCRIPTION
Makes a local WordPress site with the domain name.ddev.site where
name is the domain name provided as the argument [NAME].
The site can be described by a blueprint, a JSON file given with
-B or --blueprint. See BLUEPRINT below.
Your personal settings, and the blueprint to use when none is given
with -B, can be kept in a configuration file. See CONFIGURATION
below.
An option given on the command line always takes precedence. If a
site setting (title, language, PHP or WordPress version, Redis or
database server) is not given there, it is in the first place set to
the value in the blueprint, if there is one and it has the setting,
and in the second place to a default value. If a personal setting
(email, user, password or directory) is not given there, it is in
the first place set to the value in the configuration file specified
with -c or --config, in the second place to the value in the
configuration file ~/.mkwp, and in the third place to a default
value.
OPTIONS
-t <title>
--title=<title>
The title of the WordPress site. If omitted, [NAME] will be
used.
-d <path>
--directory=<path>
The home directory of the website. If omitted, current
directory will be used as home directory.
-D <dirname>
--dirname=<dirname>
The name of the directory the site is created in, underneath the
home directory. If omitted, [NAME] will be used.
-m <email>
--email=<email>
The email address of the first user created in WordPress. If
omitted, the current username is used as local-part and the
hostname as domain.
-u <username>
--user=<username>
The username of the first user created in WordPress. If
omitted, the local-part of the email address will be used.
-p <password>
--password=<password>
The password of the first user created in WordPress. Must at
least be 12 characters long. If omitted, a 16 character random
password is generated.
-l <locale>
--language=<locale>
The language of the WordPress site. If omitted, en_US will be
used. For a number of well-known locales, the site timezone is
set accordingly (e.g. Europe/Stockholm for sv_SE).
-T <themes>
--themes=<themes>
Comma separated list of themes to install. For a theme hosted
on WordPress.org, enter its slug or URL to its zip-file. For a
theme hosted in a remote Git repository, enter its remote URL.
For a theme hosted locally, enter the path to the zip file. The
last listed theme is activated. If omitted, WordPress's current
default theme will be used.
-P <plugins>
--plugins=<plugins>
Comma separated list of plugins to install. For a plugin hosted
on WordPress.org, enter its slug or URL to its zip-file. For a
plugin hosted in a remote Git repository, enter its remote URL.
For a plugin hosted locally, enter the path to the zip file.
-M <plugins>
--mu-plugins=<plugins>
Comma separated list of mu-plugins to install. For each plugin,
provide the plugin's URL from where it can be downloaded as
php-file (e.g. https://raw.githubusercontent.com/Kntnt/kntnt-
adminbar-sanitizer/master/kntnt-adminbar-sanitizer.php).
If omitted, no mu-plugins will be installed.
-V <version>
--php=<version>
Version of PHP to be used. If omitted, PHP 8.5 will be used.
-W <version>
--wp=<version>
Version of WordPress to install, given as <major>,
<major>.<minor> or <major>.<minor>.<patch> (e.g. 6, 6.9 or
6.9.4). Omitted parts are resolved to the highest available
release. If omitted entirely, the latest version is installed.
The core auto-update policy reflects the precision: a full
<major>.<minor>.<patch> disables auto-updates, while <major> or
<major>.<minor> enables patch-level updates only.
-R
--redis
Adds a Redis service to the site and enables it as WordPress
object cache backend: the Redis Object Cache plugin is
installed and activated, and its object-cache.php drop-in is
enabled.
--db-server=<type>:<version>
The database server of the site, given as mariadb:<version> or
mysql:<version> (e.g. mariadb:11.8), with a version that DDEV
has. If omitted, DDEV's default server is used, or, if the site
is started from a snapshot, the server the snapshot was taken
from, which is the only one it can be restored into.
-B <file>
--blueprint=<file>
Builds the site that the blueprint <file> describes, instead
of the one a configuration file names, if any. See BLUEPRINT
below.
--no-blueprint
Builds the site without a blueprint, even if a configuration
file names one. Can't be given together with -B or
--blueprint.
--destroy
Destroys the site [NAME]: deletes the DDEV project, including
its database, and removes the site directory from the file
system. Asks for confirmation before doing so. All other
options are ignored.
-c <file>
--config=<file>
Reads personal settings, and the blueprint to use, from the
configuration file <file>, or from standard input if <file>
is -. See CONFIGURATION below.
-h
--help
Display this help and exit.
CONFIGURATION
mkwp reads the configuration file given with -c or --config, if
any, and the configuration file ~/.mkwp. If ~/.mkwp is missing, an
empty one is created.
A configuration file holds your personal settings, and may name a
blueprint. Each line is a key, an equal sign and a value, with
optional white space around the equal sign. Blank lines and lines
starting with # are ignored. Any other line stops mkwp. Example:
# Personal settings
email = jane@example.com
user = jane
directory = /Users/jane/Sites
blueprint = ~/blueprints/default.json
The keys are:
email
user
password
directory
The same as the options with the same names.
blueprint
The blueprint to build the site from when none is given with
-B or --blueprint. A path starting with ~/ is in your home
directory, and a relative path is relative to the directory of
the configuration file, or to the current directory if the
configuration is read from standard input. A blueprint named
in the file given with -c is used instead of one named in
~/.mkwp. Give --no-blueprint to build a site without it.
Before mkwp 2.0.0, a configuration file could also hold the site
settings name, dirname, title, language, themes, plugins,
mu-plugins, php, wp and redis. Now they stop mkwp. Give name
and dirname on the command line, and the others in a blueprint
or as options.
BLUEPRINT
A blueprint is a JSON file that describes a site. Keep it, and the
files it refers to, in a directory of their own, which can be a Git
repository and cloned anywhere. mkwp checks the whole blueprint, and
every file it refers to, before it makes anything. Example:
{
"$schema": "https://raw.githubusercontent.com/Kntnt/mkwp/main/blueprint.schema.json",
"note": "Our starting point for client sites",
"site": { "title": "Client Site", "language": "sv_SE", "php": "8.4", "redis": true },
"themes": [
"twentytwentyfive",
{ "source": "./themes/client-child", "activate": true }
],
"plugins": [
{ "source": "query-monitor", "version": "3.19.0", "activate": true },
{ "source": "git@github.com:example/private-plugin.git", "ref": "v2.1.0", "activate": true },
{ "source": "./plugins/commercial-plugin.zip", "note": "Licensed to us" }
],
"mu_plugins": [
"./mu-plugins/client-tweaks.php",
"https://raw.githubusercontent.com/Kntnt/kntnt-adminbar-sanitizer/master/kntnt-adminbar-sanitizer.php"
]
}
A blueprint is an object with these keys, all of them optional:
$schema
The JSON Schema of the blueprint, which lets an editor check
and complete it. mkwp ignores it.
note
A note of your own, since JSON has no comments. It may also be
given in each item of themes, plugins, mu_plugins and
files. mkwp ignores it.
site
An object with the settings of the site: title, language, php,
wp and db_server, which are strings, and redis, which is true
or false. Each is checked like the option with the same name,
and is replaced by the option when it's given on the command
line.
themes
plugins
Arrays of the themes and plugins to install. An item is a
source, as described below, or an object with the source in
source, and with version, ref, activate (true or false) and
note. version is the version to install, and only a slug on
WordPress.org may have it. ref is the tag, branch or commit to
check out, and only a Git repository may have it. Neither may
hold white space or start with a hyphen, which Git would take
for an option. Each item with "activate": true is activated,
and at most one theme may have it. If no theme has it, the
last theme is activated, unless there is a database. Themes
and plugins given with -T and -P are installed after the
blueprint's, and the last theme given with -T is activated
instead of the blueprint's.
mu_plugins
An array of the mu-plugins to install. An item is a source, as
described below, or an object with the source in source, and
with note.
files
An array of the files and directories to copy into wp-content,
such as the media that a database refers to. An item is an
object with a local file or directory in source, where to
copy it in target, and note. A relative source is relative
to the blueprint's directory. The target is relative to
wp-content. It must not be absolute or start with ~, and its ..
parts must not lead out of wp-content. Missing directories in
it are created.
As with cp, the target of a file is the path of the copy, so
the copy can get a new name, as in "uploads/2024/01/logo.png",
and replaces a file with the same path. A target that ends
with /, or is a directory that already exists, is the
directory to copy the file into under its own name. The
target of a directory is the directory to merge its contents
into. Example:
"files": [
{ "source": "./media", "target": "uploads" },
{ "source": "./fonts/brand.woff2", "target": "fonts/" },
{ "source": "./logo.png", "target": "uploads/2024/01/client-logo.png" }
]
database
The path to a database to start the site from: a dump of
another WordPress site or a DDEV snapshot of one. Its tables
must have the prefix wp_. A relative path is relative to the
blueprint's directory.
mkwp tells a dump from a snapshot by its name. A dump is plain
SQL in a .sql file or gzip-compressed SQL in a .sql.gz file. A
snapshot is a file that ddev snapshot made, in any directory,
with the name DDEV gave it, which ends with the server it was
taken from, as in site-mariadb_11.8.zst. The site gets that
server, since a snapshot can only be restored into it, so a
db_server in site, or one given with --db-server, must be the
same.
The site keeps the users, settings and active theme and
plugins of the database, but for what is given explicitly:
the themes and plugins with "activate": true and the theme
given with -T are activated, and a title and a language given
replace those of the database. Without a language given, the
language pack of the database's language is installed, so
that the site shows in it. What mkwp gives a new site by
default, such as a title made from NAME, pretty permalinks
and the timezone of the language, is not applied. The user
is made an administrator with the email address and
password given, and is created if the database doesn't have
it. If the email address belongs to another user in the
database, mkwp stops, since WordPress can't give two users
the same email address. A dump or a snapshot can hold
secrets, such as licence keys and API credentials, which
mkwp doesn't remove.
Before mkwp 2.1.0, database was an object with the path in
dump or snapshot, and the server in server. It still works,
with a warning, but mkwp 3.0.0 won't accept it. Give the path
as database instead, and the server as db_server in site.
wp_cli
An array of WP-CLI commands, which are run in the given order at
the end of the build. A command is an array of the arguments to
wp, without wp itself, such as
["option", "update", "blogdescription", "Just another site"].
Each argument reaches WP-CLI exactly as written, since no shell
is involved, so nothing needs quoting. A command gets no input,
so give --yes to a command that asks for confirmation. If a
command fails, mkwp stops without opening the site.
The source of a theme or plugin is one of these, recognized in this
order:
A local path, which starts with ./, ../, / or ~/, to a
directory or a zip file. A relative path is relative to the
blueprint's directory. A directory is copied into the site
under its own name. A zip file is installed, and the theme or
plugin is named after the directory in the zip file, so the
file's own name doesn't matter.
A Git repository, whose URL ends with .git. Any URL that Git
accepts works, including SSH, so a private repository is
reached with your own SSH setup. The tag, branch or commit in
ref is checked out, or else the latest tag, or the default
branch if there is no tag.
A URL, which starts with http:// or https://, to a zip file,
which is named after the directory in it, as a local one is.
A slug on WordPress.org, which is anything else. The version
in version is installed, or else the latest one. A slug has
only lower-case letters, digits, - and _, so a local path
written without ./ in front is reported before anything is
made.
The source of an mu-plugin is a PHP file, given by a local path,
which starts with ./, ../, / or ~/ and is relative to the
blueprint's directory if it's relative, or by a URL, which starts
with http:// or https://.
A site is built in this order:
- DDEV is configured, with the cron add-on and, if Redis is
used, the Redis add-on, and started.
- WordPress is downloaded and installed.
- The themes, plugins and mu-plugins are installed.
- The files are copied into wp-content.
- The dump is imported, or the snapshot restored, if there
is one. The old address is replaced by the site's, also
where it's JSON-escaped, the database is upgraded, the
user is made an administrator, the title and language
given are applied, or else the language pack of the
database's language is installed, and the rewrite rules
and the object cache are flushed.
- The theme to activate, and the plugins with
"activate": true, are activated.
- Redis is enabled as object cache, if it is used.
- The WP-CLI commands in wp_cli are run, in order.
- The site is opened in the browser.
DEPENDENCY
This command requires that a Docker provider is installed and running
on your system and that DDEV is properly installed and in your path.
A blueprint also requires jq.
A blueprint is a JSON file that describes a site, and mkwp --blueprint=<file> NAME builds that site. The format, the rules for sources and the order in which a site is built are described under BLUEPRINT in the usage above, which also has an example.
A relative path in a blueprint is relative to the blueprint's own directory, so a blueprint and the zip files, directories, PHP files and other files it refers to can be kept together in a directory of their own. That directory can be a Git repository, private if it holds commercial plugins or code of your own, and can be cloned anywhere.
To build every site from a blueprint of yours unless you say otherwise, name it in ~/.mkwp, as in blueprint = ~/blueprints/default.json. A blueprint named in a configuration file given with -c, such as one for a client, is used instead, and a blueprint given with --blueprint is used before either. --no-blueprint builds a plain site. The configuration files hold only your personal settings and that blueprint; everything that describes the site belongs in the blueprint. See CONFIGURATION in the usage above.
A blueprint's database is the path to a dump or a DDEV snapshot of another site, as in "database": "./site.sql.gz", and starts the site with all the settings and content that site has. Only a database whose tables have the prefix wp_ can be used. A snapshot is a file that ddev snapshot writes into a site's .ddev/db_snapshots. Copy it next to the blueprint, or anywhere else, but keep the name DDEV gave it: mkwp tells a snapshot from a dump by its name, and DDEV reads the server the snapshot was taken from in it, as in site-mariadb_11.8.zst, and restores it only into that server. mkwp gives the site that server, so a blueprint needs no server for a snapshot, and a db_server in its site, or a server given with --db-server, must be the same.
A dump or a snapshot can contain secrets. Plugins keep licence keys, API credentials and similar secrets in the database, and a dump or a snapshot holds them as they are. A snapshot holds even more: every database on the server it was taken from. mkwp copies a dump or a snapshot into the new site without sanitizing it. Keep dumps and snapshots only where those secrets may be, such as a private repository, and remove what doesn't belong there before you add one to a blueprint.
blueprint.schema.json in this repository is a JSON Schema for blueprints. Name it in a blueprint's $schema key, as the example does, and an editor such as VS Code checks the blueprint and completes its keys as you write. The schema on main follows the latest mkwp. For the schema of a particular version, put its tag in place of main, as in https://raw.githubusercontent.com/Kntnt/mkwp/v2.0.0/blueprint.schema.json.
mkwp has two test suites. Both run mkwp the way a user does and look only at what a user sees: the exit status, the message, and the site that was built. Neither reads or changes your ~/.mkwp.
The fast suite checks that mkwp rejects what it must reject before it touches DDEV. It needs neither Docker nor DDEV, and takes a few seconds:
tests/run-fastThe end-to-end suite builds real sites and checks them through ddev wp. It needs a running Docker provider, DDEV on your PATH and access to the internet, and takes a few minutes. Run it before a release:
tests/run-e2eEach site it builds is named mkwp-e2e- followed by a random part, is created in a temporary directory, and is destroyed with mkwp --destroy when its test file is done, even if a test fails or you interrupt the run. It touches no other DDEV project.
To run only some of the tests, name their files, as in tests/run-fast tests/fast/options.bash. A test is a function whose name starts with test_, in a file under tests/fast or tests/e2e. The helpers the tests use are in tests/lib.
/release tags the release, and bin/build-release-assets builds the mkwp it attaches: the script as committed at the tag, with the tag's version written into it. The repository keeps no version number anywhere else; the tag and CHANGELOG.md carry it.