CI/CD para WordPress: Automatizando tu despliegue en 2026

CI/CD para WordPress: Automatizando tu despliegue en 2026

Última verificación: 15 de octubre de 2026
17 min de lectura
Guía
Desarrollador full-stack

Arrastrar temas por FTP en 2026 suele acabar igual: falta un archivo de inc/, el composer.lock viene de otro portátil y producción cae porque alguien sobrescribió wp-config.php. La Integración Continua y el Despliegue Continuo (CI/CD) no son un capricho de grandes empresas: son la forma de que cada cambio en Git pase por el mismo build, las mismas pruebas y el mismo camino hasta el servidor.

Esta guía trata de WordPress con un tema o plugin en el repositorio (Composer, npm y, si quieres, Docker), no de un sitio que solo existe en el panel. Si el código personalizado vive solo en la base de datos y en la biblioteca de medios, primero lleva temas y código propio a Git: sin eso, CI/CD no tiene nada que construir.

Documentación del runner: GitHub Actions. En paralelo conviene endurecer el servidor según Hardening WordPress. Para los comandos posteriores al despliegue, WP-CLI.

#Por qué no desplegar WordPress por FTP

El despliegue manual es lento, inconsistente y arriesgado. Tres problemas aparecen una y otra vez en tiendas WooCommerce y en traspasos entre agencias:

  1. Entornos distintos: en local PHP 8.2 y Composer 2.7, en el VPS PHP 8.1 sin intl. El composer install local “funciona”; producción cae con un class not found.
  2. Sin trazabilidad: tras un incidente nadie sabe qué commit estaba en producción. El panel del hosting muestra la fecha de un archivo, no un SHA de Git.
  3. Secretos en disco: contraseñas SFTP guardadas en FileZilla o un .env subido al repositorio “solo un momento” siguen siendo una vía clásica de fuga.

Al subir archivos a mano dependes de tu memoria para que cada archivo llegue al sitio correcto, con los permisos correctos y en el orden correcto. Los fallos típicos son sobrescribir la configuración de producción con la de desarrollo, olvidar dependencias actualizadas o mezclar archivos de ramas distintas. El protocolo FTP, además, envía las credenciales en texto plano; SFTP lo corrige, pero las credenciales guardadas en portátiles siguen siendo un objetivo para el malware.

CI/CD lo resuelve de forma mecánica: build sobre una imagen conocida (shivammathur/setup-php o tu propio Docker), artefacto versionado por SHA y despliegue por SSH con una clave guardada en Secrets. Cada despliegue queda ligado a un commit, a un pull request y a un registro del pipeline, y los pasos son idénticos lo ejecute quien lo ejecute.

#Cómo desplegar WordPress con GitHub Actions

Un workflow típico para un tema propio con plugins vía Composer:

  1. Disparador: push a main o fusión de un pull request revisado. develop suele desplegar solo a staging.
  2. Build: composer install --no-dev --optimize-autoloader, npm ci, npm run build. Fija PHP y Node en el workflow (php-version: '8.3', node-version: '24').
  3. Pruebas: PHPUnit para plugins y, si procede, Playwright en el flujo de compra. Si fallan, el job se detiene y no hay despliegue.
  4. Artefacto: empaqueta una release sin .git ni node_modules, con vendor/ y assets/dist/ ya compilados.
  5. Despliegue: rsync o SCP a releases/<run_id>/ y después el cambio atómico del enlace simbólico.

Fija versiones que aún reciben correcciones. PHP 8.2 recibe parches de seguridad solo hasta el 31 de diciembre de 2026 y PHP 8.3 hasta el 31 de diciembre de 2027 (versiones soportadas de PHP). Node.js 20 llegó al final de su vida el 30 de abril de 2026; Node.js 22 tiene soporte hasta el 30 de abril de 2027 y Node.js 24 hasta el 30 de abril de 2028 (calendario de versiones de Node.js). La versión menor de PHP sigue a producción, no a la última publicada.

Esbozo corto en GitHub Actions, con etiquetas de versión mayor para que se lea bien; la sección sobre cómo proteger el workflow, más abajo, explica por qué en producción se fijan SHAs de commit:

name: deploy-production
on:
  push:
    branches: [main]
permissions:
  contents: read
concurrency:
  group: deploy-production
  cancel-in-progress: false
