Cómo reducir el tiempo de build y evitar artefactos duplicados en CI para monolitos PHP grandes
Por Kevin Marcondes · Publicado el · 7 min de lectura
¿Tu pipeline está lento? Aprende a reducir el tiempo de build y a evitar artefactos duplicados en CI para monolitos PHP grandes. La clave está en un caché eficiente de Composer, capas Docker determinísticas y un artefacto único para pruebas y despliegue. A continuación tienes un plan práctico para diagnosticar cuellos de botella y aplicar mejoras sin rehacer todo desde cero.
Síntomas y causas comunes en builds de monolitos PHP
Los monolitos PHP grandes sufren por el tiempo de checkout, la instalación de dependencias y la reconstrucción repetida de artefactos. En muchos pipelines, cada job recompila todo, generando artefactos idénticos varias veces y desperdiciando caché.
Mapea rápidamente qué consume más tiempo. Casi siempre es Composer, build de assets y descarga/reconstrucción de capas Docker. Corrigiendo estos puntos, la mejora suele ser inmediata.
- Instalaciones repetidas del Composer en jobs paralelos.
- Caché inexistente o inválido en cada commit.
- Dockerfile que invalida capas demasiado pronto.
- Artefacto producido en más de un job del pipeline.
- Falta de control de concurrencia y cancelación de builds antiguos.
| Síntoma | Causa probable | Medida práctica |
|---|---|---|
| composer install tarda en todo el job | caché de Composer no persistido o clave errónea | persistir $COMPOSER_CACHE_DIR con clave basada en composer.lock |
| Docker reconstruye todo en cada commit | orden de COPY inválida; sin BuildKit ni caché remoto | reordenar Dockerfile y activar cache-from/cache-to |
| artefactos duplicados | el 'build' ocurre en múltiples stages | crear un artefacto único y hacer fan-out |
| checkout pesado | repositorio gigante; historial completo | shallow/partial clone y limpiar blobs de medios |
Diagnóstico orientado a datos del pipeline
Antes de cambiar, mide. Anota duraciones por etapa e identifica qué varía entre commits. Enfócate en los 2 o 3 bloques de tiempo más grandes y en lo que se repite sin necesidad.
Verifica también el I/O del runner. Discos lentos y almacenamiento de caché en red de alta latencia sabotean pipelines incluso si están bien configurados.
- Habilita logs detallados del Composer para ver hits/misses del caché.
- Revisa el tiempo de “checkout” y activa shallow clone.
- Separa el tiempo de build de imágenes del tiempo de tests.
- Mide la tasa de aciertos del caché por clave (commit, composer.lock).
Composer rápido y determinista (sin retrabajo)
El mayor impacto viene de manejar las dependencias de forma determinista y cacheable. Usa siempre composer.lock y flags no interactivas. Evita reinstalar vendor/ en varios jobs: produce vendor/ una vez y reutilízalo.
Persiste el directorio de caché del Composer entre jobs. Clavea el caché por el hash de composer.lock para invalidarlo solo cuando cambien las dependencias.
- Usa --prefer-dist, --no-interaction y el autoloader optimizado.
- Cachéa $COMPOSER_CACHE_DIR, no el vendor/ crudo entre branches.
- Genera vendor/ una sola vez y empaquétalo en el artefacto final.
- Los tokens de acceso a repos privados deben permanecer en secrets.
Si usas contenedores: BuildKit, capas y caché remoto
Con Docker, organiza el Dockerfile para maximizar el caché: copia composer.json y composer.lock antes del código. Instala dependencias primero. Solo después copia el resto. Activa BuildKit y caché remoto para compartir capas entre runners.
Para detalles prácticos de caché remoto y optimizaciones con BuildKit, consulta la guía específica abajo.
- Multi-stage para separar build y runtime.
- COPY en orden: archivos de dependencia antes del código.
- --mount=type=cache en Composer dentro del build.
- cache-to/cache-from para reutilizar capas entre builds.
Artefacto único: build once, fan-out para tests y deploy
Evita artefactos duplicados produciendo un único paquete versionado por commit (p. ej., zip/tar con código, vendor/ y assets). Los jobs siguientes solo consumen ese artefacto. Esto reduce variación entre entornos y elimina retrabajo.
Nombra el artefacto con el SHA del commit y, opcionalmente, con un content hash para permitir deduplicación en el almacenamiento.
- Genera el artefacto tras los checks rápidos de lint + unit.
- Ejecuta integración y e2e usando el mismo artefacto.
- El deploy consume exactamente el artefacto testeado.
- Define políticas de retención y limpieza.
Estrategia de pipeline: orden, matrices y concurrencia
Estructura la pipeline en stages claros: checkout ligero, build del artefacto, fan-out de tests, publicación y deploy. Usa matrices para paralelizar tests, nunca para el build del artefacto.
Controla la concurrencia: cancela runs obsoletos del mismo branch y limita builds simultáneos que compiten por cachés. Así evitas colas largas y duplicación innecesaria.
- Gate: solo crea el artefacto si las comprobaciones rápidas pasan.
- Usa matrix solo para tests; el build del artefacto es único.
- Converge hacia una etapa de publicación/despliegue.
- Define reglas para cancelar ejecuciones antiguas.
Higiene de caché, retención y seguridad
El caché acelera, pero sin higiene se convierte en basura. Limpia cachés al eliminar branches. Renueva claves cuando cambie composer.lock. Monitorea la tasa de aciertos y ajusta el particionado.
Presta atención a secrets y proveedores privados de paquetes. Almacena tokens de forma segura y restringe su alcance en la pipeline.
- Define TTLs distintos para branches y main.
- Evita compartir vendor/ entre branches diferentes.
- Rota tokens periódicamente y aísla permisos.
- Audita quién accede a artefactos y logs sensibles.
Preguntas frecuentes
¿Cómo evitar reinstalar el vendor/ en cada job del CI?
Genera el vendor/ una sola vez en la etapa de build y empaquétalo en el artefacto final. Los demás jobs consumen ese artefacto. Para más velocidad, persiste el caché del Composer claveado por el hash de composer.lock.
¿BuildKit ayuda incluso en proyectos PHP sin mucho C/C++?
Sí. Aunque no haya compilaciones pesadas, las capas determinísticas y el caché remoto reducen descargas y reinstalaciones. El orden correcto de COPY y la reutilización de capas del Composer suelen dar ganancias considerables.
¿Cuál es la mejor clave para el caché del Composer en CI?
Usa una clave derivada del hash de composer.lock y del sistema operativo/versión de PHP. Así, el caché solo se invalida cuando las dependencias cambian realmente, evitando roturas por plataformas distintas.
¿Cómo eliminar artefactos duplicados en pipelines paralelas?
Centraliza el build del artefacto en un único job por commit y usa ese paquete en fan-out. Nómbralo con el SHA del commit y, si es posible, con un content hash para deduplicación en storage. Cancela ejecuciones antiguas del mismo branch.
¿Debo cachear el directorio vendor/ directamente?
Mejor cachea el directorio de caché del Composer y produce el vendor/ en el artefacto único. Cachear vendor/ entre branches puede introducir inconsistencias difíciles de diagnosticar y complicar la depuración.
¿Quieres apoyo para diseñar tu pipeline?
Si necesitas revisar el CI de tu monolito PHP e implementar artefacto único, caché eficiente y BuildKit, podemos hablar sobre tu contexto y prioridades.
Hablar sobre optimización de CIVer también
- 502 Bad Gateway intermitente en Docker Compose con Nginx y PHP‑FPM: cómo diagnosticar y corregir timeouts y exceso de conexiones
- Cómo diagnosticar y capturar paquetes por intermitencias de red entre contenedores Docker en Debian
- Cómo detectar y corregir inconsistencias de SKUs duplicados entre WooCommerce y ERP sin perder pedidos
- Cómo resolver el error 'Too many open files' en Linux en servidores PHP‑FPM durante picos de carga