En resumen
- La verdadera ventaja de Docker para WordPress es la paridad y el onboarding: la misma versión de PHP, extensiones y MySQL para todos, arrancados con un comando en lugar de un día de configuración.
- Las trampas son específicas de WordPress, no de Docker:
DB_HOSTes el nombre del servicio, nolocalhost; WordPress guarda URLs absolutas en la base de datos; y el UID del usuario del contenedor pelea con tus permisos de archivos. - Monta tu código con bind-mount; nunca montes
vendor/,wp/ninode_modules; mantén la base de datos en un volumen con nombre para que sobreviva adocker compose down. - Esto cubre el flujo de desarrollo local. Para el despliegue en producción con git pull en hosting compartido, mira la guía de Bedrock enlazada al final.
El bug de «en mi máquina funciona» que finalmente me empujó a usar Docker con WordPress fue una extensión de PHP. Un plugin necesitaba intl, estaba en mi Mac y faltaba en la de un compañero, y perdimos una tarde con una pantalla en blanco que solo uno de los dos podía reproducir. Esa es la clase de problema que eliminan los contenedores: no por magia, sino convirtiendo el runtime en un archivo del repositorio en lugar de una propiedad del portátil de alguien.
Este artículo es la mitad de desarrollo local de ejecutar WordPress en Docker: la paridad, el flujo de trabajo y las trampas específicas de WordPress que los tutoriales genéricos de Docker se saltan. El despliegue en producción en hosting compartido es otro problema con sus propias aristas, y tiene su propia guía al final.
Qué arregla realmente Docker en el desarrollo de WordPress
WordPress está inusualmente expuesto a la deriva del entorno. Corre sobre una versión de PHP, un conjunto de extensiones, una versión de MySQL y un servidor web, y un plugin puede depender en silencio de cualquiera de ellos. Los contenedores fijan los cuatro en configuración versionada, así que el stack es idéntico para todos y reproducible en CI.
Los beneficios concretos, en orden de cuánto importan en el día a día:
Onboarding
Un desarrollador nuevo ejecuta un comando y tiene el stack completo, en vez de un README lleno de brew install.
Paridad de versiones
Fija PHP y MySQL para que coincidan con producción, para que «funciona en local» signifique algo.
Estado desechable
Rompes la base de datos, borras el volumen, reimportas. Sin miedo a corromper una instalación local.
Probar plugins
Levanta un stack desechable para probar un plugin o una actualización de PHP sin tocar tu instalación real.
Un stack de desarrollo que de verdad se puede usar
La demo de un solo contenedor que todos te muestran sirve para un vistazo de cinco minutos e es inútil para trabajo real:
docker run --name wp -p 80:80 -d wordpress:latestNo tiene base de datos persistente, ni acceso al código de tu tema y plugins, ni una forma cómoda de ejecutar WP-CLI. El desarrollo real necesita un stack multiservicio con tu código montado y la base de datos persistida:
services:
app:
image: wordpress:php8.3-apache
ports:
- "8880:80"
volumes:
- ./wp-content:/var/www/html/wp-content
environment:
WORDPRESS_DB_HOST: db
WORDPRESS_DB_NAME: wordpress
WORDPRESS_DB_USER: wordpress
WORDPRESS_DB_PASSWORD: wordpress
depends_on:
- db
db:
image: mysql:8.0
environment:
MYSQL_DATABASE: wordpress
MYSQL_USER: wordpress
MYSQL_PASSWORD: wordpress
MYSQL_ROOT_PASSWORD: root
volumes:
- db_data:/var/lib/mysql
volumes:
db_data:Dos decisiones ahí importan más de lo que parece. La versión de PHP está fijada en la etiqueta de la imagen para coincidir con producción —la deriva entre un PHP 8.3 local y un 8.1 de producción es justo el bug que Docker debe prevenir, así que no lo dejes flotando en latest. Y la base de datos vive en el volumen con nombre db_data, que sobrevive a docker compose down; el código es un bind-mount para que tus cambios en el editor estén en vivo.
Las trampas específicas de WordPress
Estos son los problemas que no están en un tutorial genérico de Docker porque son problemas de WordPress que resultan aflorar dentro de Docker.
DB_HOST es el nombre del servicio, no localhost
Dentro de la red de Compose, los contenedores se encuentran por nombre de servicio. El host de la base de datos es db, no localhost ni 127.0.0.1. Ponerlo en localhost hace que WordPress intente un socket que no existe en el contenedor de la aplicación, y obtienes «Error establishing a database connection» sin más detalle.
WordPress guarda URLs absolutas en la base de datos. Las opciones siteurl y home, y las URLs incrustadas en el contenido de las entradas, son cadenas completas http://localhost:8880. Importa un volcado de la base de producción en local y cada enlace, imagen y redirección sigue apuntando a producción. Arréglalo con un search-replace seguro para datos serializados, nunca con un UPDATE de SQL crudo que corrompe los datos serializados:
docker compose exec app wp search-replace 'https://example.com' 'http://localhost:8880' --all-tables --skip-columns=guidLa propiedad de los archivos pelea contigo. La imagen de Apache corre como www-data (UID 33). Los archivos que escribe —subidas, CSS generado, instalaciones de plugins— acaban propiedad del UID 33 en tu host, y los archivos que creas como tu usuario del host pueden no ser escribibles por el contenedor. En Linux sobre todo esto aparece como fallos de subida. La solución limpia es alinear el usuario del contenedor con el UID de tu host, o mantener los directorios escribibles (uploads) en un volumen que posee el contenedor mientras tu código sigue siendo un bind-mount simple.
Ejecuta WP-CLI a través del contenedor. WP-CLI necesita el mismo PHP y la misma base de datos que usa el sitio, así que ejecútalo dentro del contenedor de la aplicación, no en tu host:
docker compose exec app wp plugin list
docker compose exec app wp db export backup.sqlQué no montar
Los bind-mounts son cómo tu código llega al contenedor, pero montar los directorios equivocados es la forma más común de ralentizar el stack o romperlo del todo.
Montar vendor/ o wp/
Los directorios gestionados por Composer deben vivir en la imagen, no ser sobrescritos por archivos del host. Montarlos invita a desajustes de versión.
Montar node_modules
Las arquitecturas del host y del contenedor difieren. Compila los assets dentro del contenedor o mantén node_modules fuera del montaje.
Versión de PHP flotante
Usar wordpress:latest en vez de una etiqueta de PHP fijada reintroduce justo la deriva que Docker debe eliminar.
Base de datos en un bind-mount
Los datos de MySQL en un bind-mount del host son lentos y propensos a problemas de permisos. Usa un volumen con nombre.
Commitear las subidas
wp-content/uploads son datos de usuario, no código. Mantenlos fuera de git y fuera de la imagen.
localhost como host de la base de datos
Los contenedores alcanzan la base de datos por nombre de servicio. localhost apunta el contenedor de la aplicación a sí mismo.
Servicios opcionales que vale la pena añadir
Una vez que el stack base funciona, unos pocos servicios extra mejoran notablemente el desarrollo local. Un capturador de correo como Mailpit intercepta el correo saliente para que los restablecimientos de contraseña y las notificaciones caigan en una interfaz web en lugar del vacío. Redis te da una caché de objetos real que coincide con el comportamiento de producción. Un contenedor dedicado de worker de cola te deja ejercitar los jobs como se ejecutan de verdad. Cada uno son unas pocas líneas en el mismo archivo de Compose, versionadas junto al resto.
FAQ
- ¿Por qué WordPress muestra «Error establishing a database connection» en Docker?
- Casi siempre es el host de la base de datos. Dentro de Docker Compose, los servicios se alcanzan por nombre de servicio, así que
DB_HOSTdebe ser el nombre del servicio de base de datos (db), nolocalhost. Una causa secundaria es que el contenedor de la aplicación arranca antes de que MySQL esté listo;depends_onmás un reintento de conexión lo resuelve. - ¿Por qué las URLs de mis imágenes están mal tras importar una base de producción?
- WordPress guarda URLs absolutas en la base de datos. Ejecuta un
wp search-replaceseguro para datos serializados desde el dominio de producción a tu URL local en todas las tablas, omitiendo la columnaguid. Nunca lo hagas con unUPDATEde SQL crudo, que corrompe los datos serializados de las opciones. - ¿Debo montar vendor/ y wp/ como volúmenes?
- No. Los directorios gestionados por Composer pertenecen a la imagen para que las versiones sean consistentes. Monta con bind-mount solo tu propio código (temas, plugins, mu-plugins). Montar directorios gestionados es una fuente común de desajustes de «funciona en CI pero no en local».
- ¿Cómo ejecuto WP-CLI en un sitio WordPress dockerizado?
- Ejecútalo dentro del contenedor de la aplicación con
docker compose exec app wp <command>para que use la misma versión de PHP y conexión a la base de datos que el sitio. Un envoltorio de Makefile alrededor de ese comando ahorra mucho tecleo.
Artículos relacionados
- WordPress Bedrock con Docker en local y PHP puro en producción cubre la mitad del despliegue: releases con git pull a hosting compartido sin Trellis ni un VPS.
- Maximizar el rendimiento con Nginx y Apache cubre la capa del servidor web que puedes comparar limpiamente una vez que el stack es reproducible.