jobs:
  build-and-deploy:
    runs-on: ubuntu-latest
    environment: production
    steps:
      - uses: actions/checkout@v7
      - uses: shivammathur/setup-php@v2
        with:
          php-version: '8.3'
      - run: composer install --no-dev --optimize-autoloader
      - uses: actions/setup-node@v7
        with:
          node-version: '24'
          cache: npm
      - run: npm ci && npm run build
      - name: rsync release
        env:
          SSH_KEY: ${{ secrets.DEPLOY_SSH_KEY }}
        run: |
          # escribir la clave, rsync a releases/$GITHUB_SHA, ln -sfn

Guarda el script completo del enlace simbólico en el repositorio como bin/release.sh y llámalo desde el workflow. Así el mismo script funciona desde un portátil cuando Actions no está disponible.

#Qué runner de GitHub usar para WordPress

GitHub ofrece runners alojados con Ubuntu, Windows y macOS. Para WordPress lo habitual es Ubuntu, porque se parece más al servidor de producción típico. Los runners autoalojados ejecutan el pipeline en tu propia infraestructura cuando necesitas otro hardware o acceso a una red privada.

#Cómo evitar dos despliegues de WordPress a la vez en GitHub Actions

Dos fusiones rápidas a main arrancan dos ejecuciones del workflow. Sin bloqueo, ambas hacen rsync a releases/ y cambian current en un orden que nadie eligió. Un grupo concurrency en GitHub Actions permite como mucho un workflow en ejecución por grupo; una ejecución recién encolada espera como pending (documentación de GitHub: controlar la concurrencia).

En un despliegue cuentan dos detalles. Por defecto, un grupo mantiene una ejecución activa y una pendiente, y una ejecución más nueva cancela la pendiente más antigua: gana el último commit y el del medio se salta. cancel-in-progress: true cancelaría también el job en curso, lo que puede cortar el rsync a mitad de un directorio de release. En producción déjalo en false y usa true en los workflows de pruebas de los pull requests, donde solo importa el commit más reciente.

concurrency:
  group: deploy-production
  cancel-in-progress: false

Como el enlace simbólico cambia solo después de un rsync completo, una ejecución pendiente cancelada nunca toca current.

#Cómo proteger producción con entornos de GitHub Actions

Añade environment: production al job de despliegue y configura ese entorno en los ajustes del repositorio. Se aplican tres protecciones (documentación de GitHub: despliegues y entornos):

  • Revisores obligatorios: hasta seis usuarios o equipos, y una aprobación libera el job. Con el bloqueo de autoaprobación activo, quien lanzó el despliegue no puede aprobarlo.
  • Ramas y etiquetas de despliegue: solo main o las etiquetas de release pueden desplegar a production, así que un workflow en una rama de funcionalidad no llega al servidor de producción.
  • Secretos de entorno: disponibles solo para los jobs que referencian el entorno y, con revisores obligatorios, solo después de la aprobación. El DEPLOY_SSH_KEY de producción va aquí, no en los secretos del repositorio.

Cada entorno (desarrollo, staging, producción) tiene así su propio juego de secretos y variables, y los de producción no son accesibles desde staging. Revisa el plan antes de contar con ello. En GitHub Free, Pro y Team, los revisores obligatorios y los temporizadores de espera solo funcionan en repositorios públicos. Las ramas de despliegue y los secretos de entorno funcionan en repositorios privados con Pro y Team, pero no con Free. Los repositorios WordPress de agencia suelen ser privados, así que en esos planes no hay puerta de aprobación y la protección recae en las reglas de rama.

#Cómo proteger el workflow de despliegue en GitHub Actions

El job de despliegue guarda una clave SSH de producción, así que el archivo del workflow merece la misma revisión que el código PHP. La referencia de GitHub sobre uso seguro recoge las medidas que importan aquí:

  • Fija las actions de terceros al SHA completo del commit. Una etiqueta como @v2 se puede mover o borrar. Redirigir un SHA exigiría una colisión SHA-1. Toma el SHA del repositorio de la propia action, no de un fork. GitHub señala ahora un lockfile de dependencias del workflow como la opción más fuerte para los repositorios que lo adopten.
  • Da a GITHUB_TOKEN lo mínimo. Pon permissions: contents: read arriba y amplía por job solo donde un paso lo necesite. Un despliegue por rsync y SSH solo necesita leer el repositorio.
  • No metas entradas no confiables directamente en run:. El título de un pull request o el nombre de una rama dentro de ${{ }} en un script de shell abre la puerta a la inyección de código. Pasa el valor por una variable de entorno intermedia.
  • Un secreto por valor. Los datos estructurados, como un bloque JSON, pueden romper el enmascarado en los logs, así que guarda host, usuario, ruta y clave como secretos separados.

