Contenido
Tutorial de WordPress con Docker Compose: crea un sitio con MySQL
En esta sección crearás un sitio completo de WordPress con Docker Compose. Al terminar tendrás:
- WordPress (PHP + Apache, imagen oficial
wordpress:7-apache) - Una base de datos MySQL 8.4 con comprobación de estado; WordPress solo se inicia cuando la base de datos está lista
- Los datos guardados en un Volume, así que no se pierden aunque elimines y vuelvas a crear los contenedores
- El tamaño máximo de subida de archivos ajustado a 64 MB
- Una interfaz de administración phpMyAdmin que solo se inicia cuando la necesitas
La sintaxis de Compose utilizada en esta sección (depends_on, healthcheck, profiles, .env) ya se presentó en Introducción a Compose y comando docker compose.
Crear el proyecto
Crea una carpeta wordpress y añade estos tres archivos:
wordpress/
├── compose.yaml
├── .env
└── uploads.ini
.env: contraseñas
MYSQL_ROOT_PASSWORD=change-this-root-password
MYSQL_PASSWORD=change-this-wp-password
Cámbialas por tus propias contraseñas. Compose lee automáticamente el archivo .env de la misma carpeta y sustituye variables como ${MYSQL_PASSWORD} en compose.yaml por sus valores. No subas .env a Git.
uploads.ini: tamaño de subida
El límite de subida PHP predeterminado de la imagen de WordPress es de solo 2 MB, por lo que fallará la subida de imágenes algo grandes o temas. Sobrescríbelo con un archivo ini:
upload_max_filesize = 64M
post_max_size = 64M
memory_limit = 256M
La imagen oficial de PHP lee todos los archivos .ini de /usr/local/etc/php/conf.d/, así que basta con montar este archivo ahí.
compose.yaml
services:
db:
image: mysql:8.4
environment:
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
MYSQL_DATABASE: wordpress
MYSQL_USER: wordpress
MYSQL_PASSWORD: ${MYSQL_PASSWORD}
volumes:
- db_data:/var/lib/mysql
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "127.0.0.1", "-uroot", "-p${MYSQL_ROOT_PASSWORD}"]
interval: 10s
timeout: 5s
retries: 10
start_period: 30s
restart: unless-stopped
wordpress:
image: wordpress:7-apache
depends_on:
db:
condition: service_healthy
ports:
- "8080:80"
environment:
WORDPRESS_DB_HOST: db
WORDPRESS_DB_NAME: wordpress
WORDPRESS_DB_USER: wordpress
WORDPRESS_DB_PASSWORD: ${MYSQL_PASSWORD}
volumes:
- wp_data:/var/www/html
- ./uploads.ini:/usr/local/etc/php/conf.d/uploads.ini:ro
restart: unless-stopped
phpmyadmin:
image: phpmyadmin
depends_on:
- db
ports:
- "127.0.0.1:8081:80"
environment:
PMA_HOST: db
profiles: [tools]
volumes:
db_data:
wp_data:
Explicación de los puntos clave:
| Configuración | Función |
|---|---|
MYSQL_DATABASE / MYSQL_USER / MYSQL_PASSWORD | La imagen de MySQL crea automáticamente la base de datos y la cuenta la primera vez que se inicia (cuando la carpeta está vacía) |
healthcheck | Cada 10 segundos, mysqladmin ping comprueba si es posible conectarse a MySQL. Debes usar -h 127.0.0.1 en lugar de localhost; de lo contrario, se marcará como healthy antes de que termine la inicialización. Consulta MySQL para conocer el motivo |
condition: service_healthy | Espera a que db esté healthy para iniciar WordPress y evitar que arranque antes de poder conectarse a la base de datos |
WORDPRESS_DB_HOST: db | Conecta mediante el nombre del servicio db; la red de Compose lo resuelve automáticamente a la IP del contenedor MySQL |
db_data:/var/lib/mysql | Los datos de MySQL se guardan en un Volume |
wp_data:/var/www/html | El programa de WordPress, los plugins, los temas y los archivos multimedia subidos se guardan en un Volume |
profiles: [tools] | phpMyAdmin no se inicia de forma predeterminada; añade --profile tools para iniciarlo |
127.0.0.1:8081:80 | phpMyAdmin solo se enlaza a la máquina local; no es accesible desde el exterior |
WordPress accede a MySQL únicamente a través de la red interna de Compose, por eso db no tiene ports configurado y no queda expuesto al exterior.
Inicio
Ejecuta este comando en la carpeta wordpress:
$ docker compose up -d
[+] up 32/32
✔ Image wordpress:7-apache Pulled 28.0s
✔ Network wordpress_default Created 0.1s
✔ Volume wordpress_db_data Created 0.0s
✔ Volume wordpress_wp_data Created 0.0s
✔ Container wordpress-db-1 Healthy 11.1s
✔ Container wordpress-wordpress-1 Started 11.3s
Fíjate en el orden: Compose crea primero la red y los dos volúmenes. Después de que wordpress-db-1 espera 11 segundos y pasa a Healthy, wordpress-wordpress-1 aparece como Started. Ese es el efecto de condition: service_healthy. La primera vez se deben descargar las imágenes, así que tardará más.
Comprueba el estado:
$ docker compose ps
NAME IMAGE COMMAND SERVICE CREATED STATUS PORTS
wordpress-db-1 mysql:8.4 "docker-entrypoint.s…" db 16 seconds ago Up 16 seconds (healthy) 3306/tcp, 33060/tcp
wordpress-wordpress-1 wordpress:7-apache "docker-entrypoint.s…" wordpress 16 seconds ago Up 5 seconds 0.0.0.0:8080->80/tcp, [::]:8080->80/tcp
En STATUS, db aparece como (healthy) y PORTS muestra solo 3306/tcp, el puerto interno del contenedor, sin asignarlo al host. En wordpress, el puerto 8080 del host está asignado al puerto 80 del contenedor.
También se han creado los dos volúmenes. Sus nombres siguen el formato «nombre_del_proyecto_nombre_del_volumen». De forma predeterminada, el nombre del proyecto es el de la carpeta:
$ docker volume ls
DRIVER VOLUME NAME
local wordpress_db_data
local wordpress_wp_data
Completa la instalación en el navegador
Abre http://localhost:8080. En la primera pantalla, elige el idioma. Selecciona en la lista el idioma que quieras para el panel de administración (por ejemplo, «Español») y pulsa Continue. Las capturas de este tutorial usan English (United States) para que coincidan con las de las otras versiones lingüísticas.
A continuación, completa la información del sitio:

