Saltar al contenido
infraestructura / · 4 min

docker-compose de referencia para una aplicacion Laravel en produccion

Cinco servicios, cada rol en su contenedor, todo en loopback y con healthcheck. El archivo completo y el porque de cada decision.

Administrador · Desarrollador Laravel
docker-compose de referencia para una aplicacion Laravel en produccion

Este es el docker-compose.yml del que parto para cada aplicación Laravel que despliego. Cinco servicios, cada rol en su contenedor, y todo publicado en loopback.

No es un compose de desarrollo: es el de producción, y las decisiones están explicadas abajo.

El archivo

services:
  app:
    build:
      context: .
      dockerfile: docker/Dockerfile
    container_name: miapp-app
    restart: unless-stopped
    env_file: .env
    depends_on:
      db:
        condition: service_healthy
    volumes:
      - appcode:/var/www/html

  web:
    image: nginx:alpine
    container_name: miapp-web
    restart: unless-stopped
    depends_on: [app]
    ports:
      - "127.0.0.1:8082:80"
    volumes:
      - appcode:/var/www/html:ro
      - ./docker/nginx.conf:/etc/nginx/conf.d/default.conf:ro

  queue:
    build:
      context: .
      dockerfile: docker/Dockerfile
    container_name: miapp-queue
    restart: unless-stopped
    env_file: .env
    depends_on: [db]
    command: php artisan queue:work --sleep=3 --tries=3 --max-time=3600
    volumes:
      - appcode:/var/www/html

  scheduler:
    build:
      context: .
      dockerfile: docker/Dockerfile
    container_name: miapp-scheduler
    restart: unless-stopped
    env_file: .env
    depends_on: [db]
    command: php artisan schedule:work
    volumes:
      - appcode:/var/www/html

  db:
    image: postgres:17-alpine
    container_name: miapp-db
    restart: unless-stopped
    env_file: .env
    environment:
      POSTGRES_DB: ${DB_DATABASE}
      POSTGRES_USER: ${DB_USERNAME}
      POSTGRES_PASSWORD: ${DB_PASSWORD}
    ports:
      - "127.0.0.1:5433:5432"
    volumes:
      - dbdata:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U ${DB_USERNAME} -d ${DB_DATABASE}"]
      interval: 10s
      timeout: 5s
      retries: 5

volumes:
  appcode:
  dbdata:

Por qué cada decisión

Cuatro contenedores para la misma aplicación. PHP-FPM, nginx, el worker de colas y el scheduler. Comparten imagen y código, pero se reinician por separado: un worker atorado no tira el sitio, y reiniciar el sitio no mata un trabajo a la mitad.

Todos los puertos con 127.0.0.1. Es la línea más importante del archivo. Sin ese prefijo, Docker escribe reglas de iptables que se saltan el firewall y publicas tu Postgres a internet. Nadie te avisa: funciona, y también funciona para los demás.

Postgres en el 5433 del host. Para no chocar con un Postgres local en el 5432. Cuando tienes varias apps, cada una toma el suyo.

Healthcheck y service_healthy. Sin esto, la aplicación arranca antes que la base y las migraciones fallan en el primer despliegue. Con esto, espera.

Imágenes con versión fija. postgres:17-alpine, no postgres. Un up -d rutinario no debe poder traerte una versión mayor: eso se decide, no se sufre.

El código en un volumen con nombre, no en un bind mount. En producción el código sale de la imagen construida, no de la carpeta del host. nginx lo monta en solo lectura: no tiene por qué escribir en tu aplicación.

--max-time=3600 en el worker. Lo obliga a reciclarse cada hora. PHP en procesos largos acumula memoria, y un worker eterno es un fuga de memoria esperando.

El nginx que lo acompaña

server {
    listen 80;
    server_name _;
    root /var/www/html/public;
    index index.php;

    client_max_body_size 32M;

    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }

    location ~ \.php$ {
        fastcgi_pass app:9000;
        fastcgi_index index.php;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        include fastcgi_params;

        # Sin esto, las pantallas autenticadas dan 502:
        # "upstream sent too big header".
        fastcgi_buffer_size       32k;
        fastcgi_buffers           16 32k;
        fastcgi_busy_buffers_size 64k;
    }

    location ~ /\.(?!well-known).* {
        deny all;
    }
}

El último bloque niega el acceso a todo lo que empiece con punto —.env, .git— salvo .well-known, que certbot necesita.

Lo que falta y es tuyo

  • chmod 600 .env. Nace en 644 y ahí van todas tus contraseñas.
  • Contraseña distinta por stack: openssl rand -hex 24.
  • El reverse proxy del host, con el certificado por subdominio.
  • Respaldos de dbdata. Un volumen no es un respaldo.
#laravel #arquitectura #despliegue
Comentarios 0

Nadie ha comentado todavía. Estrena la sección.

Deja tu comentario
Tu correo no se publica. Reviso los comentarios antes de publicarlos.

Seguir leyendo