Español · English
Este proyecto proporciona una configuración de Docker Compose para ejecutar un entorno de desarrollo local de WordPress de forma sencilla y portable.
- Portable: Todo el entorno, incluyendo los archivos de WordPress y la base de datos MariaDB, se almacena dentro de la carpeta del proyecto (
./www/para WordPress,./data/para la base de datos). Esto facilita la creación de copias de seguridad, compartir el entorno o moverlo entre diferentes máquinas. - Aislado: Utiliza Docker para asegurar que el entorno de desarrollo no interfiera con otras configuraciones locales o del sistema.
- Fácil de Usar: Inicia todos los servicios con un simple comando (
docker compose up -d) y detenlos con (docker compose down). - phpMyAdmin Incluido: Accede fácilmente a la base de datos a través de phpMyAdmin para una gestión visual.
- Pre-configurado: Un entorno de WordPress listo para usar con una configuración básica.
Asegúrate de tener instalados:
- Docker
- Docker Compose (generalmente se incluye con Docker Desktop)
docker-compose.yml: Archivo principal de configuración de Docker Compose que define los servicios (WordPress, MariaDB, phpMyAdmin)../data/: Contiene los archivos de la base de datos MariaDB. Esta carpeta es creada por Docker al iniciar los servicios y está ignorada por Git gracias al archivo.gitignore../www/: Contiene los archivos del núcleo de WordPress, temas, plugins y archivos subidos. Esta carpeta es creada por Docker y también está ignorada por Git../apache-config.conf: Archivo de configuración adicional para Apache (utilizado por los contenedores de WordPress y phpMyAdmin, por ejemplo, paraServerName).env.sample: Plantilla comentada de las variables que puedes sobrescribir. Se versiona, y Docker Compose no la lee nunca: cópiala a.envpara usarla..env: Tus propios valores, si creas uno. Docker Compose lo lee automáticamente. Está ignorado por Git, pero sí viaja dentro de la carpeta del proyecto cuando la entregas..gitignore: Especifica los archivos y carpetas que Git debe ignorar (como./data/,./www/y.env).
-
Clonar el Repositorio: La forma más sencilla es clonar el repositorio. Esto creará un nuevo directorio llamado
docker-wordpresscon los archivos del proyecto:git clone https://github.com/raulfg/docker-wordpress.git cd docker-wordpressAlternativa: Clonar en una carpeta específica Si prefieres nombrar la carpeta de tu proyecto de manera diferente o clonar en un directorio existente: a. Crea la carpeta de proyecto deseada (por ejemplo,
mi-proyecto-wordpress) y navega hacia ella:mkdir mi-proyecto-wordpress cd mi-proyecto-wordpressb. Luego, clona el contenido del repositorio directamente en esta carpeta usando un
.al final del comando:git clone https://github.com/raulfg/docker-wordpress.git .Si no vas a clonar, simplemente asegúrate de tener
docker-compose.ymlyapache-config.confen tu directorio de trabajo. -
Iniciar los Servicios: Navega al directorio raíz del proyecto (donde se encuentra el archivo
docker-compose.yml) y ejecuta:docker compose up -d
Esto descargará las imágenes de Docker necesarias (si es la primera vez) y creará e iniciará los contenedores en segundo plano (
-d). Si prefieres ver los logs de los contenedores directamente en tu terminal (modo primer plano), puedes omitir el parámetro-d:docker compose up. Para detener los servicios cuando se ejecutan en modo primer plano, presionaCtrl+Cen la terminal. Ten en cuenta que esto solo detiene los contenedores; para eliminarlos, necesitarás ejecutardocker compose downdespués. -
Acceder a los Servicios:
- WordPress: Abre tu navegador y ve a
http://localhost(ohttp://localhost:80).- La primera vez, serás guiado a través del proceso de instalación de WordPress.
- Los valores de abajo son los predeterminados. Si fijas alguno en un archivo
.env, usa los tuyos — mira Variables que puedes sobrescribir al arrancar.- Nombre de la base de datos:
wordpress - Usuario:
wordpress - Contraseña:
wordpress - Host de la base de datos:
db(el nombre del servicio de la base de datos endocker-compose.yml) - Prefijo de tabla:
dockwp_
- Nombre de la base de datos:
- phpMyAdmin: Abre tu navegador y ve a
http://localhost:8080.- Servidor:
db - Usuario:
root - Contraseña:
password(el valor por defecto; es lo que valgaDB_ROOT_PASSWORD)
- Servidor:
- WordPress: Abre tu navegador y ve a
-
Detener y Eliminar los Servicios: Para detener y eliminar todos los contenedores, redes y volúmenes (no los mapeados localmente en
./data/y./www/) asociados a este proyecto, ejecuta en el mismo directorio:docker compose down
Este comando se utiliza tanto si iniciaste los servicios con
docker compose up -d(modo segundo plano) como si los ejecutaste en modo primer plano y los detuviste conCtrl+C. Asegura una detención y eliminación limpias de los recursos Docker creados por este proyecto. Los datos en./data/(base de datos) y./www/(archivos de WordPress) persistirán en tu sistema local.
Puedes personalizar la configuración editando el archivo docker-compose.yml:
- Versión de WordPress/MariaDB/phpMyAdmin: Cambia las etiquetas de las imágenes (ej.
wordpress:latest,mariadb:11,phpmyadmin).
Los puertos, el prefijo de tablas y las credenciales de la base de datos están parametrizados, así que no necesitas editar docker-compose.yml para cambiarlos. Todos tienen un valor por defecto, y por eso un docker compose up a secas funciona siempre sin ninguna preparación:
| Variable | Por defecto | Qué controla |
|---|---|---|
WP_PORT |
80 |
Puerto donde se publica WordPress |
PMA_PORT |
8080 |
Puerto donde se publica phpMyAdmin |
WP_PREFIX |
dockwp_ |
Prefijo de las tablas de WordPress |
DB_NAME |
wordpress |
Nombre de la base de datos |
DB_USER |
wordpress |
Usuario con el que conecta WordPress |
DB_PASSWORD |
wordpress |
Contraseña de ese usuario |
DB_ROOT_PASSWORD |
password |
Contraseña de root de MariaDB, también para entrar en phpMyAdmin |
Cada credencial se declara una sola vez y la reutilizan todos los servicios que la necesitan, así que db, wordpress y phpmyadmin ya no pueden descuadrarse entre sí. Cambiar la contraseña es una edición, no tres.
En env.sample tienes una plantilla comentada con todas ellas, en inglés y español. Cópiala para partir de ahí:
cp env.sample .envEse archivo se versiona y es inofensivo, porque Docker Compose no lo lee nunca. Compose solo recoge un archivo llamado exactamente .env, así que nada de lo que escribas en env.sample tiene efecto hasta que lo copies. Por eso en .gitignore solo está .env: la plantilla está hecha para compartirse, tus valores reales no.
Para un solo arranque, ponlas delante del comando:
WP_PORT=8081 PMA_PORT=8082 docker compose up -dEs la solución habitual cuando otro proyecto ya está publicando el puerto 80.
Para toda una sesión de terminal, expórtalas una vez:
export WP_PORT=8081
docker compose up -dDe forma permanente, para un proyecto concreto, crea un archivo .env junto a docker-compose.yml. Docker Compose lo lee automáticamente, así que quien use esa carpeta sigue escribiendo solo docker compose up:
# .env
WP_PORT=8081
WP_PREFIX=acme7x_Esta última opción es la adecuada cuando preparas una carpeta para otra persona: fijas los valores una vez, y ella nunca tiene que pasar una variable ni editar un archivo.
Los tres métodos se combinan, y gana el más específico: una variable puesta delante del comando sobrescribe a la misma variable del .env, que a su vez sobrescribe al valor por defecto. Así puedes mantener un .env permanente para un proyecto y arrancarlo puntualmente en otro puerto sin tocar el archivo.
⚠️ WP_PREFIXhay que decidirlo antes de la primera instalación. El prefijo no queda congelado enwp-config.php—ese archivo lee la variable en tiempo de ejecución—, pero las tablas de la base de datos se llaman físicamente<prefijo>posts,<prefijo>options, etc., y el prefijo va además incrustado en claves como<prefijo>user_rolesy<prefijo>capabilities. Cambiarlo en un sitio ya instalado hace que WordPress apunte a tablas que no existen. Cambiarlo después implica renombrar todas las tablas y parchear esas claves a mano.
⚠️ WP_PORTconviene decidirlo también antes de instalar. WordPress guarda su propia dirección en la base de datos durante la instalación, en las opcionessiteurlyhome. Si instalas en el puerto80y más tarde arrancas conWP_PORT=8081, el sitio te seguirá redirigiendo ahttp://localhost. No está roto, solo apunta a la dirección antigua. Elige el puerto antes de instalar, o corrige después esas dos opciones:docker compose exec db mariadb -uwordpress -pwordpress wordpress \ -e "UPDATE dockwp_options SET option_value='http://localhost:8081' WHERE option_name IN ('siteurl','home');"
⚠️ DB_NAME,DB_USER,DB_PASSWORDyDB_ROOT_PASSWORDtambién hay que decidirlos antes del primer arranque. MariaDB solo los lee mientras inicializa un./data/vacío. Una vez que ese directorio existe, la base de datos, el usuario y las contraseñas ya están guardados dentro, y cambiar las variables solo cambia lo que WordPress intenta usar. El resultado es un sitio que respondeError establishing a database connectioncon HTTP 500, mientras la base de datos sigue conservando el usuario original. Para cambiar credenciales en un proyecto en marcha, cámbialas dentro de la base de datos (o desde phpMyAdmin) y mantén las variables sincronizadas, o empieza de nuevo desde un./data/vacío.
⚠️ ¿Vienes de una copia antigua de este proyecto? Las versiones anteriores usabanmysql:5.7. MariaDB no puede leer un directorio de datos creado por MySQL, así que un./data/existente no arrancará. Volca primero la base de datos con el stack antiguo, y luego arranca desde un./data/vacío e impórtala de vuelta:docker compose exec db mysqldump -uroot -ppassword --all-databases > backup.sql
Los valores por defecto son genéricos y fáciles de recordar, y es algo deliberado: este es un entorno local, y unas claves sencillas mantienen trivial la puesta en marcha. Se quedan como defaults dentro de docker-compose.yml para que el entorno arranque siempre sin ninguna preparación.
Si llegas a poner credenciales reales, ponlas en .env, nunca en docker-compose.yml: así se quedan fuera del repositorio, porque .env está en .gitignore mientras el archivo compose sí se versiona. Ten en cuenta que eso las mantiene fuera de Git, no fuera de la carpeta: el .env sigue viajando dentro de la carpeta del proyecto cuando la entregas, que es justo lo que hace funcionar una carpeta ya preparada. No es una caja fuerte de secretos. Si este entorno llega a ser alcanzable desde fuera de tu propia máquina, da por públicas todas sus credenciales y cámbialas.
Este entorno funciona en Windows, y quitar los platform: de docker-compose.yml lo hizo de hecho más portable: ahora Docker elige por su cuenta la arquitectura del host, así que el mismo archivo sirve en un Mac con Apple Silicon y en un Windows x86. Hay dos cosas que conviene saber antes de empezar.
La base de datos vive en ./data/, montada dentro del contenedor. Cuando esa carpeta está en una ruta de Windows como C:\..., la E/S de disco de MariaDB tiene que cruzar del contenedor Linux a NTFS y se convierte en el cuello de botella: instalar WordPress y navegar por el administrador se vuelven notablemente lentos. Está registrado en el issue #1.
La solución no está en el archivo compose. Coloca la carpeta del proyecto dentro del sistema de archivos de la propia distribución WSL2 —accesible como \\wsl$\... desde el Explorador— en lugar de bajo C:\. El mismo docker compose up, con una E/S mucho mejor.
Si necesitas el proyecto en una ruta de Windows y la lentitud es inaceptable, pasar ./data/ a un volumen nombrado de Docker elimina el cuello de botella. El precio es la portabilidad sobre la que está construido este proyecto: la base de datos dejaría de viajar dentro de la carpeta.
.gitattributes fuerza LF al hacer checkout en cualquier plataforma, y no deberías sobrescribirlo. Git for Windows hace checkout en CRLF por defecto, y un retorno de carro final acaba dentro del valor de cada variable del .env: WP_PORT=80\r no es un puerto válido, y el mensaje de error no te dirá por qué.
Este README usa cp, que no existe en PowerShell ni en CMD. Usa copy:
copy env.sample .envEste repositorio es una semilla. Su función es darte el stack más reciente cada vez que empiezas un desarrollo nuevo, y por eso las imágenes usan etiquetas flotantes (wordpress:latest, mariadb:11) en lugar de versiones fijadas.
Una etiqueta es solo un nombre, no una promesa de novedad. Docker la resuelve contra el registry únicamente cuando la imagen no está ya en tu caché local, así que docker compose up por sí solo seguirá sirviendo tranquilamente la imagen que te bajaste hace meses. Esa es la peor de las dos opciones: ni reproducible como una versión fijada, ni actualizada como pretendías.
Por eso, cuando arranques un desarrollo nuevo desde esta semilla, refresca de forma explícita:
docker compose pull && docker compose up -dComprueba lo que realmente obtuviste con docker compose exec wordpress php -v y con la versión de WordPress en el escritorio de administración.
La opción pull_policy: always automatizaría esto, y aquí no se usa de forma deliberada. Convierte cada up en una dependencia dura de red: con el registry inalcanzable, el entorno se niega a arrancar incluso cuando la imagen ya está en la caché local. Como este entorno está pensado para entregarse como una carpeta autocontenida que alguien arranca con un docker compose up a secas, posiblemente sin conexión, no arrancar es muchísimo peor que ejecutar una imagen algo más antigua. La novedad es una decisión explícita que tomas con pull, no un requisito para arrancar.
Un proyecto de cliente quiere lo contrario que una semilla: el mismo entorno que ayer, todos los días, hasta que tú decidas otra cosa. En el momento en que una copia de este repositorio contiene datos reales de un cliente, congela el entorno. Fija las imágenes:
db:
image: mariadb:11.8
wordpress:
image: wordpress:php8.3-apache- No toca el núcleo de WordPress. El núcleo vive en
./www/, y el entrypoint de la imagen solo lo copia cuando el directorio no contiene ningún WordPress. Sobre una instalación existente no se copia nada, no se sobrescribe nada y no se dispara ninguna actualización de base de datos. El sitio se queda en su versión exacta, y el cliente sigue actualizando WordPress desde su panel como siempre. - Sí cambia el runtime de PHP que hay debajo. Los archivos siguen idénticos pero empiezan a ejecutarse sobre otra versión de PHP, que es la forma habitual en la que muere un plugin antiguo. Fija por tanto la versión de PHP (
wordpress:php8.3-apache) y no la de WordPress, que la imagen no controla. - Sí cambia la base de datos. Una actualización de minor de MariaDB migra el directorio de datos de
./data/, y volver a un minor anterior no está soportado. Haz unmysqldumpantes de cambiar la imagen de base de datos.
Un agradecimiento especial a Oscar García (@opobla) por darme a conocer el poder de Docker.
¡Disfruta de tu entorno de desarrollo local de WordPress portable y fácil de gestionar!
Español · English
This project provides a Docker Compose setup to run a WordPress local development environment easily and portably.
- Portable: The entire environment, including WordPress files and the MariaDB database, is stored within the project folder (
./www/for WordPress,./data/for the database). This makes it easy to create backups, share the environment, or move it between different machines. - Isolated: Uses Docker to ensure the development environment does not interfere with other local or system configurations.
- Easy to Use: Start all services with a simple command (
docker compose up -d) and stop them with (docker compose down). - phpMyAdmin Included: Easily access the database through phpMyAdmin for visual management.
- Pre-configured: A ready-to-use WordPress environment with a basic setup.
Ensure you have installed:
- Docker
- Docker Compose (usually included with Docker Desktop)
docker-compose.yml: Main Docker Compose configuration file that defines the services (WordPress, MariaDB, phpMyAdmin)../data/: Contains the MariaDB database files. This folder is created by Docker when starting the services and is ignored by Git thanks to the.gitignorefile../www/: Contains WordPress core files, themes, plugins, and uploaded files. This folder is created by Docker and is also ignored by Git../apache-config.conf: Additional configuration file for Apache (used by WordPress and phpMyAdmin containers, e.g., forServerName).env.sample: Commented template of the variables you can override. It is versioned, and Docker Compose never reads it — copy it to.envto use it..env: Your own values, if you create one. Docker Compose reads it automatically. It is ignored by Git, but it does travel inside the project folder when you hand that folder over..gitignore: Specifies files and folders that Git should ignore (like./data/,./www/and.env).
-
Clone the Repository: The simplest way is to clone the repository. This will create a new directory named
docker-wordpresswith the project files:git clone https://github.com/raulfg/docker-wordpress.git cd docker-wordpressAlternative: Clone into a specific folder If you prefer to name your project folder differently or clone into an existing directory: a. Create your desired project folder (e.g.,
my-wordpress-project) and navigate into it:mkdir my-wordpress-project cd my-wordpress-projectb. Then, clone the repository content directly into this folder using a
.at the end of the command:git clone https://github.com/raulfg/docker-wordpress.git .If you are not cloning, simply ensure you have
docker-compose.ymlandapache-config.confin your working directory. -
Start the Services: Navigate to the project's root directory (where the
docker-compose.ymlfile is located) and run:docker compose up -d
This will download the necessary Docker images (if it's the first time) and create and start the containers in the background (
-d). If you prefer to see the container logs directly in your terminal (foreground mode), you can omit the-dflag:docker compose up. To stop the services when running in foreground mode, pressCtrl+Cin the terminal. Note that this only stops the containers; to remove them, you'll still need to rundocker compose downafterwards. -
Access the Services:
- WordPress: Open your browser and go to
http://localhost(orhttp://localhost:80).- The first time, you will be guided through the WordPress installation process.
- The values below are the defaults. If you set any of them in a
.envfile, use yours instead — see Variables you can override at startup.- Database Name:
wordpress - User:
wordpress - Password:
wordpress - Database Host:
db(the name of the database service indocker-compose.yml) - Table Prefix:
dockwp_
- Database Name:
- phpMyAdmin: Open your browser and go to
http://localhost:8080.- Server:
db - User:
root - Password:
password(the default; it is whateverDB_ROOT_PASSWORDis set to)
- Server:
- WordPress: Open your browser and go to
-
Stop and Remove the Services: To stop and remove all containers, networks, and volumes (not the locally mapped ones in
./data/and./www/) associated with this project, run in the same directory:docker compose down
This command is used whether you started the services with
docker compose up -d(detached mode) or if you ran them in foreground mode and stopped them withCtrl+C. It ensures a clean shutdown and removal of the Docker resources created by this project. The data in./data/(database) and./www/(WordPress files) will persist on your local system.
You can customize the configuration by editing the docker-compose.yml file:
- WordPress/MariaDB/phpMyAdmin Versions: Change the image tags (e.g.,
wordpress:latest,mariadb:11,phpmyadmin).
The ports, the table prefix and the database credentials are all parameterized, so you do not have to edit docker-compose.yml to change them. Every one of them falls back to a default, which is why a bare docker compose up always works with no setup at all:
| Variable | Default | What it controls |
|---|---|---|
WP_PORT |
80 |
Port where WordPress is published |
PMA_PORT |
8080 |
Port where phpMyAdmin is published |
WP_PREFIX |
dockwp_ |
WordPress database table prefix |
DB_NAME |
wordpress |
Database name |
DB_USER |
wordpress |
Database user WordPress connects with |
DB_PASSWORD |
wordpress |
Password for that user |
DB_ROOT_PASSWORD |
password |
MariaDB root password, also used to log into phpMyAdmin |
Each credential is declared once and reused by every service that needs it, so the db, wordpress and phpmyadmin services can no longer drift apart. Changing the password is one edit, not three.
A commented template listing all of them lives in env.sample, in English and Spanish. Copy it to start from it:
cp env.sample .envThat file is versioned and harmless, because Docker Compose never reads it. Compose only picks up a file named exactly .env, so nothing you write in env.sample has any effect until you copy it. That is also why only .env is listed in .gitignore: the template is meant to be shared, your actual values are not.
For a single run, put them in front of the command:
WP_PORT=8081 PMA_PORT=8082 docker compose up -dThis is the usual fix when another project is already publishing port 80.
For a whole terminal session, export them once:
export WP_PORT=8081
docker compose up -dPermanently, for one project, create a .env file next to docker-compose.yml. Docker Compose reads it automatically, so whoever uses that folder keeps typing just docker compose up:
# .env
WP_PORT=8081
WP_PREFIX=acme7x_That last option is the right one when you are preparing a folder for someone else: you set the values once, and they never have to pass a variable or edit a file.
The three methods combine, and the more specific one wins: a variable placed in front of the command overrides the same variable in .env, which in turn overrides the default. So you can keep a permanent .env for a project and still start it on a different port just once, without touching the file.
⚠️ WP_PREFIXhas to be decided before the first installation. The prefix is not frozen intowp-config.php— that file reads the variable at runtime — but the database tables are physically named<prefix>posts,<prefix>optionsand so on, and the prefix is also embedded in keys such as<prefix>user_rolesand<prefix>capabilities. Changing it on a site that is already installed points WordPress at tables that do not exist. Changing it afterwards means renaming every table and patching those keys by hand.
⚠️ WP_PORTis best decided before installing too. WordPress stores its own address in the database during installation, in thesiteurlandhomeoptions. Install on port80and later start withWP_PORT=8081, and the site will keep redirecting you back tohttp://localhost. It is not broken, just pointing at the old address. Either pick the port before installing, or fix the two options afterwards:docker compose exec db mariadb -uwordpress -pwordpress wordpress \ -e "UPDATE dockwp_options SET option_value='http://localhost:8081' WHERE option_name IN ('siteurl','home');"
⚠️ DB_NAME,DB_USER,DB_PASSWORDandDB_ROOT_PASSWORDalso have to be decided before the first start. MariaDB only reads them while it initializes an empty./data/. Once that directory exists, the database, the user and the passwords are already stored inside it, and changing the variables only changes what WordPress tries to use. The result is a site that answersError establishing a database connectionwith HTTP 500, while the database still holds the original user. To change credentials on a running project, change them inside the database (or in phpMyAdmin) and keep the variables in sync, or start over from an empty./data/.
⚠️ Coming from an older copy of this project? Earlier versions usedmysql:5.7. MariaDB cannot read a data directory created by MySQL, so an existing./data/will not start. Dump the database with the old stack first, then start from an empty./data/and import it back:docker compose exec db mysqldump -uroot -ppassword --all-databases > backup.sql
The defaults are generic, memorable values, and that is deliberate: this is a local environment, and simple keys keep the startup trivial. They stay as defaults inside docker-compose.yml so that the environment always boots with no setup.
If you do set real credentials, put them in .env, never in docker-compose.yml — that way they stay out of the repository, because .env is listed in .gitignore while the compose file is versioned. Note that this keeps them out of Git, not out of the folder: .env still travels inside the project folder when you hand it over, which is exactly what makes a prepared folder work. It is not a secrets vault. If this environment is ever reachable from outside your own machine, treat every credential in it as public and change it.
This environment runs on Windows, and dropping the platform: overrides from docker-compose.yml actually made it more portable: Docker now picks the host architecture on its own, so the same file works on an Apple Silicon Mac and on an x86 Windows machine. Two things are worth knowing before you start.
The database lives in ./data/, bind-mounted into the container. When that folder sits on a Windows path such as C:\..., disk I/O for MariaDB has to cross from the Linux container to NTFS and becomes the bottleneck: installing WordPress and browsing the admin get noticeably slow. This is tracked in issue #1.
The fix is not in the compose file. Put the project folder inside the WSL2 distribution's own filesystem — reachable as \\wsl$\... from Explorer — instead of under C:\. Same docker compose up, much better I/O.
If you need the project on a Windows path and the slowness is unacceptable, moving ./data/ to a Docker named volume removes the bottleneck. The cost is the portability this project is built around: the database would no longer travel inside the folder.
.gitattributes forces LF on checkout on every platform, and you should not override it. Git for Windows checks out CRLF by default, and a trailing carriage return ends up inside the value of every variable in .env: WP_PORT=80\r is not a valid port, and the error message will not tell you why.
This README uses cp, which does not exist in PowerShell or CMD. Use copy instead:
copy env.sample .envThis repository is a seed. Its job is to hand you the newest stack every time you start a new development, which is why the images use floating tags (wordpress:latest, mariadb:11) instead of pinned versions.
A tag is only a name, not a promise of freshness. Docker resolves a tag against the registry only when the image is not already in your local cache, so docker compose up on its own will happily keep serving the image you pulled months ago. That is the worst of both worlds: neither reproducible like a pinned version nor current like you intended.
So when you start a new development from this seed, refresh explicitly:
docker compose pull && docker compose up -dCheck what you actually got with docker compose exec wordpress php -v and the WordPress version in the admin dashboard.
The pull_policy: always option would automate this, and it is deliberately not used here. It turns every single up into a hard network dependency: with an unreachable registry the stack refuses to start even when the image is already in the local cache. Since this environment is meant to be handed over as a self-contained folder that someone starts with a bare docker compose up, possibly offline, failing to boot is far worse than running a slightly older image. Freshness is an explicit decision you make with pull, not a precondition for booting.
A client project wants the opposite of a seed: the same environment as yesterday, every day, until you decide otherwise. The moment a copy of this repository holds real client data, freeze the environment. Pin the images:
db:
image: mariadb:11.8
wordpress:
image: wordpress:php8.3-apache- It does not touch the WordPress core. The core lives in
./www/, and the image entrypoint copies it only when the directory contains no WordPress at all. On an existing installation nothing is copied, nothing is overwritten, and no database upgrade is triggered. The site stays on its exact version, and the client keeps updating WordPress from the admin panel as always. - It does change the PHP runtime underneath. The files stay identical but start running on a different PHP version, which is the usual way a legacy plugin dies. So pin for the PHP version (
wordpress:php8.3-apache) rather than for the WordPress version, which the image does not control anyway. - It does change the database. A MariaDB minor upgrade migrates the data directory in
./data/, and returning to an earlier minor is not supported. Take amysqldumpbefore changing the database image.
Special thanks to Oscar García (@opobla) for introducing me to the power of Docker.
Enjoy your portable and easy-to-manage local WordPress development environment!