Clone it, run one command, and you have a full WordPress + MySQL development environment in Docker — ready to build a site on. When you're ready, deploy it to any Docker host, take it onto your own account, and (if you like) keep pulling improvements from upstream.
Modern stack — WordPress 7.0 · PHP 8.4 · MySQL 8.4 LTS — with GitHub Actions CI, a published image on GHCR, and an optional one-push deploy with automatic rollback.
- Quick start
- Features
- Architecture
- The image
- Development commands
- Running the tests
- Deploy to your server
- Releasing
- Migrating an existing site
- Database strategy
- Make it your own
- Need a hand?
- Contributing & security
- License
Prerequisites: Docker Desktop (or Docker Engine + the Compose v2 plugin) and Bash (Git Bash on Windows).
git clone https://github.com/jackwjensen/wordpress-docker-template.git my-site
cd my-site
cp .env.example .env # set COMPOSE_PROJECT_NAME=my-site
./dev.sh up # Windows: dev.bat upOpen http://localhost:8080 and finish the WordPress install wizard. That's it — you're developing.
- WordPress 7.0 + PHP 8.4 + Apache on a thin image built from the official upstream, with tuned PHP upload limits baked in.
- MySQL 8.4 LTS with a real authenticated health check.
- CI on every push/PR — ShellCheck, Hadolint, Compose validation, actionlint, plus a build + smoke test (see the badge above).
- Published image on GHCR, built and released from a version tag (keyless).
- Optional one-push deploy over SSH with automatic rollback if WordPress fails to serve afterwards — dormant until you configure a target.
- WP-CLI available locally for command-line management.
The same two services run locally and in production; a Compose override file swaps the environment-specific bits.
Local (docker-compose.yml) |
Production (+ docker-compose.production.yml) |
|
|---|---|---|
| WordPress | Built from Dockerfile, port 8080:80, debug on |
Same image, no published port, debug off, DISALLOW_FILE_EDIT |
| MySQL | Port 3306 published, dev password |
No published port, password from .env |
| Networking | Default bridge only | Also joins an external reverse-proxy network |
| Routing | Direct to localhost:8080 |
Reverse proxy → <project>-wordpress:80 |
Production merges both files via
COMPOSE_FILE=docker-compose.yml:docker-compose.production.yml in the server's
.env. Each site sets a unique COMPOSE_PROJECT_NAME, which names the containers
(<project>-wordpress, <project>-mysql) so a reverse proxy can route to the
right one.
WordPress is built from a small Dockerfile that extends the
official image and bakes in config/uploads.ini, so the PHP
limits travel with the image. Local dev and production both build it; tagged
releases publish it to GHCR:
docker pull ghcr.io/jackwjensen/wordpress-docker-template:latest
# or pin a version
docker pull ghcr.io/jackwjensen/wordpress-docker-template:1.0.0| Command | Description |
|---|---|
dev.sh up |
Build and start containers |
dev.sh down |
Stop containers |
dev.sh reset |
Destroy volumes and restart fresh |
dev.sh logs |
Follow WordPress logs |
dev.sh cli wp plugin list |
Run WP-CLI commands |
dev.sh backup |
Dump the database to backups/ |
dev.sh restore backups/file.sql |
Restore the database from a dump |
(dev.bat provides the same commands on Windows.)
The same checks CI runs, locally:
shellcheck scripts/*.sh dev.sh tests/*.sh # shell lint
hadolint Dockerfile # Dockerfile lint
docker compose config -q # Compose validation
bash tests/healthcheck.test.sh # fast: no Docker stack needed
bash tests/smoke.sh # builds the image + brings up the stacktests/smoke.sh asserts that WordPress serves HTTP, that the baked-in
upload_max_filesize is active, and that WordPress can reach MySQL.
Deployment is optional and dormant until you configure it — a fresh clone runs locally and passes CI with no deploy setup at all.
The included workflow (deploy.yml) deploys over
SSH to any Docker host on push to master. To activate it:
-
Add two repository secrets:
DEPLOY_HOST— your server's host or IPDEPLOY_SSH_KEY— a private SSH key the server accepts
(Optional variables
DEPLOY_USERandDEPLOY_PATHdefault torootand/opt/apps/<repo>.) -
On the server, run
./scripts/setup-server.sh <site-name> <repo-url>once — it clones the repo, generates the DB password, builds the image, and starts the stack. -
Put a reverse proxy in front for TLS and routing, pointing your domain at the
wordpresscontainer on port 80. -
Push to
master. The workflow rebuilds, health-checks that WordPress actually serves HTTP, and rolls back automatically if it doesn't.
Example setup (what Allegro IT runs in production): a VPS with Nginx Proxy Manager terminating TLS and routing each site's domain to its
<project>-wordpress:80container. Any Docker host plus a reverse proxy works the same way.
Push a SemVer tag to build and publish the image and create a GitHub Release:
git tag v1.0.0
git push origin v1.0.0release.yml authenticates to GHCR with the
built-in GITHUB_TOKEN (no stored secret), publishes
ghcr.io/<owner>/<repo> tagged 1.0.0, 1.0, 1, and latest, attaches a
build-provenance attestation, and drafts release notes.
- Install the Duplicator plugin on
the existing site and create a package (
installer.php+ archive zip). - Set up the server with
scripts/setup-server.shand finish the WordPress install wizard (throwaway values — Duplicator overwrites everything). - Copy the Duplicator files to the server and run
scripts/import-duplicator.sh installer.php <archive.zip>. - Open
https://example.com/installer.phpand complete the wizard:- DB Host:
mysql(NOTlocalhost— containers use Docker DNS) - DB Name:
wordpress· DB User:root - DB Password: from
MYSQL_ROOT_PASSWORDin the server's.env
- DB Host:
- Commit the
wp-content/themes/andwp-content/plugins/changes and push. - Sync the production DB to local:
./scripts/sync-db-from-prod.sh <site-name> example.com(requires theDEPLOY_HOSTenv var; see the script's note about serialized data).
WordPress plugins manage their own schema via dbDelta() — there are no migration
files. Instead:
- Install/uninstall plugins locally, commit the files, push. WordPress converges the schema on activation.
- Pull prod DB to local with
scripts/sync-db-from-prod.sh. - Never push the local DB to production — let WordPress/plugins upgrade their own schema. User content lives in production.
This template is yours to adopt. Two common paths — and they combine well:
Take it onto your own account
- Create your own repo and re-point
origin:git remote set-url origin git@github.com:you/your-site.git git push -u origin master
- Update the bits that name the upstream project:
CODEOWNERS, the badges/links at the top of this README, and the OCI labels in theDockerfile. The release workflow already publishes toghcr.io/<your-account>/<repo>automatically — no edit needed. - Add your own
DEPLOY_HOST/DEPLOY_SSH_KEYsecrets to deploy to your server. - Optionally remove the Need a hand? section.
Stay connected — pull future improvements
Keep this repo as an upstream remote and merge template updates when you want
them (CI, Docker, and tooling improvements — never your site content):
git remote add upstream https://github.com/jackwjensen/wordpress-docker-template.git
git fetch upstream
git merge upstream/master # or: git rebase upstream/masterYour themes, plugins, uploads, and database stay yours; upstream changes touch only the scaffolding (Dockerfile, Compose, CI, scripts, docs).
This template is built and maintained by Allegro IT ApS, a Danish software studio. If you'd rather have us build, migrate, or host your WordPress site — on EU-based, GDPR-friendly infrastructure — we're happy to help: kontakt@allegroit.dk.
- Contributions: see CONTRIBUTING.md and our Code of Conduct.
- Security: please report vulnerabilities privately — see SECURITY.md.
MIT © Allegro IT ApS — fork it, ship it, make it yours.