Docker Compose permite describir en un único archivo YAML todos los contenedores de una aplicación, con sus redes, volúmenes y variables, y gestionarlos juntos con un solo comando. En este tutorial desplegarás en Ubuntu 24.04 un WordPress con su base de datos MariaDB usando Compose, guardarás las credenciales fuera del archivo principal, harás que WordPress espere a que la base de datos esté lista y aprenderás los comandos para operar, actualizar y eliminar la pila.

Requisitos previos

  • Un servidor con Ubuntu 24.04 LTS, por ejemplo un VPS de CubePath, con al menos 1 GB de RAM.
  • Docker Engine y el plugin docker-compose-plugin instalados desde el repositorio oficial de Docker.
  • Un usuario no root con privilegios sudo y miembro del grupo docker.

Comprueba que tienes Compose v2:

docker compose version
Docker Compose version v2.39.2

El comando actual es docker compose (con espacio). El antiguo binario docker-compose escrito en Python está abandonado desde 2023; si tus scripts lo usan, cámbialos a la nueva sintaxis.

Paso 1: Crear el directorio del proyecto

Compose usa el nombre del directorio como nombre del proyecto, que sirve de prefijo para contenedores, redes y volúmenes. Crea uno para la pila:

mkdir -p ~/wordpress && cd ~/wordpress

Paso 2: Guardar las credenciales en un archivo .env

Compose lee automáticamente el archivo .env del directorio del proyecto y sustituye sus valores en compose.yaml. Así las contraseñas no quedan en el archivo que versionas.

nano .env
MARIADB_ROOT_PASSWORD=your_root_password
MARIADB_PASSWORD=your_db_password
WP_PORT=8080

Sustituye your_root_password y your_db_password por contraseñas largas y distintas; puedes generarlas con openssl rand -base64 24. Restringe los permisos del archivo:

chmod 600 .env

Paso 3: Escribir el archivo compose.yaml

El nombre recomendado del archivo es compose.yaml (también se aceptan compose.yml y el antiguo docker-compose.yml). Crea el archivo:

nano compose.yaml
services:
  db:
    image: mariadb:11.4
    restart: unless-stopped
    environment:
      MARIADB_DATABASE: wordpress
      MARIADB_USER: wordpress
      MARIADB_PASSWORD: ${MARIADB_PASSWORD:?define MARIADB_PASSWORD en .env}
      MARIADB_ROOT_PASSWORD: ${MARIADB_ROOT_PASSWORD:?define MARIADB_ROOT_PASSWORD en .env}
    volumes:
      - db_data:/var/lib/mysql
    networks:
      - backend
    healthcheck:
      test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"]
      interval: 10s
      timeout: 5s
      retries: 5
      start_period: 30s

  wordpress:
    image: wordpress:php8.3-apache
    restart: unless-stopped
    depends_on:
      db:
        condition: service_healthy
    environment:
      WORDPRESS_DB_HOST: db
      WORDPRESS_DB_NAME: wordpress
      WORDPRESS_DB_USER: wordpress
      WORDPRESS_DB_PASSWORD: ${MARIADB_PASSWORD}
    ports:
      - "127.0.0.1:${WP_PORT:-8080}:80"
    volumes:
      - wp_data:/var/www/html
    networks:
      - backend

volumes:
  db_data:
  wp_data:

networks:
  backend:

Los bloques del archivo son:

  • services: cada entrada es un contenedor. image indica la imagen (con etiqueta fija), restart la política de reinicio, environment las variables y volumes los datos persistentes.
  • ${VARIABLE}: se sustituye con el valor de .env. La forma ${VAR:?mensaje} hace que Compose se niegue a arrancar si la variable no está definida, y ${VAR:-8080} aplica un valor por defecto.
  • depends_on con condition: service_healthy: WordPress no arranca hasta que el healthcheck de MariaDB da positivo. Sin la condición, Compose solo esperaría a que el contenedor existiera, no a que la base de datos aceptara conexiones.
  • healthcheck: la imagen oficial de MariaDB incluye el script healthcheck.sh, que comprueba la conexión y que InnoDB está inicializado.
  • networks: los servicios de la misma red se localizan por su nombre. Por eso WORDPRESS_DB_HOST es simplemente db.
  • volumes (nivel superior): declara volúmenes con nombre que sobreviven a la eliminación de los contenedores.

Ya no hace falta la clave version: al principio del archivo. Compose la ignora y muestra un aviso de que está obsoleta.

El puerto se publica solo en 127.0.0.1, porque los puertos que publica Docker no pasan por UFW. Para exponer el sitio lo correcto es poner delante un proxy inverso con HTTPS (Nginx o Caddy) que reenvíe a 127.0.0.1:8080.

Antes de arrancar, valida el archivo y comprueba cómo quedan las variables sustituidas:

docker compose config

Si hay un error de sintaxis YAML o falta una variable obligatoria, Compose lo indica aquí con la línea exacta. Ten en cuenta que la salida muestra las contraseñas en claro.

Paso 4: Arrancar la pila

