Back

Loading…

Documentation

🔄 composer install vs composer update: cómo funciona realmente Composer en un proyecto Drupal

En los artículos anteriores vimos cómo estructurar mejor nuestro composer.json y qué funcionalidades importantes ofrece Composer más allá de declarar dependencias.

Ahora toca una de las dudas más frecuentes —y también una de las más importantes para trabajar en equipo:

¿Cuál es realmente la diferencia entre composer install y composer update?

A primera vista parecen comandos similares.

Ambos descargan paquetes.

Ambos pueden modificar nuestro directorio:

vendor/

y ambos pueden afectar nuestro proyecto Drupal.

Pero conceptualmente hacen cosas muy diferentes.

Entender esta diferencia es fundamental para conseguir:

  • 🔁 builds reproducibles;
  • 🚀 deployments seguros;
  • 👥 entornos de desarrollo alineados;
  • 🔐 actualizaciones controladas;
  • 🧹 menos sorpresas después de hacer git pull.

🧠 Primero: composer.json y composer.lock no tienen la misma función

La forma más sencilla de entender Composer es separar estos dos archivos:

composer.json
composer.lock

composer.json describe qué aceptamos

Por ejemplo:

{
  "require": {
    "drupal/core-recommended": "^11.4",
    "drupal/pathauto": "^1.13"
  }
}

Estamos diciendo aproximadamente:

Drupal Core
→ queremos una versión compatible con ^11.4

Pathauto
→ queremos una versión compatible con ^1.13

Pero no estamos diciendo necesariamente qué versión exacta debe instalarse.

Dependiendo de las versiones disponibles, Composer podría resolver esas constraints de diferentes maneras.


🔒 composer.lock describe qué hemos decidido instalar

Después de resolver las dependencias, Composer guarda las versiones concretas en:

composer.lock

Conceptualmente:

composer.json
      ↓
¿Qué versiones están permitidas?
      ↓
Composer resuelve dependencias
      ↓
composer.lock
      ↓
Estas son las versiones exactas seleccionadas

Por ejemplo, composer.json podría permitir:

drupal/pathauto: ^1.13

mientras composer.lock fija algo concreto como:

drupal/pathauto
1.x.y

junto con todas sus dependencias transitivas.

Este es uno de los motivos por los que composer.lock es tan importante en una aplicación Drupal.

🔐 composer.json expresa nuestras reglas; composer.lock registra el resultado concreto de esas reglas.


📥 ¿Qué hace composer install?

Cuando ejecutamos:

composer install

y existe composer.lock, Composer utiliza las versiones almacenadas en ese lock file.

No intenta buscar arbitrariamente versiones nuevas permitidas por composer.json.

En términos simplificados:

composer install
        ↓
lee composer.lock
        ↓
instala exactamente ese árbol de dependencias

Eso significa que si un desarrollador ha actualizado correctamente el proyecto y ha hecho commit de:

composer.json
composer.lock

otro desarrollador puede hacer:

git pull
composer install

y obtener el mismo conjunto de dependencias.

Ese comportamiento es precisamente lo que buscamos en nuestros proyectos.


🔄 ¿Qué hace entonces composer update?

composer update tiene una responsabilidad diferente.

Cuando ejecutamos:

composer update

Composer vuelve a resolver las dependencias utilizando las restricciones declaradas en:

composer.json

y las versiones actualmente disponibles.

Después escribe el nuevo resultado en:

composer.lock

Conceptualmente:

composer.json
      ↓
composer update
      ↓
buscar versiones compatibles actuales
      ↓
resolver dependencias
      ↓
actualizar composer.lock
      ↓
instalar nuevas versiones

Por eso:

composer update cambia potencialmente nuestra decisión de versiones.

Mientras:

composer install reproduce una decisión ya tomada.

Esta diferencia parece pequeña, pero cambia completamente cuándo debemos utilizar cada comando.


👨‍💻 Después de git pull: normalmente composer install

Supongamos que un compañero actualiza Drupal o instala un nuevo módulo.

Ejecuta algo como:

composer require drupal/pathauto

Composer modifica:

composer.json
composer.lock

y esos dos archivos se incluyen en el commit.

Después nosotros hacemos:

git pull

¿Qué debemos ejecutar?

Normalmente:

composer install

No:

composer update

¿Por qué?

Porque nuestro compañero ya tomó la decisión sobre el árbol de dependencias y la dejó registrada en composer.lock.