| Campo | Descripción |
|---|---|
| Site Title | Título del sitio. Puedes cambiarlo más adelante desde el panel de administración. |
| Username | Cuenta de administrador. No uses admin, porque es fácil de adivinar (en este ejemplo solo se muestra a modo ilustrativo). |
| Password | Contraseña del administrador. WordPress muestra su nivel de seguridad. |
| Your Email | Correo electrónico del administrador. Se usa para recibir notificaciones y restablecer la contraseña. |
| Search engine visibility | Si marcas esta casilla, se pedirá a los motores de búsqueda que no indexen el sitio. Puedes marcarla mientras el sitio esté en pruebas. |
Aquí no aparece una pantalla para configurar la base de datos, porque las variables de entorno WORDPRESS_DB_* ya proporcionaron los datos de conexión y, al iniciarse, la imagen generó automáticamente wp-config.php.
Después de pulsar Install WordPress, aparecerá una pantalla con el mensaje Success para indicar que la instalación ha terminado:


http://localhost:8080/wp-login.phpInicia sesión con el nombre de usuario y la contraseña que acabas de configurar para acceder al panel de administración. La versión que aparece en el área de bienvenida es la 7.1.3, la versión incluida en ese momento en la imagen wordpress:7-apache:

En la página pública, http://localhost:8080, ya puedes ver la entrada predeterminada Hello world!:

Comprueba el límite de tamaño de las cargas
En el panel de administración, ve a Media → Add Media File. Debajo del área de carga se muestra el límite actual. Si aparece Maximum upload file size: 64 MB, significa que uploads.ini está funcionando:

Si sigue siendo de 2 MB, comprueba que la ruta de montaje de uploads.ini sea correcta. Después de modificarla, ejecuta docker compose up -d para volver a crear el contenedor (restart no aplica la nueva configuración).
Administrar la base de datos con phpMyAdmin (opcional)
phpMyAdmin tiene configurado profiles: [tools], por lo que no se inicia con el comando habitual docker compose up -d. Cuando lo necesites, añade --profile tools:
$ docker compose --profile tools up -d phpmyadmin
[+] up 25/25
✔ Image phpmyadmin Pulled 6.3s
✔ Container wordpress-db-1 Running 0.0s
✔ Container wordpress-phpmyadmin-1 Started 0.6s
Al final del comando se especifica phpmyadmin, así que solo se inicia ese servicio (y su dependencia db, que ya está en ejecución).
Abre http://localhost:8081, escribe root en Username y el valor de MYSQL_ROOT_PASSWORD de .env en Password:

Después de iniciar sesión, haz clic en la base de datos wordpress de la izquierda para ver las 12 tablas que ha creado WordPress (wp_posts, wp_options, wp_users…):