La misma página indica que el enmascarado solo cubre los secretos usados en un job y accesibles para el runner, así que nunca hagas echo de valores derivados, como una clave decodificada.

#Despliegue atómico de WordPress sin tiempo de inactividad

Sobrescribir archivos en su sitio dentro de public_html hace que algunas peticiones vean durante un momento un functions.php viejo con una plantilla nueva. En WooCommerce eso puede romper sesiones de carrito en plena campaña.

Estructura atómica:

  • base: /var/www/site/
  • releases: /var/www/site/releases/20260920-142211/
  • shared: uploads, wp-config.php, caché de objetos, fuera de la release
  • current: enlace simbólico a la release activa

Tras un rsync correcto:

ln -sfn /var/www/site/releases/20260920-142211 /var/www/site/current

El root de Nginx apunta a .../current/public (o su equivalente). El cambio es inmediato. Borra las releases antiguas pasados N días o conserva las tres últimas para volver atrás.

Los uploads de WordPress tienen que vivir fuera de la release; si no, cada despliegue borra medios o copia gigas. El patrón clásico: shared/uploads enlazado en wp-content/uploads de cada release. Volver atrás es lo mismo al revés: apuntar current a la release anterior.

#Entorno staging de WordPress y estrategias de despliegue comparadas

En 2026, el camino desde una rama de funcionalidad por un pull request y staging hasta main y producción no es opcional para tiendas y webs de captación. El entorno de staging debe compartir con producción:

  • la misma versión de PHP y las mismas extensiones
  • una copia de la base de datos (anonimizada si hay datos personales bajo el RGPD)
  • los secretos de pago solo en modo sandbox

Las diferencias entre staging y producción son la fuente más común de errores del tipo “funciona en staging, pero no en producción”. Las URL de previsualización por pull request ayudan en rediseños y revisiones de CSS, sobre todo en agencias donde el cliente aprueba cambios visuales antes de publicarlos; las migraciones de esquema siguen necesitando staging con un volcado real.

EstrategiaRiesgoCoste de mantenimientoCuándo encaja
FTP manualMuy altoArranque barato, caídas carasSolo pruebas o aprendizaje
Hook de Git en el servidorMedioBajoPortfolio pequeño, un desarrollador
CI + rsync atómicoBajoMedioWebs de negocio, WooCommerce, agencias
Blue/green o dos VPSMínimoAltoMucho tráfico, SLA, pagos

En blue/green mantienes dos entornos de producción idénticos: uno sirve el tráfico y el otro recibe la versión nueva; cuando está verificada, el balanceador cambia el tráfico. Un despliegue canary manda primero la versión nueva a una parte pequeña del tráfico y la amplía mientras los errores se mantengan bajos. Ambos exigen infraestructura duplicada y rara vez compensan por debajo de cientos de sesiones simultáneas. A la mayoría de tiendas medianas les basta con el enlace simbólico atómico y la vuelta atrás por directorio.

#Pruebas automatizadas en un pipeline CI/CD de WordPress

Un pipeline sin pruebas solo envía código roto más deprisa. Cuatro capas encajan en un repositorio WordPress, de la más rápida a la más lenta:

  • Pruebas unitarias con PHPUnit: comprueban funciones sueltas de un plugin o tema sin base de datos. Se ejecutan en cada push.
  • Pruebas de integración: cargan WordPress contra una base de datos de pruebas temporal. wp scaffold plugin-tests genera la estructura: phpunit.xml.dist, bin/install-wp-tests.sh para la suite de pruebas y la base de datos, tests/bootstrap.php, una prueba de ejemplo y un conjunto de reglas .phpcs.xml.dist. El archivo de CI por defecto es para CircleCI; --ci=github escribe en su lugar una configuración para GitHub Actions (WP-CLI: wp scaffold plugin-tests).
  • Análisis estático con PHPStan y PHP_CodeSniffer: detecta errores de tipos e infracciones del estándar de código sin ejecutarlo.
  • Pruebas end-to-end con Playwright: recorren flujos reales de usuario, como un formulario de contacto o el carrito y el checkout de WooCommerce. Son la capa más lenta, así que ejecútalas en los pull requests a main y contra staging, no en cada commit.

Un job en rojo en cualquier capa detiene el workflow antes del paso del artefacto, y nada llega al servidor.

#Cómo gestionar secretos y migraciones de base de datos en el CI/CD de WordPress