No necesitamos volver a resolverla.

Queremos reproducirla.

Compañero
composer require/update
      ↓
composer.lock cambia
      ↓
Git
      ↓
Nosotros
git pull
      ↓
composer install

Este es uno de los workflows más importantes que queremos mantener consistentes.


⚠️ ¿Qué podría pasar si hacemos composer update después de cada git pull?

Imaginemos:

composer.json

contiene:

{
  "require": {
    "vendor/package": "^2.0"
  }
}

Nuestro compañero probó el proyecto utilizando:

vendor/package 2.4

y esa versión quedó registrada en composer.lock.

Después hacemos:

git pull
composer update

Pero entretanto se publicó:

vendor/package 2.5

Como sigue siendo compatible con:

^2.0

Composer podría seleccionar esa nueva versión.

Ahora tenemos algo distinto de lo que nuestro compañero probó.

Eso rompe una propiedad muy valiosa:

mismo commit
=
mismas dependencias

Por eso composer update no debería formar parte del workflow rutinario simplemente para “sincronizar” un proyecto.


🛠️ Entonces, ¿cuándo usamos composer update?

Cuando queremos actualizar dependencias.

Por ejemplo:

composer update drupal/pathauto

o, si necesitamos actualizar también sus dependencias:

composer update drupal/pathauto --with-all-dependencies

La documentación actual de Drupal recomienda precisamente este patrón para actualizar módulos contrib.

También podemos actualizar Drupal Core mediante un comando dirigido, por ejemplo:

composer update "drupal/core-*" --with-all-dependencies

dependiendo de la estructura del proyecto. Drupal documenta actualmente este workflow para proyectos basados en core-recommended.

La idea importante es:

🎯 Preferimos actualizaciones explícitas y con intención.

En lugar de ejecutar:

composer update

sin saber qué va a cambiar, podemos actualizar exactamente aquello que queremos revisar.


🔍 Antes de actualizar: composer outdated

Composer puede decirnos qué dependencias tienen nuevas versiones disponibles:

composer outdated

Para concentrarnos en Drupal:

composer outdated "drupal/*"

Drupal incluye actualmente este comando dentro de su workflow recomendado para revisar actualizaciones.

Esto nos permite separar dos acciones:

Ver qué podría actualizarse
→ composer outdated

Decidir qué queremos actualizar
→ composer update paquete

Mucho mejor que actualizar primero y mirar qué ocurrió después.


🧪 --dry-run: mirar antes de tocar

Otra herramienta extremadamente útil es:

composer update drupal/example --with-all-dependencies --dry-run

Composer calcula lo que haría sin aplicar realmente los cambios.

Drupal recomienda también utilizar --dry-run al revisar actualizaciones.

Nos permite ver operaciones como:

Upgrading package A
Upgrading package B
Removing package C
Installing package D

antes de modificar nuestro proyecto.

Para actualizaciones sensibles, es un hábito excelente.


📦 composer require también puede actualizar

Cuando hacemos:

composer require drupal/pathauto

Composer no se limita a escribir una línea dentro de composer.json.

También:

modifica composer.json
      ↓
resuelve dependencias
      ↓
modifica composer.lock
      ↓
instala los paquetes

Por eso, cuando añadimos una nueva dependencia, normalmente debemos hacer commit de ambos:

composer.json
composer.lock

Lo mismo aplica cuando cambiamos manualmente una constraint y luego resolvemos las nuevas dependencias.


🔐 composer.lock debe formar parte de Git

Para aplicaciones como nuestros sitios Drupal, composer.lock debe mantenerse en el repositorio.

Nuestro commit debería contener algo como:

composer.json
composer.lock

y, cuando utilizamos Composer Patches 2:

patches.lock.json

también.

Esto permite que:

composer install

reconstruya el árbol de dependencias que hemos probado.

Drupal recomienda explícitamente hacer commit de composer.lock y desplegar utilizando ese archivo.


🚀 Producción: composer install, no composer update

Esta es probablemente la regla más importante de todo el artículo.

En producción queremos reproducir un build conocido.

No queremos decidir nuevas versiones.

Por eso Drupal recomienda explícitamente:

composer install --no-dev

en producción y no:

composer update

Nuestro deployment debería conceptualmente ser:

Desarrollo / CI
─────────────────────────
actualizar dependencias
↓
probar
↓
commit composer.lock
↓
Merge Request
↓
review
↓
merge

