wordpress-nginx-docker-compose runs a local WordPress behind NGINX
Run WordPress with nginx using Docker Compose.
At a glance
- What is it?
- This project assembles a five-container WordPress development environment with Docker Compose: NGINX in front, WordPress on Bedrock with Composer, MariaDB, phpMyAdmin and Mailpit, plus mkcert for local HTTPS. The README is explicit that it is a local development environment and is not intended to be exposed to the internet or used in production as-is.
- Who is it for?
- Developers who want a reproducible WordPress site on their own machine should use this stack, because Bedrock and Composer give a proper dependency workflow and mkcert makes local HTTPS a one-command job instead of a browser warning. It is the wrong tool for a production site, since the README says so in as many words, and it is the wrong tool if your production environment is managed WordPress hosting.
- Can I use it commercially?
- Not without permission. GitHub finds no licence file in the repository, and without a licence all rights are reserved by default: you may read the code but not reuse it. Check the README, or ask the authors, before using it.
- Is it still maintained?
- Yes. The repository last received commits 5 days ago.
- What is it written in?
- Mainly CSS, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.
Editorial analysis
Five containers, one Compose file
The purpose is stated in one line: use WordPress locally with Docker using Docker Compose. Everything else in the repository serves that goal.
The compose file defines the topology. An nginx service on nginx:1.27-alpine publishes ports 80 and 443 and mounts the nginx directory into the template directory, the source directory into the web root, and a certs directory. A mysql service on mariadb:11.4 holds the database with a lower_case_table_names=2 command and a data volume. A wordpress service is built from the local Dockerfile rather than pulled from a registry. A phpmyadmin service on phpmyadmin:5.2 publishes port 8082 and reads the database host from the environment. A mailpit service catches outgoing mail, with the comment in the compose file noting that Mailpit is the actively maintained successor to the archived MailHog and keeps the same 8025 web and 1025 SMTP ports.
Two conventions run through all of it. Container names are prefixed with the application name, so a second copy of the stack on the same machine does not collide, and every path is relative to the repository, so the whole thing is portable.
The word that matters is local. The README's security section states that this is a local development environment and is not intended to be exposed to the public internet or used in production as-is, and it asks that security issues be reported privately through SECURITY.md rather than in a public issue.
One .env file drives the domain, the app name and the database
The configuration is a single file at the root, and the example is short enough to reproduce in full:
IP=127.0.0.1
APP_NAME=myapp
DOMAIN=myapp.local
DB_HOST=mysql
DB_NAME=myapp
DB_ROOT_PASSWORD=password
DB_TABLE_PREFIX=wp_Everything else derives from it. The nginx service takes DOMAIN from the environment and the compose file sets NGINX_ENVSUBST_TEMPLATE_SUFFIX to .conf, which is the mechanism that lets the official nginx image substitute environment variables into the files it treats as templates. The changelog entry from 2021-01-02 records that change: using the template suffix and better substitution of environment variables in the nginx config.
The other half of the story is the 2020-01-30 entry, which introduced the entrypoint command in Docker Compose to replace the domain name in the nginx config. The README says the point was removing the need to manually edit the domain name in the nginx conf, using the .env value DOMAIN. That is a small change with a large effect on how often you have to touch the configuration.
Because the database name, the root password and the table prefix are also in the same file, the stack has one obvious place to look when a service will not start, and one obvious file to change when you want a second copy of the site.
Bedrock and Composer instead of a downloaded WordPress
The most consequential decision in this stack is how WordPress itself is obtained.
The versions table says WordPress is managed via Composer with Bedrock, the roots/wordpress package, and the contents list describes Bedrock as modern development tools, easier configuration, and an improved secured folder structure for WordPress. In practice that means the WordPress core is a Composer dependency, the project has a composer.json, and the update path for WordPress is a dependency update rather than a file replacement.
The Dockerfile is where that gets installed. It starts from a custom base image, urre/wordpress-nginx-docker-compose-image:1.5.0, and the README describes that base image as a Dockerfile for extending a base image and using a custom Docker image with an automated build on Docker Hub. The base image is multi-arch, linux/amd64 and linux/arm64, so it runs natively on both Intel and Apple Silicon. The 2026-07-24 changelog entry says the PHP 8.1 to 8.3 upgrade came via a rebuilt multi-arch base image, which fixes the ARM emulation issue.
On top of the base the Dockerfile installs sudo, less and mariadb-client, then downloads WP-CLI from the published phar and makes it executable, and creates a cache directory owned by www-data. WP-CLI is listed in the contents as the command line interface for WordPress, and the cache directory is the standard place where WP-CLI keeps its downloaded artefacts.
The result is a WordPress that a developer can update with Composer and inspect with wp-cli, rather than a zip unpacked into a directory.
Mailpit and a sendmail shim keep mail out of your inbox
Email is the classic way a local development environment disturbs the people around you, and this stack addresses it explicitly.
The Mailpit service replaces MailHog, which the changelog records as archived, with Mailpit as the maintained successor on the same 8025 web and 1025 SMTP ports. The web interface is at http://127.0.0.1:8025, so every message the application sends is caught and can be read in a browser instead of being delivered.
Wiring PHP to it takes two small pieces in the Dockerfile. The first is mhsendmail, described in the comment as a tiny SMTP relay that delivers PHP mail() to the mailpit service, fetched from the mhsendmail release page. The second is a generated PHP configuration file that sets the sendmail path:
sendmail_path="/usr/local/bin/mhsendmail --smtp-addr=mailpit:1025 [email protected]"That ini file is dropped into the PHP configuration directory, and the source of the from address is derived from the DOMAIN value, so the mail your application sends looks like it came from your local site.
The Dockerfile also carries a note that matters for the day-to-day loop: use docker compose up -d --force-recreate --build when the Dockerfile has changed. Without the rebuild, the image is not rebuilt, and the change you just made silently does nothing.
Local HTTPS with mkcert and the CLI script
Custom domain and HTTPS support is a listed feature, with an example domain of https://myapp.local, and there is a CLI script to create an SSL certificate using mkcert.
The certificate directory is part of the compose setup: the nginx service mounts ./certs into /etc/certs, so the script writes into the same place the proxy reads from. The CLI script lives in the cli directory at the repository root, and the README links to it in the contents list under the installation section.
That is the whole mechanism. mkcert creates a certificate that the local browser trusts, because mkcert installs its own certificate authority into the system trust store, and the script then wires the resulting files into the directory the nginx container serves from. The changelog shows this has been a supported path since 2020-04-19, when Windows support for creating the SSH certificate, trusting it and setting up the hosts file entry was added.
The nginx side has been hardened in the same spirit. The 2026-07-24 entry records TLS 1.2 and 1.3 only with TLSv1 and TLSv1.1 dropped, modern ciphers, an http2 on directive, and added security headers, and an earlier entry from 2020-05-03 added gzip compression.
So the local stack is HTTPS by default rather than by exception, which is the difference between a development environment that behaves like production and one that hides problems until deploy day.
The July 2026 modernisation and the stale release tags
The changelog's most recent entry, dated 2026-07-24, is a substantial rewrite of the stack, and it is worth reading as a single package.
The Compose version key was dropped, because it is obsolete, and the image tags were pinned. MariaDB moved from 10.7 to 11.4 LTS, nginx to 1.27-alpine and phpMyAdmin to 5.2, and the versions table reflects the same set. The archived MailHog was replaced with the maintained Mailpit on the same ports. Nginx was hardened as described above. CI was modernised with actions/checkout@v4, a PHP 8.1 to 8.3 matrix, Composer caching, composer validate, and runs on pull requests. A SECURITY.md, a CLAUDE.md and an expanded dependabot.yml covering Composer, Docker, GitHub Actions and npm were added, and composer/installers was bumped to v2 to remove a PHP 8.1 deprecation notice.
The version table also lists the rest of the stack: PHP 8.3 via PHP-FPM through the custom base image, MariaDB 11.4 LTS, phpMyAdmin 5.2, Mailpit latest, Composer latest, WP-CLI latest.
Against that, the release tags are old. The only releases are 1.1.0 on 2020-01-11 and 1.2.0 on 2020-01-30, even though the last push was on 2026-10-18. In other words, the code is current and the tags are not, so anyone pinning to 1.2.0 would get a six year old stack with PHP 8.0 and MailHog. Use the default branch, and read the changelog before you do.
Where this stack is the wrong tool
The boundaries are stated, and they are decisive.
First, this is not a production deployment. The README says it plainly: it is a local development environment and is not intended to be exposed to the public internet or used in production as-is. The defaults follow from that, with phpMyAdmin published on 8082, MariaDB on 3306, a root database password of password in the example, and a domain of myapp.local. Those are development conveniences, and the compose file even carries a comment warning that bumping the MariaDB major version can require a fresh ./data/db volume with a dump and import, which is a hint about the cost of treating this as long-lived infrastructure.
Second, there is a documentation gap that the README itself points at. The instructions and help section says to see the new Wiki, and the wiki is where the install steps, the certificate steps and the troubleshooting live. The README is the map, not the manual, and the repository listing shows no docs directory, only a cli directory for the scripts.
Third, the toolchain is opinionated. Bedrock means the WordPress layout follows Roots conventions rather than a plain core checkout, and Bedrock brings its own environment file handling, which is a difference you will feel if you move between a Bedrock site and a classic one. WP-CLI is also assumed present for anything you do from a shell.
Finally, there is no release cadence to rely on. The tags stop in 2020 while the commits continue, so the version of the stack you run is the branch, not a release, and the licence for the repository itself is not stated in the metadata. For a production WordPress site, a managed host or a purpose-built image with a support contract is the better tool; this one is for the local machine.
Editorial conclusion
Developers who want a reproducible WordPress site on their own machine should use this stack, because Bedrock and Composer give a proper dependency workflow and mkcert makes local HTTPS a one-command job instead of a browser warning. It is the wrong tool for a production site, since the README says so in as many words, and it is the wrong tool if your production environment is managed WordPress hosting. Before adopting, copy .env.example to .env and set your own DOMAIN and database values, read the wiki for the install and certificate steps, and note that the GitHub releases stop at 1.2.0 from 2020 while the stack itself was modernised in July 2026.
Frequently asked questions
How do I configure the domain and database for this WordPress stack?
Copy .env.example to .env and set your own values. The file holds IP, APP_NAME, DOMAIN, DB_HOST, DB_NAME, DB_ROOT_PASSWORD and DB_TABLE_PREFIX, and the nginx container substitutes DOMAIN into its configuration templates using NGINX_ENVSUBST_TEMPLATE_SUFFIX.
How does local HTTPS work in this project?
There is a CLI script that creates an SSL certificate using mkcert, and the nginx container mounts ./certs into /etc/certs so the generated files are served directly. The README lists custom domain and HTTPS support, for example with https://myapp.local.
Where do outgoing emails go instead of my inbox?
To Mailpit, the maintained successor to the archived MailHog, on the same 8025 web and 1025 SMTP ports, with the web interface at http://127.0.0.1:8025. The Dockerfile installs mhsendmail as a small SMTP relay and points the PHP sendmail_path at the mailpit service.
Can I use this stack in production?
No. The README states that this is a local development environment and is not intended to be exposed to the public internet or used in production as-is, and it asks that security issues be reported privately via SECURITY.md rather than in a public issue.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/urre-wordpress-nginx-docker-compose)Was this useful?