Nunca subas al repositorio:

  • claves SSH
  • contraseñas de base de datos
  • AUTH_KEY / SECURE_AUTH_KEY de wp-config.php
  • tokens de API de pasarelas de pago

En GitHub están en Settings, luego Secrets and variables, luego Actions. Se inyectan como env en el paso de despliegue, así que aunque el repositorio se filtre, el servidor sigue protegido. En el servidor, wp-config.php vive en shared/ y no forma parte del artefacto de CI.

WP-CLI tras el cambio del enlace simbólico:

  • wp cache flush si hay caché de objetos
  • wp rewrite flush después de cambiar CPT
  • wp plugin list como línea de smoke en el log

#Cómo ejecutar WP-CLI en el servidor de producción desde el runner

Estos comandos tienen que ejecutarse en el servidor, contra la release nueva. WP-CLI lo hace desde el runner con el parámetro global --ssh ([<scheme>:][<user>@]<host>[:<port>][<path>]) o con alias en el wp-cli.yml del proyecto (manual de WP-CLI: ejecutar comandos en remoto):

@staging:
  ssh: [email protected]/var/www/site/current/public
@prod:
  ssh: [email protected]/var/www/site/current/public

Con ese archivo en el repositorio, wp @prod cache flush se ejecuta en producción. WP-CLI tiene que estar instalado igualmente en el servidor y accesible como wp: el runner solo pasa el comando por SSH.

Otros comandos útiles en el paso posterior al cambio, todos en la referencia de comandos de WP-CLI:

  • wp core is-installed no imprime nada y responde con el código de salida, así que una comprobación fallida hace fallar el paso.
  • wp core update-db --dry-run compara las versiones de la base de datos antes del wp core update-db real tras una actualización del núcleo.
  • wp core verify-checksums compara los archivos del núcleo con las sumas de comprobación de WordPress.org y detecta archivos cambiados fuera de Git.
  • wp maintenance-mode activate y deactivate cubren la release poco frecuente que debe cambiar el esquema mientras nada escribe en él.

Planifica las migraciones de base de datos aparte del despliegue de archivos. Volver atrás el enlace simbólico no deshace un ALTER TABLE. En migraciones destructivas: primero copia de seguridad (wp db export en staging y producción), luego la migración y solo entonces el tráfico al código que necesita el esquema, o bien un feature flag en PHP.

Documentación de dependencias PHP: Composer install. En CI, siempre --no-dev para producción y un composer.lock bloqueado.

#Errores comunes en un pipeline de despliegue de WordPress

En VPS (DigitalOcean, Linode, Hetzner) y en hosting gestionado con SSH se repiten estos fallos:

  • .gitignore se come vendor/: producción recibe un tema sin autoloader. O construyes vendor en el runner y lo incluyes en el artefacto, o ejecutas Composer en el servidor después del rsync (y entonces el servidor necesita Composer y acceso a Packagist).
  • Rutas equivocadas: el script supone /var/www/html y el hosting usa /home/user/domains/.... Fija las rutas en Secrets por entorno.
  • Opcache conserva el código viejo: después de ln -sfn, recarga PHP-FPM o llama a opcache_reset mediante WP-CLI o un mu-plugin, solo para el usuario de despliegue.
  • Cron y colas: el Action Scheduler de WooCommerce puede ejecutar tareas con el código viejo durante unos minutos; tras una release grande, vigila un rato las tareas fallidas.

Smoke test tras el despliegue (curl desde el runner o en un job aparte):

  • portada con HTTP 200
  • /wp-login.php con 200
  • una URL crítica de producto o de formulario
  • opcionalmente un HEAD a la CDN si los assets llevan hash en el nombre

Si el smoke test falla, volver automáticamente al enlace simbólico anterior cuesta menos que la llamada del cliente a las 23:00.

#Cómo desplegar temas de bloques y mu-plugins con Git

No toda la superficie de WordPress cabe en el mismo pipeline. El contenido en la base de datos (entradas, grupos de campos ACF si no exportas JSON, CSS de Elementor) sigue fuera de la release. CI/CD protege el código; no sustituye a las copias de seguridad de la base de datos.

Divisiones prácticas:

  • Tema clásico o híbrido con Vite: build completo en CI, artefacto con style.css + dist/.
  • Tema de bloques (FSE): guarda theme.json y las plantillas HTML en Git; los cambios hechos en el editor del sitio vuelven por pull request, o producción y Git divergen tras el primer “Guardar”.
  • Must-use plugins: viajan con la release; no se actualizan desde el panel. Buen sitio para comprobaciones de salud del despliegue y para desactivar el editor de archivos.
  • Plugins de wordpress.org: fija versiones con Composer (wpackagist) o actualízalos en un job revisado. Mezclar “Actualizar en el panel” con “CI sobrescribió plugins/” deja un árbol de plugins aleatorio.