Descarga las imágenes, crea la red y los volúmenes y arranca los contenedores en segundo plano:

docker compose up -d
[+] Running 5/5
 Network wordpress_backend      Created
 Volume "wordpress_db_data"     Created
 Volume "wordpress_wp_data"     Created
 Container wordpress-db-1       Healthy
 Container wordpress-wordpress-1 Started

Observa que el contenedor de WordPress arranca después de que db pase a Healthy. Consulta el estado de los servicios:

docker compose ps
NAME                    IMAGE                     SERVICE     STATUS                    PORTS
wordpress-db-1          mariadb:11.4              db          Up 40 seconds (healthy)   3306/tcp
wordpress-wordpress-1   wordpress:php8.3-apache   wordpress   Up 12 seconds             127.0.0.1:8080->80/tcp

Comprueba que WordPress responde y redirige al asistente de instalación:

curl -I http://127.0.0.1:8080
HTTP/1.1 302 Found
Location: http://127.0.0.1:8080/wp-admin/install.php

Para completar la instalación desde tu navegador sin exponer el puerto, abre un túnel SSH desde tu equipo con ssh -L 8080:127.0.0.1:8080 your_user@your_server_ip y visita http://localhost:8080.

Paso 5: Operar los servicios

Todos los comandos de Compose se ejecutan desde el directorio del proyecto y aceptan el nombre de un servicio para actuar solo sobre él.

Ver los logs de un servicio y seguirlos en tiempo real:

docker compose logs -f --tail 50 wordpress

Ejecutar un comando dentro de un servicio en marcha, por ejemplo una consola de MariaDB (te pedirá la contraseña de wordpress):

docker compose exec db mariadb -u wordpress -p wordpress

Comprueba que las tablas existen una vez completada la instalación web:

SHOW TABLES;

Escribe exit para salir. Otros comandos habituales:

ComandoEfecto
docker compose stopDetiene los contenedores sin eliminarlos
docker compose startArranca los contenedores detenidos
docker compose restart wordpressReinicia un servicio
docker compose topProcesos en ejecución de cada servicio
docker compose downDetiene y elimina contenedores y redes. Los volúmenes se conservan

Paso 6: Cambiar la configuración y actualizar imágenes

Compose compara el estado actual con compose.yaml y solo recrea lo que ha cambiado. Por ejemplo, para limitar la memoria de la base de datos, añade la clave mem_limit al servicio db:

nano compose.yaml
  db:
    image: mariadb:11.4
    restart: unless-stopped
    mem_limit: 512m

Aplica el cambio:

docker compose up -d
 Container wordpress-db-1         Recreated
 Container wordpress-wordpress-1  Running

Solo se recrea db; los datos siguen en el volumen wordpress_db_data.

Para actualizar a las últimas versiones publicadas de las etiquetas que usas, descarga las imágenes y recrea los contenedores que hayan cambiado:

docker compose pull
docker compose up -d

Después elimina las imágenes antiguas que ya no usa ningún contenedor:

docker image prune

Paso 7: Separar la configuración de desarrollo

Si existe un archivo compose.override.yaml junto a compose.yaml, Compose lo combina automáticamente. Es útil para ajustes que solo quieres en tu máquina local, por ejemplo montar un tema de WordPress en desarrollo:

nano compose.override.yaml
services:
  wordpress:
    volumes:
      - ./mi-tema:/var/www/html/wp-content/themes/mi-tema
    environment:
      WORDPRESS_DEBUG: "1"

En el servidor de producción no copies el archivo de override, o indica explícitamente qué archivos usar con -f:

docker compose -f compose.yaml up -d

Paso 8: Detener y eliminar la pila

Para eliminar contenedores y redes conservando los datos:

docker compose down

Si vuelves a ejecutar docker compose up -d, WordPress arrancará con todo su contenido. Para borrarlo todo, volúmenes incluidos:

docker compose down -v

Solución de problemas

required variable MARIADB_PASSWORD is missing a value. Falta el archivo .env o la variable en él. Compose busca .env en el directorio del proyecto, que por defecto es el directorio donde está compose.yaml.

dependency failed to start: container wordpress-db-1 is unhealthy. MariaDB no ha arrancado bien. Revisa docker compose logs db. Si cambiaste las contraseñas de .env después del primer arranque, ten en cuenta que MariaDB solo las aplica al inicializar un volumen vacío.

WordPress muestra Error establishing a database connection. Comprueba que WORDPRESS_DB_PASSWORD y MARIADB_PASSWORD coinciden y que ambos servicios están en la misma red con docker compose config.

Conclusión

Has desplegado una aplicación de dos servicios con Docker Compose, con credenciales en .env, volúmenes persistentes, una red interna y un arranque ordenado mediante healthchecks, y conoces los comandos para operarla y actualizarla. Como siguientes pasos, pon un proxy inverso con HTTPS delante de WordPress, programa copias de seguridad de los volúmenes y convierte tus propias aplicaciones en servicios de Compose a partir de su Dockerfile.