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ónFunción
MYSQL_DATABASE / MYSQL_USER / MYSQL_PASSWORDLa 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)
healthcheckCada 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_healthyEspera a que db esté healthy para iniciar WordPress y evitar que arranque antes de poder conectarse a la base de datos
WORDPRESS_DB_HOST: dbConecta mediante el nombre del servicio db; la red de Compose lo resuelve automáticamente a la IP del contenedor MySQL
db_data:/var/lib/mysqlLos datos de MySQL se guardan en un Volume
wp_data:/var/www/htmlEl 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:80phpMyAdmin 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:

Pantalla de instalación de WordPress: introduce el título del sitio, el nombre de usuario, la contraseña y el correo electrónico
Pantalla de instalación: Site Title, Username, Password y Your Email; al final, pulsa Install WordPress
CampoDescripción
Site TitleTítulo del sitio. Puedes cambiarlo más adelante desde el panel de administración.
UsernameCuenta de administrador. No uses admin, porque es fácil de adivinar (en este ejemplo solo se muestra a modo ilustrativo).
PasswordContraseña del administrador. WordPress muestra su nivel de seguridad.
Your EmailCorreo electrónico del administrador. Se usa para recibir notificaciones y restablecer la contraseña.
Search engine visibilitySi 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:

Pantalla de instalación correcta de WordPress
Instalación correcta. Pulsa Log In para ir a la página de inicio de sesión.
Página de inicio de sesión de WordPress
Página de inicio de sesión http://localhost:8080/wp-login.php

Inicia 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:

Panel de administración de WordPress 7.1.3
Panel de administración de WordPress (Dashboard)

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

Página pública del sitio de WordPress
Página pública del sitio (si has iniciado sesión, verás la barra de herramientas de administración en la parte superior)

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:

La página de carga de WordPress muestra Maximum upload file size: 64 MB
La página Upload Media muestra el límite de 64 MB en la parte inferior

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:

Pantalla de inicio de sesión de phpMyAdmin
Pantalla de inicio de sesión de phpMyAdmin; PMA_HOST ya ha especificado db como servidor

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…):

phpMyAdmin muestra las tablas de la base de datos wordpress
Tablas de la base de datos wordpress

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

VolumeContenido
wordpress_db_dataBase de datos MySQL: entradas, páginas, ajustes, usuarios y comentarios
wordpress_wp_dataArchivos 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 en wordpress.sql; puedes ignorarla.
  • El comienzo de wordpress.sql contiene MySQL dump … Distrib 8.4.11, lo que indica que la exportación se ha realizado correctamente.
  • $(basename "$PWD")_wp_data genera el nombre del Volume wordpress_wp_data. Si el nombre de tu carpeta es diferente, compruébalo con docker volume ls.
  • :ro indica 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-apache se actualizará junto con la serie 7.x; si quieres fijar una versión, puedes escribir el número completo, por ejemplo wordpress:7.1.3-apache.
  • Si wp_data ya 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 ports ni publiques phpMyAdmin en Internet.
  • No uses admin como 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 .env a Git.

Resumen

  • Gestiona WordPress y MySQL con Compose; los datos se guardan por separado en los Volumes db_data y wp_data.
  • La comprobación de estado de MySQL debe usar -h 127.0.0.1 para 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