Las pilas de agencia suelen mezclar un tema propio, WooCommerce desde wpackagist y dos plugins premium en zip instalados con un repositorio path de Composer. El pipeline debe conocer las tres fuentes, o staging y producción acabarán diferenciándose en un zip de hace seis meses.

#Lista de comprobación para el despliegue continuo de WordPress

Antes de conectar main a producción, recorre esta lista una vez:

  1. Repositorio privado, o confirma que el historial no contiene secretos (git log -p no muestra .env antiguos).
  2. Protección de rama: pull request obligatorio y workflow test en verde obligatorio.
  3. Secrets separados: DEPLOY_HOST_STAGING, DEPLOY_HOST_PROD y claves SSH distintas.
  4. Staging con la misma versión de PHP que producción (no “nosotros usamos 8.3 y el cliente está en 8.1”).
  5. Copia de la base de datos antes del primer cambio atómico, aunque la vuelta atrás del código esté lista.
  6. Monitorización de disponibilidad (UptimeRobot, Better Stack o similar) con aviso por SMS o Slack sobre la URL de producción.
  7. Un runbook de una página: cómo hacer a mano ln -sfn a la release anterior y quién tiene acceso al servidor.

Solo entonces activa el despliegue automático desde main. Hasta ese momento basta con “build + pruebas” en los pull requests y un workflow_dispatch manual a staging.

#Conclusión

CI/CD para WordPress no es un “YAML mágico”. Es un contrato repetible: el mismo PHP, el mismo lockfile, el mismo artefacto y un cambio sin ventana de archivos a medio subir. Empieza por Git y staging, añade Actions para los builds de Composer y npm, y termina con un current atómico y un smoke test. El FTP pasa a ser una herramienta de emergencia, no el proceso.

Si necesitas un desarrollador WordPress que mantenga ese pipeline junto al código del tema de una tienda o una web de captación, WPPoland trabaja con una pila real. Para reforzar la parte de seguridad, revisa también la auditoría de seguridad WordPress.

Siguiente paso

Transforma el artículo en una implementación real

Este bloque refuerza el enlazado interno y lleva al lector al siguiente paso más útil dentro de la arquitectura del sitio.

¿Quieres implementar esto en tu sitio?

Si quieres transformar el artículo en mejoras concretas, rediseño o un plan de implementación, puedo cerrar el alcance y ejecutar.

Explora otros servicios WordPress y base de conocimiento

Refuerza tu negocio con soporte técnico profesional en áreas clave del ecosistema WordPress.

Preguntas frecuentes

Respuestas prácticas para aplicar el tema en la ejecución real.

¿Tiene sentido CI/CD para un freelance que trabaja solo?#
Sí. Quien trabaja solo es quien más pierde con un include de PHP olvidado o un composer.lock generado con otra versión de PHP. El pipeline fuerza el mismo build en cada commit y deja un registro auditable de qué SHA está en producción.
¿Qué es el despliegue sin tiempo de inactividad en WordPress?#
El código nuevo llega a releases/TIMESTAMP. Solo después de un rsync correcto y, si hace falta, wp cache flush, cambias el enlace simbólico current. PHP-FPM o Nginx sirven desde current, así que el cambio tarda milisegundos y no necesita modo mantenimiento.
¿Necesito un VPS para CI/CD?#
Un VPS o contenedores dan control total (SSH, enlaces simbólicos, WP-CLI). En hosting gestionado sueles tener una API de despliegue o SFTP con una clave en Secrets, que sigue siendo mejor que arrastrar archivos en FileZilla, aunque no haya blue/green real.
¿Qué pasa si un despliegue falla?#
Guarda N directorios de releases anteriores. Si el smoke test devuelve 500 o falta vendor/, apunta el enlace simbólico a la release anterior. Planifica las migraciones de base de datos aparte: volver atrás el código no deshace un ALTER TABLE.
¿Dónde se compilan los temas con Vite o webpack?#
En el runner de CI: checkout, setup de Node, npm ci, npm run build y después dist/ entra en el artefacto junto con el PHP. No subas node_modules ni fuentes TypeScript a producción, solo CSS y JS compilados.

¿Necesitas un FAQ adaptado a tu sector y mercado? Preparo una versión alineada con tus objetivos de negocio.

Hablemos

Artículos Relacionados