> ## Content Index
> Fetch the complete content index at: https://blog.ifirestone.dev/llms.txt
> Use this file to discover other available public pages before exploring further.

# Laravel con Docker Compose: cinco errores de infraestructura que vale la pena evitar
- URL: https://blog.ifirestone.dev/laravel-con-docker-compose-cinco-errores-de-infraestructura-que-vale-la-pena-evitar/
- Published: 2026-10-24T12:00:00.000Z
- Updated: 2026-10-24T12:00:00.000Z
- Description: Nombres de servicio, healthchecks de Postgres, un scheduler sin tabla de caché, colas y migraciones al desplegar: los errores que aparecen al llevar Laravel a Docker Compose y cómo evitarlos.
- Author: Ivan Firestone
- Tags: laravel, docker, devops, #spanish

> Nombres de servicio, healthchecks de Postgres, un scheduler sin tabla de caché, colas y migraciones al desplegar: los errores que aparecen al llevar Laravel a Docker Compose y cómo evitarlos.

## Resumen

Llevar una aplicación Laravel a Docker Compose parece trivial hasta que el primer despliegue falla por razones que no están en tu código. Estos son cinco errores de infraestructura que encontré depurando proyectos reales, con el síntoma, la causa y la solución.

## 1\. El nombre del servicio no coincide con `DB_HOST`

**Síntoma:** la aplicación no puede conectarse a la base de datos: `getaddrinfo failed`, `Connection refused` o `Name or service not known`.

**Causa:** en una red de Compose, los contenedores se resuelven por el **nombre del servicio**. Si tu servicio se llama `postgres` en `docker-compose.yml` pero el `.env` dice `DB_HOST=db`, no hay nada que resolver. Pasa mucho al renombrar servicios o al copiar un `.env` de otro proyecto.

**Solución:** que el valor de `DB_HOST` sea exactamente el nombre del servicio.

```yaml
services:
  postgres:
    image: postgres:16
  app:
    environment:
      DB_HOST: postgres # el nombre del servicio, no "localhost"

```

Un clásico relacionado: usar `localhost` dentro del contenedor. Dentro de un contenedor, `localhost` es ese mismo contenedor.

## 2\. Healthcheck de Postgres con credenciales distintas a las reales

**Síntoma:** el contenedor de la base de datos queda `unhealthy` y los servicios que dependen de él nunca arrancan. En los logs de Postgres: `FATAL: role "postgres" does not exist`.

**Causa:** el healthcheck usa un usuario o una base de datos distintos de los configurados en las variables de entorno. Si defines `POSTGRES_USER=laravel` y tu healthcheck ejecuta `pg_isready -U postgres`, estás comprobando contra un rol que no existe.

**Solución:** que el healthcheck use las mismas variables, y escapar el `$` para que lo resuelva el contenedor y no Compose:

```yaml
services:
  postgres:
    image: postgres:16
    environment:
      POSTGRES_USER: ${DB_USERNAME}
      POSTGRES_PASSWORD: ${DB_PASSWORD}
      POSTGRES_DB: ${DB_DATABASE}
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}"]
      interval: 5s
      timeout: 3s
      retries: 10

  app:
    depends_on:
      postgres:
        condition: service_healthy

```

Con `depends_on` y `service_healthy`, la app espera hasta que la base de datos realmente acepta conexiones, no solo hasta que el contenedor existe.

## 3\. El scheduler falla porque la tabla de caché no existe

**Síntoma:** el scheduler lanza errores relacionados con la tabla `cache` o `cache_locks`, sobre todo cuando usas tareas con `withoutOverlapping()` o `onOneServer()`.

**Causa:** con `CACHE_STORE=database`, Laravel guarda la caché y los locks en tablas. Si esas tablas no existen (la migración no se ejecutó o la base de datos es nueva), el scheduler falla al intentar adquirir un lock.

**Solución:** asegúrate de que la tabla de caché esté migrada. En Laravel 11 o superior:

```bash
php artisan make:cache-table
php artisan migrate

```

(En versiones anteriores el comando era `cache:table`.) Y ejecuta el scheduler como un proceso propio:

```bash
php artisan schedule:work

```

## 4\. Todos los procesos en un solo contenedor

**Síntoma:** en desarrollo todo funciona, y en producción las colas se detienen en silencio, los reinicios son impredecibles y no puedes escalar solo los *workers*.

**Causa:** en desarrollo es normal usar `concurrently` para levantar el servidor web, la cola y el scheduler en una sola terminal. Llevar ese patrón a producción mete varios procesos en un contenedor, y Docker solo vigila el proceso principal: si la cola muere, nadie se entera.

**Solución:** un proceso por contenedor, con la misma imagen y un comando distinto:

```yaml
services:
  app:
    image: myapp:latest
    command: php-fpm

  queue:
    image: myapp:latest
    command: php artisan queue:work --tries=3 --timeout=90
    restart: unless-stopped

  scheduler:
    image: myapp:latest
    command: php artisan schedule:work
    restart: unless-stopped

```

Ventajas: reinicios independientes, escalado de workers por separado, logs separados y señales de parada manejadas por Docker.

## 5\. Las migraciones se ejecutan en cada arranque del contenedor

**Síntoma:** al desplegar con varias réplicas aparecen errores intermitentes de migración, bloqueos en la base de datos o tablas creadas a medias.

**Causa:** poner `php artisan migrate --force` en el `entrypoint` hace que **cada** contenedor intente migrar al iniciar. Con varias réplicas, compiten entre sí.

**Solución:** las migraciones son un paso del despliegue, no del arranque de la aplicación. Ejecútalas **una sola vez**, como tarea previa en el pipeline de CI/CD o como un servicio de una sola ejecución:

```yaml
services:
  migrate:
    image: myapp:latest
    command: php artisan migrate --force
    restart: "no"
    depends_on:
      postgres:
        condition: service_healthy

```

Y haz que `app`, `queue` y `scheduler` arranquen después de que la migración termine con éxito.

## Lista de verificación rápida

- \[ \] `DB_HOST` coincide con el nombre del servicio, nunca `localhost`.
- \[ \] El healthcheck de la base de datos usa las mismas credenciales que el servicio.
- \[ \] `depends_on` usa `condition: service_healthy`.
- \[ \] La tabla de caché existe si usas `CACHE_STORE=database`.
- \[ \] Un proceso por contenedor: web, cola y scheduler separados.
- \[ \] Las migraciones se ejecutan una vez por despliegue, no en cada arranque.
- \[ \] Las imágenes tienen una versión fija, no `latest`.

## Conclusión

Casi todos estos errores tienen el mismo origen: asumir que la infraestructura se comporta como tu máquina local. Compose tiene reglas claras (resolución por nombre, orden de arranque, un proceso por contenedor). Conocerlas ahorra horas de depuración.

Si tienes un error recurrente en tu propio despliegue, compártelo en los comentarios: seguro alguien más se lo ha encontrado también.