El puerto de phpMyAdmin está vinculado a 127.0.0.1, así que solo se puede acceder desde la máquina local. Si se trata de un servidor remoto, conéctate mediante un túnel SSH (ssh -L 8081:127.0.0.1:8081 usuario@servidor) y no lo expongas a Internet. Cuando termines, puedes detener solo este servicio:
docker compose --profile tools stop phpmyadmin
Dónde se almacenan los datos
| Volume | Contenido |
|---|---|
wordpress_db_data | Base de datos MySQL: entradas, páginas, ajustes, usuarios y comentarios |
wordpress_wp_data | Archivos de WordPress, temas, plugins y archivos multimedia subidos (wp-content/uploads) |
Debes hacer una copia de seguridad de ambos; si falta uno, no podrás restaurar el sitio completo.
Copia de seguridad
Ejecuta estos comandos en la carpeta del proyecto. Primero, exporta la base de datos con mysqldump; después, empaqueta el Volume wp_data en un archivo tar.gz con un contenedor alpine temporal:
$ docker compose exec -T db sh -c 'mysqldump -uroot -p"$MYSQL_ROOT_PASSWORD" wordpress' > wordpress.sql
mysqldump: [Warning] Using a password on the command line interface can be insecure.
$ ls -lh wordpress.sql && head -n 5 wordpress.sql
-rw-r--r-- 1 root root 880.8K Oct 13 00:22 wordpress.sql
-- MySQL dump 10.13 Distrib 8.4.11, for Linux (x86_64)
--
-- Host: localhost Database: wordpress
-- ------------------------------------------------------
-- Server version 8.4.11
$ docker run --rm -v "$(basename "$PWD")_wp_data:/data:ro" -v "$PWD:/backup" alpine:3.23 tar czf /backup/wp_data.tar.gz -C /data .
$ ls -lh wp_data.tar.gz
-rw-r--r-- 1 root root 33.9M Oct 13 00:22 wp_data.tar.gz
Explicación:
exec -T: desactiva TTY para que la salida se pueda redirigir correctamente a un archivo con>.sh -c '... -p"$MYSQL_ROOT_PASSWORD"': usa comillas simples para expandir la variable dentro del contenedor (el contenedor de MySQL tiene esta variable de entorno); la máquina host no necesita conocer la contraseña.[Warning] Using a password on the command line interface can be insecure.es una advertencia de MySQL que se escribe en stderr y no se incluye enwordpress.sql; puedes ignorarla.- El comienzo de
wordpress.sqlcontieneMySQL dump … Distrib 8.4.11, lo que indica que la exportación se ha realizado correctamente. $(basename "$PWD")_wp_datagenera el nombre del Volumewordpress_wp_data. Si el nombre de tu carpeta es diferente, compruébalo condocker volume ls.:roindica que el montaje es de solo lectura; el proceso de copia de seguridad no modifica los datos.
Copia wordpress.sql y wp_data.tar.gz a otra máquina o a un servicio de almacenamiento en la nube para tener una copia de seguridad de verdad.
Copia de seguridad en Windows
PowerShell no admite <, y > en Windows PowerShell 5.1 guarda los archivos SQL en UTF-16. En su lugar, crea el archivo dentro del contenedor y cópialo con docker compose cp. Al empaquetar wp_data, escribe directamente el nombre del Volume (de forma predeterminada, nombre_de_carpeta_wp_data):
docker compose exec db sh -c 'mysqldump -uroot -p$MYSQL_ROOT_PASSWORD --result-file=/tmp/wordpress.sql wordpress'
docker compose cp db:/tmp/wordpress.sql wordpress.sql
docker run --rm -v wordpress_wp_data:/data:ro -v "$(pwd):/backup" alpine:3.23 tar czf /backup/wp_data.tar.gz -C /data .
docker compose exec db sh -c 'mysqldump -uroot -p$MYSQL_ROOT_PASSWORD --result-file=/tmp/wordpress.sql wordpress'
docker compose cp db:/tmp/wordpress.sql wordpress.sql
docker run --rm -v wordpress_wp_data:/data:ro -v "${PWD}:/backup" alpine:3.23 tar czf /backup/wp_data.tar.gz -C /data .
docker compose exec db sh -c "mysqldump -uroot -p$MYSQL_ROOT_PASSWORD --result-file=/tmp/wordpress.sql wordpress"
docker compose cp db:/tmp/wordpress.sql wordpress.sql
docker run --rm -v wordpress_wp_data:/data:ro -v "%cd%:/backup" alpine:3.23 tar czf /backup/wp_data.tar.gz -C /data .
Para restaurar, haz lo contrario: docker compose cp wordpress.sql db:/tmp/restore.sql y después docker compose exec db sh -c 'mysql -uroot -p$MYSQL_ROOT_PASSWORD wordpress < /tmp/restore.sql'. El empaquetado y la extracción de tar se realizan dentro del contenedor alpine, por lo que Windows no afecta al proceso.
Restauración
En una máquina nueva (o después de borrar los datos), coloca compose.yaml, .env, uploads.ini y los dos archivos de copia de seguridad. Después, ejecuta:
# 1. Inicia solo la base de datos y espera a que esté healthy
docker compose up -d --wait db
# 2. Importa la base de datos
docker compose exec -T db sh -c 'mysql -uroot -p"$MYSQL_ROOT_PASSWORD" wordpress' < wordpress.sql
# 3. Restaura los archivos de WordPress en el Volume
docker run --rm \
-v "$(basename "$PWD")_wp_data:/data" \
-v "$PWD:/backup" \
alpine:3.23 sh -c "cd /data && tar xzf /backup/wp_data.tar.gz"
# 4. Inicia todos los servicios
docker compose up -d
--wait espera a que el servicio pase a healthy antes de terminar, para evitar que la importación empiece antes de que la base de datos esté lista. La contraseña de .env debe ser la misma que se usó al crear la copia de seguridad, porque wp-config.php (en wp_data) contiene la contraseña anterior de la base de datos.
Actualización
Puedes actualizar el núcleo de WordPress, los temas y los plugins directamente desde el panel de administración; los archivos se escribirán en el Volume wp_data.
Para actualizar las propias imágenes (PHP, Apache y versiones menores de MySQL):
$ docker compose pull
[+] pull 2/2
✔ Image wordpress:7-apache Pulled 0.6s
✔ Image mysql:8.4 Pulled 0.7s
$ docker compose up -d
[+] up 2/2
✔ Container wordpress-wordpress-1 Running 0.0s
✔ Container wordpress-db-1 Healthy 0.5s
pull descarga las imágenes nuevas; up -d solo vuelve a crear los contenedores cuyas imágenes hayan cambiado. En el ejemplo anterior las imágenes no tienen versiones nuevas, así que los contenedores siguen en estado Running.
Ten en cuenta lo siguiente:
- Haz una copia de seguridad antes de actualizar.
wordpress:7-apachese actualizará junto con la serie 7.x; si quieres fijar una versión, puedes escribir el número completo, por ejemplowordpress:7.1.3-apache.- Si
wp_dataya contiene archivos de WordPress, la imagen no los sobrescribirá, así que la versión del núcleo de WordPress dependerá de las actualizaciones desde el panel de administración. - Antes de actualizar MySQL entre versiones principales (por ejemplo, de 8.0 a 8.4), lee primero las instrucciones oficiales de actualización de MySQL; no cambies la etiqueta directamente.
Eliminar todo el entorno
Cuando termines de practicar, para limpiar el entorno añade --profile tools para eliminar también phpMyAdmin; -v eliminará los Volumes, por lo que se perderán todos los datos del sitio web:
$ docker compose --profile tools down -v
[+] down 6/6
✔ Container wordpress-wordpress-1 Removed 1.5s
✔ Container wordpress-phpmyadmin-1 Removed 1.5s
✔ Container wordpress-db-1 Removed 2.3s
✔ Volume wordpress_wp_data Removed 0.3s
✔ Network wordpress_default Removed 0.2s
✔ Volume wordpress_db_data Removed 0.3s
Consideraciones para producción
- Coloca delante un proxy inverso que gestione HTTPS, como Nginx, Caddy o Traefik. Consulta Sitio estático y proxy inverso con Nginx.
- No expongas la base de datos al exterior con
portsni publiques phpMyAdmin en Internet. - No uses
admincomo nombre de la cuenta de administrador y elige una contraseña segura. - Programa copias de seguridad periódicas (por ejemplo, ejecutando el comando de copia de seguridad anterior con cron) y guárdalas en otra máquina.
- No subas
.enva Git.
Resumen
- Gestiona WordPress y MySQL con Compose; los datos se guardan por separado en los Volumes
db_dataywp_data. - La comprobación de estado de MySQL debe usar
-h 127.0.0.1para evitar que se marque como healthy durante la inicialización. - Coloca phpMyAdmin en un profile y vincúlalo solo a 127.0.0.1.
- Las copias de seguridad deben incluir tanto la base de datos como los archivos del sitio web, y guardarse en otra máquina.
Ejercicios
Ejercicio 1: Usa un comando para confirmar que el límite de carga de PHP ya es 64M.
Respuesta
docker compose exec wordpress php -r "echo ini_get('upload_max_filesize');"
La salida es 64M.
Ejercicio 2: Tienes que trasladar el sitio web a otra máquina. ¿Qué archivos debes llevar?
Respuesta
compose.yaml, .env, uploads.ini y las dos copias de seguridad: wordpress.sql (base de datos) y wp_data.tar.gz (archivos del sitio web). En la nueva máquina, sigue los pasos de «Restauración» de este capítulo.
Preguntas frecuentes
¿Por qué la pantalla de instalación no me pide la configuración de la base de datos?
Las variables de entorno WORDPRESS_DB_* ya proporcionan la información de conexión, y la imagen genera automáticamente wp-config.php al iniciarse.
¿Por qué aparece un error de límite de tamaño al subir un archivo?
El límite de subida PHP predeterminado de la imagen de WordPress es 2 MB. Ajústalo con uploads.ini de este capítulo y reconstruye los contenedores con docker compose up -d.
¿Cómo hago una copia de seguridad en Windows?
PowerShell no admite <, y > en Windows PowerShell 5.1 cambia la codificación del archivo. La sección «Copias de seguridad» de este capítulo incluye una forma de exportar desde el contenedor y luego copiarlo con docker cp.
Actualizado 14 de octubre de 2026