Producción
─────────────────────────
git pull / checkout release
↓
composer install --no-dev
↓
Drupal deployment

El servidor de producción no debería estar tomando decisiones sobre qué versión nueva instalar.

Esas decisiones deberían haberse tomado y probado antes.


🧪 ¿Qué significa --no-dev?

En nuestro composer.json podemos tener:

{
  "require-dev": {
    "phpunit/phpunit": "...",
    "phpstan/phpstan": "..."
  }
}

Estas herramientas son útiles durante desarrollo y CI, pero no necesariamente deben instalarse en producción.

Con:

composer install --no-dev

Composer utiliza el mismo composer.lock, pero omite los paquetes marcados como dependencias de desarrollo.

Esto permite tener:

Desarrollo
→ herramientas de testing y análisis

Producción
→ solamente runtime dependencies

Drupal también utiliza este patrón en su documentación para deployments.


🚀 --optimize-autoloader

En producción podemos además utilizar:

composer install --no-dev --optimize-autoloader

Composer optimiza la información utilizada para cargar clases PHP.

Es una opción habitual para aplicaciones desplegadas en producción.

Nuestro comando de build puede por tanto terminar siendo:

composer install \
  --no-dev \
  --optimize-autoloader

La configuración exacta puede depender de nuestra infraestructura, pero conceptualmente sigue siendo un install, no un update.


🗄️ Composer actualiza código; Drupal puede necesitar actualizar la base de datos

Hay otro detalle fundamental.

Después de:

composer install

podemos tener una nueva versión de Drupal Core o de un módulo.

Eso no significa necesariamente que el sitio esté completamente actualizado.

Un módulo puede incluir:

hook_update_N()

u otros cambios que necesitan ejecutarse contra la base de datos.

Por eso un deployment Drupal normalmente continúa con:

drush updatedb -y

y posteriormente otras operaciones necesarias.

La documentación actual de Drupal describe precisamente este workflow: primero construir el código a partir de composer.lock y después ejecutar las actualizaciones de Drupal.


⚙️ ¿Y la configuración de Drupal?

En proyectos donde utilizamos Configuration Management, también tenemos:

config/sync/

Versionado en Git.

Por eso nuestro deployment tiene realmente varias capas:

Git
↓
código + composer.lock + configuración

Composer
↓
dependencias PHP/Drupal

Drupal
↓
actualizaciones de base de datos

Config Management
↓
configuración de Drupal

Un workflow típico podría ser:

composer install --no-dev --optimize-autoloader

drush updatedb -y
drush config:import -y
drush cache:rebuild

Drupal documenta esta secuencia de deployment y señala además que los pasos posteriores a Composer pueden realizarse mediante Drush.


🪄 drush deploy: agrupar el deployment Drupal

En versiones modernas de Drush podemos simplificar parte de ese proceso con:

drush deploy -y

Drupal menciona expresamente drush deploy como una forma de agrupar los pasos posteriores del deployment y proporcionar un flujo consistente.

Esto nos permite pensar el deployment en dos bloques claros:

composer install --no-dev --optimize-autoloader
drush deploy -y

Composer se encarga del árbol de código y dependencias.

Drush se encarga del estado de Drupal.

Esa separación conceptual es muy útil.


👥 El workflow que queremos para un equipo

Podemos resumir nuestras operaciones normales así.

📥 Recibir cambios de un compañero

git pull
composer install

Después ejecutar los pasos Drupal que requiera el proyecto.


📦 Añadir una dependencia

composer require drupal/example

Revisar:

composer.json
composer.lock

Probar y hacer commit de ambos.


🔄 Actualizar una dependencia

Primero:

composer outdated "drupal/*"

Después:

composer update drupal/example --with-all-dependencies

Revisar el diff, probar y hacer commit de composer.lock.


🚀 Desplegar

composer install --no-dev --optimize-autoloader
drush deploy -y

Nunca utilizar composer update simplemente como parte rutinaria del deployment.


🧩 ¿Qué ocurre si composer.json cambia pero composer.lock no?

Esta situación merece atención.

Supongamos que alguien modifica:

"drupal/example": "^2.0"

manualmente, pero no actualiza:

composer.lock

Ahora los dos archivos pueden estar desalineados.

Cuando ejecutamos:

composer install

Composer puede advertirnos que el lock file no está actualizado respecto a composer.json.

Eso es una señal importante.

No deberíamos solucionar automáticamente el problema con:

composer update

sin entender qué cambio faltó.

Primero debemos determinar:

¿Se olvidó hacer commit de composer.lock?

¿Se modificó composer.json manualmente?

¿Realmente queríamos actualizar una dependencia?

El lock file forma parte del cambio funcional.


🚫 No editar composer.lock manualmente

composer.lock es generado por Composer.

No deberíamos modificar directamente:

version
source
dist
content-hash

para “corregir” una instalación.

La solución debe realizarse mediante comandos Composer que reconstruyan correctamente el grafo de dependencias.


🧯 ¿Y si quiero reconstruir vendor/?

Una buena propiedad de un proyecto Composer es que:

vendor/

no debería ser nuestra fuente de verdad.

Nuestra fuente de verdad es:

composer.json
composer.lock

Por eso, conceptualmente podemos eliminar:

vendor/

y ejecutar:

composer install

para reconstruirlo.

En Drupal sucede algo parecido con módulos y themes administrados por Composer: no deberíamos modificar directamente el código instalado dentro de:

web/modules/contrib/
web/themes/contrib/
vendor/

Si necesitamos modificar upstream, utilizamos mecanismos como patches.


🔐 Añadamos una comprobación más: composer audit

Después de instalar o actualizar dependencias podemos ejecutar:

composer audit

Composer revisa las dependencias contra advisories de seguridad conocidos.

La documentación actual de Drupal incluye composer audit dentro de su workflow para revisar problemas de seguridad en dependencias PHP y Drupal.

Esto puede encajar muy bien en CI:

composer install
↓
composer audit
↓
tests
↓
static analysis

🤖 CI debe reproducir, no decidir

Nuestro pipeline de CI debería comportarse igual que otro miembro del equipo:

composer install

No debería ejecutar:

composer update

porque CI debe comprobar el estado que hemos comprometido en Git.

Queremos saber:

“¿Funciona este commit con las dependencias que hemos declarado y bloqueado?”

No:

“¿Funciona este commit con cualquier versión nueva que haya aparecido esta mañana?”

La diferencia es enorme.


🧠 Una forma sencilla de recordarlo

Podemos resumirlo así:

composer require
= quiero añadir/cambiar una dependencia

composer update
= quiero tomar nuevas decisiones de versiones

composer install
= quiero reproducir las decisiones existentes

O todavía más corto:

📥 Install reproduce. 🔄 Update resuelve.Require modifica requisitos y resuelve.


🏗️ Nuestro workflow ideal

En un proyecto Drupal mantenido por un equipo queremos llegar a algo parecido a esto:

Developer A
────────────
composer update drupal/example
↓
composer.lock cambia
↓
tests
↓
commit
↓
Merge Request

            Git

Developer B
────────────
git pull
composer install

CI
────────────
composer install
tests

Production
────────────
checkout release
composer install --no-dev
drush deploy

Todos trabajan con el mismo árbol de dependencias.

Solo el desarrollador que está realizando deliberadamente una actualización ejecuta composer update.


✅ La regla que queremos recordar

Si solamente pudiéramos quedarnos con una idea de este artículo sería esta:

composer update es una operación de desarrollo. composer install es una operación de reproducción.

Utilizamos update cuando queremos cambiar nuestro árbol de dependencias.

Probamos ese cambio.

Versionamos:

composer.json
composer.lock

y después dejamos que los demás entornos utilicen:

composer install

para reproducirlo.

Ese pequeño cambio mental hace que Composer sea mucho más fácil de entender y hace nuestros proyectos mucho más predecibles.


🔜 ¿Qué podemos explorar después?

Ahora sabemos:

  • cómo estructurar composer.json;
  • qué otras herramientas ofrece;
  • cómo funciona composer.lock;
  • cuándo utilizar install, update y require;
  • y cómo llevar esas decisiones hasta producción.

Pero todavía queda otra pregunta muy práctica:

¿Cómo sabemos exactamente por qué Composer instaló una determinada versión o por qué se niega a actualizar un paquete?

Composer incluye herramientas muy potentes para investigar su propio árbol de dependencias:

composer show
composer outdated
composer why
composer why-not
composer prohibits

👉 En el próximo artículo podemos aprender a diagnosticar Composer: entender conflictos de versiones, descubrir quién requiere un paquete y resolver actualizaciones bloqueadas sin recurrir al ensayo y error.

Contents