One place for hosting & domains

      Laravel

      Cómo configurar Laravel, Nginx y MySQL con Docker Compose


      El autor seleccionó a The FreeBSD Foundation para recibir una donación como parte del programa Write for DOnations.

      Introducción

      Durante los últimos años, Docker se ha convertido en una solución de uso frecuente para implementar aplicaciones gracias a la forma en que simplifican el funcionamiento y la implementación de aplicaciones en contenedores efímeros. Cuando se usa una pila de aplicación LEMP, por ejemplo, con PHP, Nginx, MySQL y el framework de Laravel, Docker puede simplificar considerablemente el proceso de configuración.

      Docker Compose ha simplificado aún más el proceso de desarrollo al permitir que los desarrolladores definan su infraestructura, incluidos los servicios de aplicación, las redes y los volúmenes, en un único archivo. Docker Compose ofrece una alternativa eficaz para ejecutar varios comandos docker container create y docker container run.

      A través de este tutorial, creará una aplicación web utilizando el marco Laravel con Nginx como servidor web y MySQL como base de datos; todo ello dentro de contenedores de Docker. Definirá toda la configuración de pila en un archivo docker-compose, junto con los archivos de configuración para PHP, MySQL y Nginx.

      Requisitos previos

      Antes de comenzar, necesitará lo siguiente:

      Paso 1: Descargar Laravel e instalar dependencias

      Como primer paso, obtendremos la versión más reciente de Laravel e instalaremos las dependencias del proyecto. Incluiremos Composer, el administrador de paquetes de nivel de aplicación para PHP. Instalaremos estas dependencias con Docker para evitar la instalación de Composer a nivel global.

      Primero, compruebe que se encuentre en su directorio de inicio, clone la última versión de Laravel y dispóngala en un directorio llamado laravel-app:

      • cd ~
      • git clone https://github.com/laravel/laravel.git laravel-app

      Posiciónese en el directorio laravel-app:“

      A continuación, utilice la imagen de composer de Docker a fin de montar los directorios que necesitará para su proyecto de Laravel y evitar la sobrecarga que implica instalar Composer a nivel global:

      • docker run --rm -v $(pwd):/app composer install

      Mediante los indicadores -v y --rm con docker run se crea un contenedor efímero para el cual se aplica un montaje “bind” a la lista de comandos actual antes de su eliminación. Con esto, se copiará el contenido de su directorio ~/laravel-app al contenedor y también se garantizará que la carpeta vendor creada por Composer dentro del contenedor se copie a su directorio actual.

      Como paso final, establezca permisos en el directorio del proyecto para que sea propiedad de su usuario no root:

      • sudo chown -R $USER:$USER ~/laravel-app

      Esto será importante al escribir el Dockerfile para la imagen de su aplicación en el paso 4, ya que le permitirá trabajar con el código de la aplicación y ejecutar procesos en su contenedor como usuario no root.

      Una vez establecido el código de su aplicación, puede proceder a definir sus servicios con Docker Compose.

      Paso 2: Crear el archivo de Docker Compose

      Desarrollar sus aplicaciones con Docker Compose simplifica el proceso de configuración y control de versiones de su infraestructura. Para configurar nuestra aplicación de Laravel, escribiremos un archivo docker-compose que defina nuestro servidor web, nuestra base de datos y nuestros servicios de aplicación.

      Abra el archivo:

      • nano ~/laravel-app/docker-compose.yml

      En el archivo docker-compose, definirá tres servicios: app, webserver y db. Agregue el siguiente código al archivo, asegúrese de sustituir la contraseña root de MySQL_ROOT_PASSWORD, definida como una variable de entorno bajo el servicio db, por una contraseña segura de su elección:

      ~/laravel-app/docker-compose.yml

      version: '3'
      services:
      
        #PHP Service
        app:
          build:
            context: .
            dockerfile: Dockerfile
          image: digitalocean.com/php
          container_name: app
          restart: unless-stopped
          tty: true
          environment:
            SERVICE_NAME: app
            SERVICE_TAGS: dev
          working_dir: /var/www
          networks:
            - app-network
      
        #Nginx Service
        webserver:
          image: nginx:alpine
          container_name: webserver
          restart: unless-stopped
          tty: true
          ports:
            - "80:80"
            - "443:443"
          networks:
            - app-network
      
        #MySQL Service
        db:
          image: mysql:5.7.22
          container_name: db
          restart: unless-stopped
          tty: true
          ports:
            - "3306:3306"
          environment:
            MYSQL_DATABASE: laravel
            MYSQL_ROOT_PASSWORD: your_mysql_root_password
            SERVICE_TAGS: dev
            SERVICE_NAME: mysql
          networks:
            - app-network
      
      #Docker Networks
      networks:
        app-network:
          driver: bridge
      

      Los servicios definidos aquí incluyen lo siguiente:

      • app: esta definición de servicio contiene la aplicación de Laravel y ejecuta una imagen personalizada de Docker, digitalocean.com/php, que usted definirá en el paso 4. También fija /var/www para working_dir en el contenedor.
      • webserver: esta definición de servicio obtiene la imagen nginx:alpine de Docker y expone los puertos 80 y 443.
      • db: esta definición de servicio obtiene la imagen mysql:5.7.22 de Docker y define algunas variables de entorno, incluida una base de datos llamada laravel para su aplicación y la contraseña root para la base de datos. Puede nombrar la base de datos como lo desee y debe sustituir your_mysql_root_password por su propia contraseña segura. Esta definición de servicio también asigna el puerto 3306 en el host al puerto 3306 en el contenedor.

      Cada propiedad container_name define un nombre para el contenedor, que corresponde al nombre del servicio. Si no define esta propiedad, Docker asignará un nombre a cada contenedor combinando el nombre de una persona históricamente famosa y una palabra al azar separada por un guión bajo.

      Para facilitar la comunicación entre contenedores, los servicios se conectan a una red de puente llamada app-network. Una red de puente utiliza un puente de software que permite que los contenedores conectados a la misma red de puente se comuniquen entre sí. El controlador de puente instala de forma automática reglas en la máquina host para que los contenedores de diferentes redes de puente no puedan comunicarse directamente entre sí. Esto crea un mayor nivel de seguridad para las aplicaciones y garantiza que solo los servicios relacionados puedan comunicarse entre sí. También implica que usted puede definir diferentes redes y servicios que se conectan a funciones relacionadas: los servicios de aplicaciones clientes pueden utilizar una red frontend, por ejemplo, y los servicios de servidor pueden usar una red backend.

      Veamos la forma de agregar volúmenes y montajes “bind” a sus definiciones de servicio para persistir los datos de su aplicación.

      Paso 3: Persistir datos

      Docker tiene características potentes y convenientes para la persistencia de datos. En nuestra aplicación, usaremos volúmenes y montajes bind para persistir la base de datos y los archivos de aplicación y configuración. Los volúmenes ofrecen flexibilidad para los respaldos y la persistencia más allá del ciclo de vida de un contenedor, mientras que los montajes “bind” facilitan los cambios de código durante el desarrollo y realizan cambios en los archivos host o directorios disponibles de inmediato en sus contenedores. En nuestra configuración usaremos ambas opciones.

      Advertencia: Al usar de montajes “bind”, permite cambiar el sistema de archivos host a través de procesos que se ejecutan en un contenedor. Se incluye la creación, modificación o eliminación de archivos o directorios importantes del sistema. Esta es una poderosa capacidad que tiene consecuencias para la seguridad y podría afectar a los procesos no Docker del sistema host. Use los montajes “bind” con cuidado.

      En el archivo docker-compose, defina un volumen llamado dbdata bajo la definición de servicio db para persistir la base de datos de MySQL:

      ~/laravel-app/docker-compose.yml

      ...
      #MySQL Service
      db:
        ...
          volumes:
            - dbdata:/var/lib/mysql
          networks:
            - app-network
        ...
      

      El volumen llamado dbdata persiste el contenido de la carpeta /var/lib/mysql situada dentro del contenedor. Esto le permite detener y reiniciar el servicio db sin perder datos.

      En la parte inferior del archivo, agregue la definición para el volumen dbdata:

      ~/laravel-app/docker-compose.yml

      ...
      #Volumes
      volumes:
        dbdata:
          driver: local
      

      Una vez implementada esta definición, podrá utilizar este volumen en todos los servicios.

      A continuación, agregue un montaje “bind” al servicio db para los archivos de configuración de MySQL que creará en el paso 7:

      ~/laravel-app/docker-compose.yml

      ...
      #MySQL Service
      db:
        ...
          volumes:
            - dbdata:/var/lib/mysql
            - ./mysql/my.cnf:/etc/mysql/my.cnf
        ...
      

      Este montaje “bind” vincula ~/laravel-app/mysql/my.cnf a /etc/mysql/my.cnf en el contenedor.

      A continuación, agregue montajes “bind” al servicio webserver. Habrá dos: uno para el código de su aplicación y otro para la definición de configuración de Nginx que creará en el paso 6:

      ~/laravel-app/docker-compose.yml

      #Nginx Service
      webserver:
        ...
        volumes:
            - ./:/var/www
            - ./nginx/conf.d/:/etc/nginx/conf.d/
        networks:
            - app-network
      

      El primer montaje “bind” vincula el código de la aplicación situado en el directorio ~/laravel-app al directorio /var/www dentro del contenedor. El archivo de configuración que agregará a ~/laravel-app/nginx/conf.d/ también se montará a /etc/nginx/conf.d/ en el contenedor, lo que le permitirá agregar o modificar el contenido del directorio de configuración cuando sea necesario.

      Por último, agregue los siguientes montajes “bind” al servicio app para el código de la aplicación y los archivos de configuración:

      ~/laravel-app/docker-compose.yml

      #PHP Service
      app:
        ...
        volumes:
             - ./:/var/www
             - ./php/local.ini:/usr/local/etc/php/conf.d/local.ini
        networks:
            - app-network
      

      El servicio app vincula mediante montaje “bind” la carpeta ~/laravel-app, que contiene el código de la aplicación, a la carpeta /var/www en el contenedor. Esto acelerará el proceso de desarrollo, ya que los cambios realizados en el directorio de su aplicación local se reflejarán de inmediato dentro del contenedor. También vinculará su archivo de configuración PHP, ~/laravel-app/php/local.ini, a /usr/local/etc/php/conf.d/local.ini, dentro del contenedor. Creará el archivo de configuración de PHP local en el paso 5.

      Ahora, su archivo docker-compose tendrá el siguiente aspecto:

      ~/laravel-app/docker-compose.yml

      version: '3'
      services:
      
        #PHP Service
        app:
          build:
            context: .
            dockerfile: Dockerfile
          image: digitalocean.com/php
          container_name: app
          restart: unless-stopped
          tty: true
          environment:
            SERVICE_NAME: app
            SERVICE_TAGS: dev
          working_dir: /var/www
          volumes:
            - ./:/var/www
            - ./php/local.ini:/usr/local/etc/php/conf.d/local.ini
          networks:
            - app-network
      
        #Nginx Service
        webserver:
          image: nginx:alpine
          container_name: webserver
          restart: unless-stopped
          tty: true
          ports:
            - "80:80"
            - "443:443"
          volumes:
            - ./:/var/www
            - ./nginx/conf.d/:/etc/nginx/conf.d/
          networks:
            - app-network
      
        #MySQL Service
        db:
          image: mysql:5.7.22
          container_name: db
          restart: unless-stopped
          tty: true
          ports:
            - "3306:3306"
          environment:
            MYSQL_DATABASE: laravel
            MYSQL_ROOT_PASSWORD: your_mysql_root_password
            SERVICE_TAGS: dev
            SERVICE_NAME: mysql
          volumes:
            - dbdata:/var/lib/mysql/
            - ./mysql/my.cnf:/etc/mysql/my.cnf
          networks:
            - app-network
      
      #Docker Networks
      networks:
        app-network:
          driver: bridge
      #Volumes
      volumes:
        dbdata:
          driver: local
      

      Guarde el archivo y cierre su editor cuando termine de realizar cambios.

      Ahora que su archivo docker-compose está escrito, podrá crear la imagen personalizada para su aplicación.

      Paso 4: Crear el Dockerfile

      Docker le permite especificar el entorno dentro de contenedores individuales con un Dockerfile. Un Dockerfile le permite crear imágenes personalizadas que puede emplear para instalar el software requerido por su aplicación y configurar los ajustes según sus requisitos. Puede introducir en Docker Hub o en cualquier registro privado las imágenes personalizadas que cree.

      Nuestro Dockerfile se ubicará en nuestro directorio ~/laravel-app. Cree el archivo:

      • nano ~/laravel-app/Dockerfile

      Este Dockerfile establecerá la imagen de base y especificará los comandos y las instrucciones que se necesitan para crear la imagen de aplicación de Laravel. Agregue el siguiente código al archivo:

      ~/laravel-app/php/Dockerfile

      FROM php:7.2-fpm
      
      # Copy composer.lock and composer.json
      COPY composer.lock composer.json /var/www/
      
      # Set working directory
      WORKDIR /var/www
      
      # Install dependencies
      RUN apt-get update && apt-get install -y 
          build-essential 
          mysql-client 
          libpng-dev 
          libjpeg62-turbo-dev 
          libfreetype6-dev 
          locales 
          zip 
          jpegoptim optipng pngquant gifsicle 
          vim 
          unzip 
          git 
          curl
      
      # Clear cache
      RUN apt-get clean && rm -rf /var/lib/apt/lists/*
      
      # Install extensions
      RUN docker-php-ext-install pdo_mysql mbstring zip exif pcntl
      RUN docker-php-ext-configure gd --with-gd --with-freetype-dir=/usr/include/ --with-jpeg-dir=/usr/include/ --with-png-dir=/usr/include/
      RUN docker-php-ext-install gd
      
      # Install composer
      RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer
      
      # Add user for laravel application
      RUN groupadd -g 1000 www
      RUN useradd -u 1000 -ms /bin/bash -g www www
      
      # Copy existing application directory contents
      COPY . /var/www
      
      # Copy existing application directory permissions
      COPY --chown=www:www . /var/www
      
      # Change current user to www
      USER www
      
      # Expose port 9000 and start php-fpm server
      EXPOSE 9000
      CMD ["php-fpm"]
      

      Primero, el Dockerfile crea una imagen en la parte superior de la imagen de php:7.2-fpm Docker. Esta es una imagen basada en Debian que tiene instalada la implementación PHP-FPM de PHP FastCGI. El archivo también instala con composer los paquetes previos para Laravel: mcrypt, pdo_mysql, mbstring e imagick.

      La directiva RUN especifica los comandos para actualizar, instalar y configurar los ajustes dentro del contenedor. Esto incluye la creación de un usuario dedicado y un grupo llamado www. La instrucción WORKDIR especifica el directorio /var/www como directorio de trabajo para la aplicación.

      Crear un usuario dedicado y un grupo con permisos restringidos mitiga la vulnerabilidad inherente al ejecutar contenedores de Docker, que se funcionan por defecto como root. En lugar de ejecutar este contenedor como root, creamos el usuario** www**, que tiene acceso de lectura y escritura a la carpeta /var/www gracias a la instrucción COPY que usaremos con el indicador --chown para copiar los permisos de la carpeta de la aplicación.

      Por último, el comando EXPOSE expone un puerto del contenedor, el 9000, para el servidor php-fpm. CMD especifica el comando que debe ejecutarse una vez que se cree el contenedor. Aquí, el CMD especifica “php-fpm”, que iniciará el servidor.

      Guarde el archivo y cierre su editor cuando termine de realizar cambios.

      Ahora podrá definir su configuración de PHP.

      Paso 5: Configurar PHP

      Ahora que definió su infraestructura en el archivo docker-compose, puede configurar el servicio PHP para que funciones como procesador PHP para solicitudes entrantes de Nginx.

      Para configurar PHP, creará el archivo local.ini dentro de la carpeta php. Este es el archivo que vinculó mediante montaje “bind” a /usr/local/etc/php/conf.d/local.ini dentro del contenedor en el paso 2. Crear este archivo le permitirá anular el archivo php.ini predeterminado que PHP lee al iniciarse.

      Cree el directorio php:

      A continuación, abra el archivo local.ini:

      • nano ~/laravel-app/php/local.ini

      Con el propósito de demostrar cómo configurar PHP, agregaremos el siguiente código para establecer limitaciones de tamaño para archivos cargados:

      ~/laravel-app/php/local.ini

      upload_max_filesize=40M
      post_max_size=40M
      

      Las directivas upload_max_filesize y post_max_size establecen el tamaño máximo permitido para los archivos cargados y demuestran la forma en que puede configurar parámetros php.ini desde su archivo local.ini. Puede disponer cualquier configuración específica de PHP que desee anular en el archivo local.ini.

      Guarde el archivo y cierre el editor.

      Una vez preparado su archivo PHP local.ini, podrá configurar Nginx.

      Paso 6: Configurar Nginx

      Una vez configurado el servicio PHP, podrá modificar el servicio Nginx para usar PHP-FPM como servidor de FastCGI para proporcionar contenido dinámico. El servidor FastCGI se basa en un protocolo binario para interconectar programas interactivos con un servidor web. Para obtener más información, consulte este artículo sobre Comprensión e implementación de proxy de FastCGI en Nginx.

      Para configurar Nginx, creará un archivo app.conf con la configuración del servicio en la carpeta ~/laravel-app/nginx/conf.d/.

      Primero, cree el directorio nginx/conf.d/:

      • mkdir -p ~/laravel-app/nginx/conf.d

      Luego, cree el archivo de configuración app.conf:

      • nano ~/laravel-app/nginx/conf.d/app.conf

      Agregue el siguiente código al archivo para especificar su configuración de Nginx:

      ~/laravel-app/nginx/conf.d/app.conf

      server {
          listen 80;
          index index.php index.html;
          error_log  /var/log/nginx/error.log;
          access_log /var/log/nginx/access.log;
          root /var/www/public;
          location ~ .php$ {
              try_files $uri =404;
              fastcgi_split_path_info ^(.+.php)(/.+)$;
              fastcgi_pass app:9000;
              fastcgi_index index.php;
              include fastcgi_params;
              fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
              fastcgi_param PATH_INFO $fastcgi_path_info;
          }
          location / {
              try_files $uri $uri/ /index.php?$query_string;
              gzip_static on;
          }
      }
      

      El bloque de servidor define la configuración para el servidor web de Nginx con las siguientes directivas:

      • listen: esta directiva define el puerto en el cual el servidor escuchará las solicitudes entrantes.
      • error_log y access_log: estas directivas definen los archivos para escribir registros.
      • root: esta directiva establece la ruta de la carpeta root y forma la ruta para cualquier archivo solicitado en el sistema de archivos local.

      En el bloque de ubicación php, la directiva fastcgi_pass especifica que el servicio de app escuchando en un socket TCP del puerto 9000. Esto hace que el servidor PHP-FPM escuche a través de la red en lugar de hacerlo en un socket de Unix. Aunque un socket de Unix tiene una ligera ventaja de velocidad con respecto a un socket de TCP, no cuenta con un protocolo de red y, por lo tanto, omite la pila de red. Para los casos en los cuales los hosts están ubicados en una máquina, puede tener sentido la presencia de un socket de Unix. Sin embargo, en los casos en los que tenga servicios ejecutándose en diferentes hosts, un socket de TCP le ofrece la ventaja de permitirle conectarse a servicios distribuidos. Debido a que nuestro contenedor app se ejecuta en un host diferente del de nuestro contenedor webserver, un socket de TCP es la opción que más sentido tiene para nuestro tipo de configuración.

      Guarde el archivo y cierre su editor cuando termine de realizar cambios.

      Gracias al montaje “bind” mount que creó en el paso 2, cualquier cambio que realice dentro de la carpeta nginx/conf.d/ se reflejará directamente dentro del contenedor webserver.

      A continuación, observaremos nuestras configuraciones de MySQL.

      Paso 7: Configurar MySQL

      Una vez configurados PHP y Nginx, podrá habilitar MySQL para que actúe como base de datos para su aplicación.

      Para configurar MySQL, creará el archivo my.cnf en la carpeta mysql. Este es el archivo que vinculó mediante montaje “bind” a /etc/mysql/my.cnf dentro del contenedor en el paso 2. Este montaje “bind” le permite anular los ajustes de my.cnf según sea necesario.

      Para demostrar cómo funciona esto, agregaremos al archivo my.cnf ajustes que habiliten el registro general de consulta y especifiquen el archivo de registro.

      Primero, cree el directorio mysql:

      • mkdir ~/laravel-app/mysql

      A continuación, cree el archivo my.cnf:

      • nano ~/laravel-app/mysql/my.cnf

      En el archivo, agregue el siguiente código para habilitar el registro de consulta y establecer la ubicación del archivo de registro:

      ~/laravel-app/mysql/my.cnf

      [mysqld]
      general_log = 1
      general_log_file = /var/lib/mysql/general.log
      

      Este archivo my.cnf habilita los registros y define la configuración de general_log con el valor 1 para permitir registros generales. La configuración general_log_file especifica dónde se almacenarán los registros.

      Guarde el archivo y cierre el editor.

      Nuestro siguiente paso será iniciar los contenedores.

      Paso 8: Ejecutar los contenedores y modificar las preferencias de entorno

      Ahora que definió todos sus servicios en su archivo docker-compose y creó los archivos de configuración para estos servicios, puede iniciar los contenedores. Sin embargo, como paso final, crearemos una copia del archivo .env.example que Laravel incluye por defecto y daremos a la copia el nombre .env, que corresponde al archivo que Laravel prevé que definirá su entorno:

      Configuraremos los detalles específicos de nuestros ajustes en este archivo una vez que iniciemos los contenedores.

      Una vez definidos todos sus servicios en su archivo docker-compose, solo deberá emitir un comando para iniciar todos los contenedores, crear los volúmenes y configurar y conectar las redes:

      Cuando ejecute docker-compose por primera vez, descargará todas las imágenes de Docker necesarias, lo cual podría tardar un tiempo. Una vez que las imágenes se descarguen y se almacenen en su máquina local, Compose creará sus contenedores. El indicador -d agrega un demonio al proceso y ejecuta sus contenedores en segundo plano.

      Una vez que el proceso esté completo, use el siguiente comando para enumerar todos los contenedores en ejecución:

      Verá el siguiente resultado con detalles sobre sus contenedores app, webserver y db:

      Output

      CONTAINER ID NAMES IMAGE STATUS PORTS c31b7b3251e0 db mysql:5.7.22 Up 2 seconds 0.0.0.0:3306->3306/tcp ed5a69704580 app digitalocean.com/php Up 2 seconds 9000/tcp 5ce4ee31d7c0 webserver nginx:alpine Up 2 seconds 0.0.0.0:80->80/tcp, 0.0.0.0:443->443/tcp

      CONTAINER ID en este resultado es un identificador único para cada contenedor, mientras que NAMES enumera el nombre de servicio asociado con cada una. Puede usar ambos identificadores para acceder a los contenedores. IMAGE define el nombre de imagen para cada contenedor, mientras que STATUS proporciona información sobre el su estado: en ejecución, en proceso de reinicio o detenido.

      Ahora puede modificar el archivo .env en el contenedor app para incluir detalles específicos sobre su configuración.

      Abra el archivo usando docker-compose exec, que permite ejecutar comandos específicos en contenedores. En este caso, abrirá el archivo para editar lo siguiente:

      • docker-compose exec app nano .env

      Busque el bloque que especifica DB_CONNECTION y actualícelo para reflejar los detalles de su configuración. Cambiará los siguientes campos:

      • DB_HOST será su contenedor de base de datos db.
      • `DB_DATABASE será la base de datos laravel.
      • `DB_USERNAME será el nombre de usuario que usará para su base de datos. En este caso, usaremos laraveluser.
      • DB_PASSWORD será la contraseña segura que desee utilizar para esta cuenta de usuario.

      /var/www/.env

      DB_CONNECTION=mysql
      DB_HOST=db
      DB_PORT=3306
      DB_DATABASE=laravel
      DB_USERNAME=laraveluser
      DB_PASSWORD=your_laravel_db_password
      

      Guarde sus cambios y cierre el editor.

      A continuación, establezca la clave de aplicación para la aplicación de Laravel con el comando php artisan key:generate. Este comando generará una clave, dispondrá una copia de esta en su archivo .env y se asegurará de que las sesiones y los datos cifrados de sus usuarios permanezcan seguros:

      • docker-compose exec app php artisan key:generate

      Con esto, dispondrá de los ajustes de entorno necesarios para ejecutar su aplicación. Para almacenar en caché estos ajustes en un archivo, lo cual aumentará la velocidad de carga de su aplicación, ejecute lo siguiente:

      • docker-compose exec app php artisan config:cache

      Sus ajustes de configuración se cargarán en /var/www/bootstrap/cache/config.php en el contenedor.

      Como paso final, visite http://your_server_ip en el navegador. Verá la siguiente página de inicio para su aplicación Laravel:

      Página de inicio de Laravel

      Con sus contenedores en ejecución y la información de su configuración lista, podrá configurar la información de usuario para la base de datos laravel en el contenedor db.

      Paso 9: Crear un usuario para MySQL

      La instalación predeterminada de MySQL solo crea la cuenta administrativa root, que tiene privilegios ilimitados en el servidor de base de datos. Por lo general, es mejor evitar el uso de la cuenta administrativa root al interactuar con la base de datos. En su lugar, crearemos un usuario de base de datos dedicado para la base de datos de nuestra aplicación de Laravel.

      Para crear un nuevo usuario, ejecute un shell bash interactivo en el contenedor db con docker-compose exec:

      • docker-compose exec db bash

      Dentro del contenedor, inicie sesión en la cuenta administrativa root de MySQL:

      Se le solicitará la contraseña que estableció para la cuenta root de MySQL durante la instalación en su archivo docker-compose.

      Comience revisando la base de datos llamada laravel, que definió en su archivo docker-compose. Ejecute el comando show databases para verificar las bases de datos existentes:”“”

      Verá la base de datos laravel en el resultado:“

      Output

      +--------------------+ | Database | +--------------------+ | information_schema | | laravel | | mysql | | performance_schema | | sys | +--------------------+ 5 rows in set (0.00 sec)

      A continuación, cree la cuenta de usuario que tendrá permisos de acceso a esta base de datos. Nuestro nombre de usuario será laraveluser, aunque puede cambiarlo por otro que prefiera. Asegúrese de que su nombre de usuario y contraseña aquí coincidan con la información que estableció en su archivo .env en el paso anterior:”“

      • GRANT ALL ON laravel.* TO 'laraveluser'@'%' IDENTIFIED BY 'your_laravel_db_password';

      Elimine los privilegios para notificar los cambios al servidor MySQL:

      Cierre MySQL:

      Por último, cierre el contenedor:

      Con esto, habrá configurado la cuenta de usuario para la base de datos de su aplicación de Laravel y estará listo para migrar sus datos y trabajar con la consola Tinker.

      Paso 10: Migrar datos y trabajar con la consola Tinker

      Con su aplicación en ejecución, podrá migrar sus datos y experimentar con el comando tinker, que iniciará una consola PsySH con Laravel precargada. PsySH es una consola para desarrolladores de tiempo de ejecución y un depurador interactivo para PHP, y Tinker es un REPL específico para Laravel. Usar el comando tinker le permitirá interactuar con su aplicación de Laravel desde la línea de comandos en un shell interactivo.

      Primero, pruebe la conexión con MySQL ejecutando el comando Laravel artisan migrate, que crea una tabla migrations en la base de datos dentro del contenedor:

      • docker-compose exec app php artisan migrate

      Con este comando se migrarán las tablas predeterminadas de Laravel. El resultado que confirme la migración tendrá este aspecto:

      Output

      Migration table created successfully. Migrating: 2014_10_12_000000_create_users_table Migrated: 2014_10_12_000000_create_users_table Migrating: 2014_10_12_100000_create_password_resets_table Migrated: 2014_10_12_100000_create_password_resets_table

      Una vez que la migración esté completa, podrá ejecutar una consulta para verificar si su conexión a la base de datos es correcta usando el comando tinker:

      • docker-compose exec app php artisan tinker

      Pruebe la conexión de MySQL obteniendo los datos que acaba de migrar:

      • DB::table('migrations')->get();

      Verá un resultado similar a este:

      Output

      => IlluminateSupportCollection {#2856 all: [ {#2862 +"id": 1, +"migration": "2014_10_12_000000_create_users_table", +"batch": 1, }, {#2865 +"id": 2, +"migration": "2014_10_12_100000_create_password_resets_table", +"batch": 1, }, ], }

      Puede usar tinker para interactuar con sus bases de datos y experimentar con servicios y modelos.

      Una vez implementada su aplicación de Laravel, estará listo para seguir adelante con el desarrollo y la experimentación.

      Conclusión

      Con esto, dispondrá de una aplicación de pila LEMP en ejecución en su servidor, que probó accediendo a la página de bienvenida de Laravel y creando migraciones de base de datos de MySQL.

      La clave de la simplicidad en esta instalación es Docker Compose, que le permite crear un grupo de contenedores de Docker definidos en un solo archivo mediante un comando. Si desea obtener más información sobre cómo realizar una integración continua con Docker Compose, consulte Cómo configurar un entorno de prueba de integración continua con Docker y Docker Compose en Ubuntu 16.04. Si desea simplificar su proceso de implementación de la aplicación de Laravel, resultará pertinente el recurso Cómo implementar de forma automática aplicaciones de Laravel con Deployer en Ubuntu 16.04.



      Source link

      How to Set Up a Scalable Laravel 6 Application using Managed Databases and Object Storage


      Introduction

      When scaling web applications horizontally, the first difficulties you’ll typically face are dealing with file storage and data persistence. This is mainly due to the fact that it is hard to maintain consistency of variable data between multiple application nodes; appropriate strategies must be in place to make sure data created in one node is immediately available to other nodes in a cluster.

      A practical way of solving the consistency problem is by using managed databases and object storage systems. The first will outsource data persistence to a managed database, and the latter will provide a remote storage service where you can keep static files and variable content such as images uploaded by users. Each node can then connect to these services at the application level.

      The following diagram demonstrates how such a setup can be used for horizontal scalability in the context of PHP applications:

      Laravel at scale diagram

      In this guide, we will update an existing Laravel 6 application to prepare it for horizontal scalability by connecting it to a managed MySQL database and setting up an S3-compatible object store to save user-generated files. By the end, you will have a travel list application running on an Nginx + PHP-FPM web server:

      Travellist v1.0

      Note: this guide uses DigitalOcean Managed MySQL and Spaces to demonstrate a scalable application setup using managed databases and object storage. The instructions contained here should work in a similar way for other service providers.

      Prerequisites

      To begin this tutorial, you will first need the following prerequisites:

      • Access to an Ubuntu 18.04 server as a non-root user with sudo privileges, and an active firewall installed on your server. To set these up, please refer to our Initial Server Setup Guide for Ubuntu 18.04.
      • Nginx and PHP-FPM installed and configured on your server, as explained in steps 1 and 3 of How to Install LEMP on Ubuntu 18.04. You should skip the step where MySQL is installed.
      • Composer installed on your server, as explained in steps 1 and 2 of How to Install and Use Composer on Ubuntu 18.04.
      • Admin credentials to a managed MySQL 8 database. For this guide, we’ll be using a DigitalOcean Managed MySQL cluster, but the instructions here should work similarly for other managed database services.
      • A set of API keys with read and write permissions to an S3-compatible object storage service. In this guide, we’ll use DigitalOcean Spaces, but you are free to use a provider of your choice.
      • The s3cmd tool installed and configured to connect to your object storage drive. For instructions on how to set this up for DigitalOcean Spaces, please refer to our product documentation.

      Step 1 — Installing the MySQL 8 Client

      The default Ubuntu apt repositories come with the MySQL 5 client, which is not compatible with the MySQL 8 server we’ll be using in this guide. To install the compatible MySQL client, we’ll need to use the MySQL APT Repository provided by Oracle.

      Begin by navigating to the MySQL APT Repository page in your web browser. Find the Download button in the lower-right corner and click through to the next page. This page will prompt you to log in or sign up for an Oracle web account. You can skip that and instead look for the link that says No thanks, just start my download. Copy the link address and go back to your terminal window.

      This link should point to a .deb package that will set up the MySQL APT Repository in your server. After installing it, you’ll be able to use apt to install the latest releases of MySQL. We’ll use curl to download this file into a temporary location.

      Go to your server’s tmp folder:

      Now download the package with curl and using the URL you copied from the MySQL APT Repository page:

      • curl -OL https://dev.mysql.com/get/mysql-apt-config_0.8.13-1_all.deb

      After the download is finished, you can use dpkg to install the package:

      • sudo dpkg -i mysql-apt-config_0.8.13-1_all.deb

      You will be presented with a screen where you can choose which MySQL version you’d like to select as default, as well as which MySQL components you’re interested in:

      MySQL APT Repository Install

      You don’t need to change anything here, because the default options will install the repositories we need. Select “Ok” and the configuration will be finished.

      Next, you’ll need to update your apt cache with:

      Now we can finally install the MySQL 8 client with:

      • sudo apt install mysql-client

      Once that command finishes, check the software version number to ensure that you have the latest release:

      You’ll see output like this:

      Output

      mysql Ver 8.0.18 for Linux on x86_64 (MySQL Community Server - GPL)

      In the next step, we’ll use the MySQL client to connect to your managed MySQL server and prepare the database for the application.

      Step 2 — Creating a new MySQL User and Database

      At the time of this writing, the native MySQL PHP library mysqlnd doesn’t support caching_sha2_authentication, the default authentication method for MySQL 8. We’ll need to create a new user with the mysql_native_password authentication method in order to be able to connect our Laravel application to the MySQL 8 server. We’ll also create a dedicated database for our demo application.

      To get started, log into your server using an admin account. Replace the highlighted values with your own MySQL user, host, and port:

      • mysql -u MYSQL_USER -p -h MYSQL_HOST -P MYSQL_PORT

      When prompted, provide the admin user’s password. After logging in, you will have access to the MySQL 8 server command line interface.

      First, we’ll create a new database for the application. Run the following command to create a new database named travellist:

      • CREATE DATABASE travellist;

      Next, we’ll create a new user and set a password, using mysql_native_password as default authentication method for this user. You are encouraged to replace the highlighted values with values of your own, and to use a strong password:

      • CREATE USER "http://www.digitalocean.com/travellist-user'@'%' IDENTIFIED WITH mysql_native_password BY "http://www.digitalocean.com/MYSQL_PASSWORD';

      Now we need to give this user permission over our application database:

      • GRANT ALL ON travellist.* TO "http://www.digitalocean.com/travellist-user'@'%';

      You can now exit the MySQL prompt with:

      You now have a dedicated database and a compatible user to connect from your Laravel application. In the next step, we’ll get the application code and set up configuration details, so your app can connect to your managed MySQL database.

      In this guide, we’ll use Laravel Migrations and database seeds to set up our application tables. If you need to migrate an existing local database to a DigitalOcean Managed MySQL database, please refer to our documentation on How to Import MySQL Databases into DigitalOcean Managed Databases.

      Step 3 — Setting Up the Demo Application

      To get started, we’ll fetch the demo Laravel application from its Github repository. Feel free to inspect the contents of the application before running the next commands.

      The demo application is a travel bucket list app that was initially developed in our guide on How to Install and Configure Laravel with LEMP on Ubuntu 18.04. The updated app now contains visual improvements including travel photos that can be uploaded by a visitor, and a world map. It also introduces a database migration script and database seeds to create the application tables and populate them with sample data, using artisan commands.

      To obtain the application code that is compatible with this tutorial, we’ll download the 1.1 release from the project’s repository on Github. We’ll save the downloaded zip file as travellist.zip inside our home directory:

      • cd ~
      • curl -L https://github.com/do-community/travellist-laravel-demo/archive/1.1.zip -o travellist.zip

      Now, unzip the contents of the application and rename its directory with:

      • unzip travellist.zip
      • mv travellist-laravel-demo-1.1 travellist

      Navigate to the travellist directory:

      Before going ahead, we’ll need to install a few PHP modules that are required by the Laravel framework, namely: php-xml, php-mbstring, php-xml and php-bcmath. To install these packages, run:

      • sudo apt install unzip php-xml php-mbstring php-xml php-bcmath

      To install the application dependencies, run:

      You will see output similar to this:

      Output

      Loading composer repositories with package information Installing dependencies (including require-dev) from lock file Package operations: 80 installs, 0 updates, 0 removals - Installing doctrine/inflector (v1.3.0): Downloading (100%) - Installing doctrine/lexer (1.1.0): Downloading (100%) - Installing dragonmantank/cron-expression (v2.3.0): Downloading (100%) - Installing erusev/parsedown (1.7.3): Downloading (100%) ... Generating optimized autoload files > IlluminateFoundationComposerScripts::postAutoloadDump > @php artisan package:discover --ansi Discovered Package: beyondcode/laravel-dump-server Discovered Package: fideloper/proxy Discovered Package: laravel/tinker Discovered Package: nesbot/carbon Discovered Package: nunomaduro/collision Package manifest generated successfully.

      The application dependencies are now installed. Next, we’ll configure the application to connect to the managed MySQL Database.

      Creating the .env configuration file and setting the App Key

      We’ll now create a .env file containing variables that will be used to configure the Laravel application in a per-environment basis. The application includes an example file that we can copy and then modify its values to reflect our environment settings.

      Copy the .env.example file to a new file named .env:

      Now we need to set the application key. This key is used to encrypt session data, and should be set to a unique 32 characters-long string. We can generate this key automatically with the artisan tool:

      Let’s edit the environment configuration file to set up the database details. Open the .env file using your command line editor of choice. Here, we will be using nano:

      Look for the database credentials section. The following variables need your attention:

      DB_HOST: your managed MySQL server host.
      DB_PORT: your managed MySQL server port.
      DB_DATABASE: the name of the application database we created in Step 2.
      DB_USERNAME: the database user we created in Step 2.
      DB_PASSWORD: the password for the database user we defined in Step 2.

      Update the highlighted values with your own managed MySQL info and credentials:

      ...
      DB_CONNECTION=mysql
      DB_HOST=MANAGED_MYSQL_HOST
      DB_PORT=MANAGED_MYSQL_PORT
      DB_DATABASE=MANAGED_MYSQL_DB
      DB_USERNAME=MANAGED_MYSQL_USER
      DB_PASSWORD=MANAGED_MYSQL_PASSWORD
      ...
      

      Save and close the file by typing CTRL+X then Y and ENTER when you’re done editing.

      Now that the application is configured to connect to the MySQL database, we can use Laravel’s command line tool artisan to create the database tables and populate them with sample data.

      Migrating and populating the database

      We’ll now use Laravel Migrations and database seeds to set up the application tables. This will help us determine if our database configuration works as expected.

      To execute the migration script that will create the tables used by the application, run:

      You will see output similar to this:

      Output

      Migration table created successfully. Migrating: 2019_09_19_123737_create_places_table Migrated: 2019_09_19_123737_create_places_table (0.26 seconds) Migrating: 2019_10_14_124700_create_photos_table Migrated: 2019_10_14_124700_create_photos_table (0.42 seconds)

      To populate the database with sample data, run:

      You will see output like this:

      Output

      Seeding: PlacesTableSeeder Seeded: PlacesTableSeeder (0.86 seconds) Database seeding completed successfully.

      The application tables are now created and populated with sample data.

      To finish the application setup, we also need to create a symbolic link to the public storage folder that will host the travel photos we’re using in the application. You can do that using the artisan tool:

      Output

      The [public/storage] directory has been linked.

      This will create a symbolic link inside the public directory pointing to storage/app/public, where we’ll save the travel photos. To check that the link was created and where it points to, you can run:

      You’ll see output like this:

      Output

      total 36 drwxrwxr-x 5 sammy sammy 4096 Oct 25 14:59 . drwxrwxr-x 12 sammy sammy 4096 Oct 25 14:58 .. -rw-rw-r-- 1 sammy sammy 593 Oct 25 06:29 .htaccess drwxrwxr-x 2 sammy sammy 4096 Oct 25 06:29 css -rw-rw-r-- 1 sammy sammy 0 Oct 25 06:29 favicon.ico drwxrwxr-x 2 sammy sammy 4096 Oct 25 06:29 img -rw-rw-r-- 1 sammy sammy 1823 Oct 25 06:29 index.php drwxrwxr-x 2 sammy sammy 4096 Oct 25 06:29 js -rw-rw-r-- 1 sammy sammy 24 Oct 25 06:29 robots.txt lrwxrwxrwx 1 sammy sammy 41 Oct 25 14:59 storage -> /home/sammy/travellist/storage/app/public -rw-rw-r-- 1 sammy sammy 1194 Oct 25 06:29 web.config

      Running the test server (optional)

      You can use the artisan serve command to quickly verify that everything is set up correctly within the application, before having to configure a full-featured web server like Nginx to serve the application for the long term.

      We’ll use port 8000 to temporarily serve the application for testing. If you have the UFW firewall enabled on your server, you should first allow access to this port with:

      Now, to run the built in PHP server that Laravel exposes through the artisan tool, run:

      • php artisan serve --host=0.0.0.0 --port=8000

      This command will block your terminal until interrupted with a CTRL+C. It will use the built-in PHP web server to serve the application for test purposes on all network interfaces, using port 8000.

      Now go to your browser and access the application using the server’s domain name or IP address on port 8000:

      http://server_domain_or_IP:8000
      

      You will see the following page:

      Travellist v1.0

      If you see this page, it means the application is successfully pulling data about locations and photos from the configured managed database. The image files are still stored in the local disk, but we’ll change this in a following step of this guide.

      When you are finished testing the application, you can stop the serve command by hitting CTRL+C.

      Don’t forget to close port 8000 again if you are running UFW on your server:

      Step 4 — Configuring Nginx to Serve the Application

      Although the built-in PHP web server is very useful for development and testing purposes, it is not intended to be used as a long term solution to serve PHP applications. Using a full featured web server like Nginx is the recommended way of doing that.

      To get started, we’ll move the application folder to /var/www, which is the usual location for web applications running on Nginx. First, use the mv command to move the application folder with all its contents to /var/www/travellist:

      • sudo mv ~/travellist /var/www/travellist

      Now we need to give the web server user write access to the storage and bootstrap/cache folders, where Laravel stores application-generated files. We’ll set these permissions using setfacl, a command line utility that allows for more robust and fine-grained permission settings in files and folders.

      To include read, write and execution (rwx) permissions to the web server user over the required directories, run:

      • sudo setfacl -R -m g:www-data:rwx /var/www/travellist/storage
      • sudo setfacl -R -m g:www-data:rwx /var/www/travellist/bootstrap/cache

      The application files are now in order, but we still need to configure Nginx to serve the content. To do this, we’ll create a new virtual host configuration file at /etc/nginx/sites-available:

      • sudo nano /etc/nginx/sites-available/travellist

      The following configuration file contains the recommended settings for Laravel applications on Nginx:

      /etc/nginx/sites-available/travellist

      server {
          listen 80;
          server_name server_domain_or_IP;
          root /var/www/travellist/public;
      
          add_header X-Frame-Options "SAMEORIGIN";
          add_header X-XSS-Protection "1; mode=block";
          add_header X-Content-Type-Options "nosniff";
      
          index index.html index.htm index.php;
      
          charset utf-8;
      
          location / {
              try_files $uri $uri/ /index.php?$query_string;
          }
      
          location = /favicon.ico { access_log off; log_not_found off; }
          location = /robots.txt  { access_log off; log_not_found off; }
      
          error_page 404 /index.php;
      
          location ~ .php$ {
              fastcgi_pass unix:/var/run/php/php7.2-fpm.sock;
              fastcgi_index index.php;
              fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
              include fastcgi_params;
          }
      
          location ~ /.(?!well-known).* {
              deny all;
          }
      }
      

      Copy this content to your /etc/nginx/sites-available/travellist file and adjust the highlighted values to align with your own configuration. Save and close the file when you’re done editing.

      To activate the new virtual host configuration file, create a symbolic link to travellist in sites-enabled:

      • sudo ln -s /etc/nginx/sites-available/travellist /etc/nginx/sites-enabled/

      Note: If you have another virtual host file that was previously configured for the same server_name used in the travellist virtual host, you might need to deactivate the old configuration by removing the corresponding symbolic link inside /etc/nginx/sites-enabled/.

      To confirm that the configuration doesn’t contain any syntax errors, you can use:

      You should see output like this:

      Output

      • nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
      • nginx: configuration file /etc/nginx/nginx.conf test is successful

      To apply the changes, reload Nginx with:

      • sudo systemctl reload nginx

      If you reload your browser now, the application images will be broken. That happens because we moved the application directory to a new location inside the server, and for that reason we need to re-create the symbolic link to the application storage folder.

      Remove the old link with:

      • cd /var/www/travellist
      • rm -f public/storage

      Now run once again the artisan command to generate the storage link:

      Now go to your browser and access the application using the server’s domain name or IP address, as defined by the server_name directive in your configuration file:

      http://server_domain_or_IP
      

      Travellist v1.0

      In the next step, we’ll integrate an object storage service into the application. This will replace the current local disk storage used for the travel photos.

      Step 5 — Integrating an S3-Compatible Object Storage into the Application

      We’ll now set up the application to use an S3-compatible object storage service for storing the travel photos exhibited on the index page. Because the application already has a few sample photos stored in the local disk, we’ll also use the s3cmd tool to upload the existing local image files to the remote object storage.

      Setting Up the S3 Driver for Laravel

      Laravel uses league/flysystem, a filesystem abstraction library that enables a Laravel application to use and combine multiple storage solutions, including local disk and cloud services. An additional package is required to use the s3 driver. We can install this package using the composer require command.

      Access the application directory:

      • composer require league/flysystem-aws-s3-v3

      You will see output similar to this:

      Output

      Using version ^1.0 for league/flysystem-aws-s3-v3 ./composer.json has been updated Loading composer repositories with package information Updating dependencies (including require-dev) Package operations: 8 installs, 0 updates, 0 removals - Installing mtdowling/jmespath.php (2.4.0): Loading from cache - Installing ralouphie/getallheaders (3.0.3): Loading from cache - Installing psr/http-message (1.0.1): Loading from cache - Installing guzzlehttp/psr7 (1.6.1): Loading from cache - Installing guzzlehttp/promises (v1.3.1): Loading from cache - Installing guzzlehttp/guzzle (6.4.1): Downloading (100%) - Installing aws/aws-sdk-php (3.112.28): Downloading (100%) - Installing league/flysystem-aws-s3-v3 (1.0.23): Loading from cache ...

      Now that the required packages are installed, we can update the application to connect to the object storage. First, we’ll open the .env file again to set up configuration details such as keys, bucket name, and region for your object storage service.

      Open the .env file:

      Include the following environment variables, replacing the highlighted values with your object store configuration details:

      /var/www/travellist/.env

      DO_SPACES_KEY=EXAMPLE7UQOTHDTF3GK4
      DO_SPACES_SECRET=exampleb8e1ec97b97bff326955375c5
      DO_SPACES_ENDPOINT=https://ams3.digitaloceanspaces.com
      DO_SPACES_REGION=ams3
      DO_SPACES_BUCKET=sammy-travellist
      

      Save and close the file when you’re done. Now open the config/filesystems.php file:

      • nano config/filesystems.php

      Within this file, we’ll create a new disk entry in the disks array. We’ll name this disk spaces, and we’ll use the environment variables we’ve set in the .env file to configure the new disk. Include the following entry in the disks array:

      config/filesystems.php

      
      'spaces' => [
         'driver' => 's3',
         'key' => env('DO_SPACES_KEY'),
         'secret' => env('DO_SPACES_SECRET'),
         'endpoint' => env('DO_SPACES_ENDPOINT'),
         'region' => env('DO_SPACES_REGION'),
         'bucket' => env('DO_SPACES_BUCKET'),
      ],
      
      

      Still in the same file, locate the cloud entry and change it to set the new spaces disk as default cloud filesystem disk:

      config/filesystems.php

      'cloud' => env('FILESYSTEM_CLOUD', "http://www.digitalocean.com/spaces'),
      

      Save and close the file when you’re done editing. From your controllers, you can now use the Storage::cloud() method as a shortcut to access the default cloud disk. This way, the application stays flexible to use multiple storage solutions, and you can switch between providers on a per-environment basis.

      The application is now configured to use object storage, but we still need to update the code that uploads new photos to the application.

      Let’s first examine the current uploadPhoto route, located in the PhotoController class. Open the file using your text editor:

      • nano app/Http/Controllers/PhotoController.php

      app/Http/Controllers/PhotoController.php

      …
      
      public function uploadPhoto(Request $request)
      {
         $photo = new Photo();
         $place = Place::find($request->input('place'));
      
         if (!$place) {
             //add new place
             $place = new Place();
             $place->name = $request->input('place_name');
             $place->lat = $request->input('place_lat');
             $place->lng = $request->input('place_lng');
         }
      
         $place->visited = 1;
         $place->save();
      
         $photo->place()->associate($place);
         $photo->image = $request->image->store('/', 'public');
         $photo->save();
      
         return redirect()->route('Main');
      }
      
      

      This method accepts a POST request and creates a new photo entry in the photos table. It begins by checking if an existing place was selected in the photo upload form, and if that’s not the case, it will create a new place using the provided information. The place is then set to visited and saved to the database. Following that, an association is created so that the new photo is linked to the designated place. The image file is then stored in the root folder of the public disk. Finally, the photo is saved to the database. The user is then redirected to the main route, which is the index page of the application.

      The highlighted line in this code is what we’re interested in. In that line, the image file is saved to the disk using the store method. The store method is used to save files to any of the disks defined in the filesystem.php configuration file. In this case, it is using the default disk to store uploaded images.

      We will change this behavior so that the image is saved to the object store instead of the local disk. In order to do that, we need to replace the public disk by the spaces disk in the store method call. We also need to make sure the uploaded file’s visibility is set to public instead of private.

      The following code contains the full PhotoController class, including the updated uploadPhoto method:

      app/Http/Controllers/PhotoController.php

      <?php
      
      namespace AppHttpControllers;
      
      use IlluminateHttpRequest;
      use AppPhoto;
      use AppPlace;
      use IlluminateSupportFacadesStorage;
      
      class PhotoController extends Controller
      {
         public function uploadForm()
         {
             $places = Place::all();
      
             return view('upload_photo', [
                 'places' => $places
             ]);
         }
      
         public function uploadPhoto(Request $request)
         {
             $photo = new Photo();
             $place = Place::find($request->input('place'));
      
             if (!$place) {
                 //add new place
                 $place = new Place();
                 $place->name = $request->input('place_name');
                 $place->lat = $request->input('place_lat');
                 $place->lng = $request->input('place_lng');
             }
      
             $place->visited = 1;
             $place->save();
      
             $photo->place()->associate($place);
             $photo->image = $request->image->store('/', 'spaces');
             Storage::setVisibility($photo->image, 'public');
             $photo->save();
      
             return redirect()->route('Main');
         }
      }
      
      
      

      Copy the updated code to your own PhotoController so that it reflects the highlighted changes. Save and close the file when you’re done editing.

      We still need to modify the application’s main view so that it uses the object storage file URL to render the image. Open the travel_list.blade.php template:

      • nano resources/views/travel_list.blade.php

      Now locate the footer section of the page, which currently looks like this:

      resources/views/travel_list.blade.php

      @section('footer')
         <h2>Travel Photos <small>[ <a href="{{ route('Upload.form') }}">Upload Photo</a> ]</small></h2>
         @foreach ($photos as $photo)
             <div class="photo">
                <img src="https://www.digitalocean.com/{{ asset('storage') . '/' . $photo->image }}" />
                 <p>{{ $photo->place->name }}</p>
             </div>
         @endforeach
      
      @endsection
      

      Replace the current image src attribute to use the file URL from the spaces storage disk:

      <img src="https://www.digitalocean.com/{{ Storage::disk('spaces')->url($photo->image) }}" />
      

      If you go to your browser now and reload the application page, it will show only broken images. That happens because the image files for those travel photos are still only in the local disk. We need to upload the existing image files to the object storage, so that the photos already stored in the database can be successfully exhibited in the application page.

      Syncing local images with s3cmd

      The s3cmd tool can be used to sync local files with an S3-compatible object storage service. We’ll run a sync command to upload all files inside storage/app/public/photos to the object storage service.

      Access the public app storage directory:

      • cd /var/www/travellist/storage/app/public

      To have a look at the files already stored in your remote disk, you can use the s3cmd ls command:

      • s3cmd ls s3://your_bucket_name

      Now run the sync command to upload existing files in the public storage folder to the object storage:

      • s3cmd sync ./ s3://your_bucket_name --acl-public --exclude=.gitignore

      This will synchronize the current folder (storage/app/public) with the remote object storage’s root dir. You will get output similar to this:

      Output

      upload: './bermudas.jpg' -> 's3://sammy-travellist/bermudas.jpg' [1 of 3] 2538230 of 2538230 100% in 7s 329.12 kB/s done upload: './grindavik.jpg' -> 's3://sammy-travellist/grindavik.jpg' [2 of 3] 1295260 of 1295260 100% in 5s 230.45 kB/s done upload: './japan.jpg' -> 's3://sammy-travellist/japan.jpg' [3 of 3] 8940470 of 8940470 100% in 24s 363.61 kB/s done Done. Uploaded 12773960 bytes in 37.1 seconds, 336.68 kB/s.

      Now, if you run s3cmd ls again, you will see that three new files were added to the root folder of your object storage bucket:

      • s3cmd ls s3://your_bucket_name

      Output

      2019-10-25 11:49 2538230 s3://sammy-travellist/bermudas.jpg 2019-10-25 11:49 1295260 s3://sammy-travellist/grindavik.jpg 2019-10-25 11:49 8940470 s3://sammy-travellist/japan.jpg

      Go to your browser and reload the application page. All images should be visible now, and if you inspect them using your browser debug tools, you’ll notice that they’re all using URLs from your object storage.

      Testing the Integration

      The demo application is now fully functional, storing files in a remote object storage service, and saving data to a managed MySQL database. We can now upload a few photos to test our setup.

      Access the /upload application route from your browser:

      http://server_domain_or_IP/upload
      

      You will see the following form:

      Travellist  Photo Upload Form

      You can now upload a few photos to test the object storage integration. After choosing an image from your computer, you can select an existing place from the dropdown menu, or you can add a new place by providing its name and geographic coordinates so it can be loaded in the application map.

      Step 6 — Scaling Up a DigitalOcean Managed MySQL Database with Read-Only Nodes (Optional)

      Because read-only operations are typically more frequent than writing operations on database servers, its is a common practice to scale up a database cluster by setting up multiple read-only nodes. This will distribute the load generated by SELECT operations.

      To demonstrate this setup, we’ll first add 2 read-only nodes to our DigitalOcean Managed MySQL cluster. Then, we’ll configure the Laravel application to use these nodes.

      Access the DigitalOcean Cloud Panel and follow these instructions:

      1. Go to Databases and select your MySQL cluster.
      2. Click Actions and choose Add a read-only node from the drop-down menu.
      3. Configure the node options and hit the Create button. Notice that it might take several minutes for the new node to be ready.
      4. Repeat steps 1-4 one more time so that you have 2 read-only nodes.
      5. Note down the hosts of the two nodes as we will need them for our Laravel configuration.

      Once you have your read-only nodes ready, head back to your terminal.

      We’ll now configure our Laravel application to work with multiple database nodes. When we’re finished, queries such as INSERT and UPDATE will be forwarded to your primary cluster node, while all SELECT queries will be redirected to your read-only nodes.

      First, go to the application’s directory on the server and open your .env file using your text editor of choice:

      • cd /var/www/travellist
      • nano .env

      Locate the MySQL database configuration and comment out the DB_HOST line:

      /var/www/travellist/.env

      DB_CONNECTION=mysql
      #DB_HOST=MANAGED_MYSQL_HOST
      DB_PORT=MANAGED_MYSQL_PORT
      DB_DATABASE=MANAGED_MYSQL_DB
      DB_USERNAME=MANAGED_MYSQL_USER
      DB_PASSWORD=MANAGED_MYSQL_PASSWORD
      

      Save and close the file when you’re done. Now open the config/database.php in your text editor:

      Look for the mysql entry inside the connections array. You should include three new items in this configuration array: read, write, and sticky. The read and write entries will set up the cluster nodes, and the sticky option set to true will reuse write connections so that data written to the database is immediately available in the same request cycle. You can set it to false if you don’t want this behavior.

      /var/www/travel_list/config/database.php

      ...
            'mysql' => [
               'read' => [
                 'host' => [
                    "http://www.digitalocean.com/READONLY_NODE1_HOST',
                    "http://www.digitalocean.com/READONLY_NODE2_HOST',
                 ],
               ],
               'write' => [
                 'host' => [
                   "http://www.digitalocean.com/MANAGED_MYSQL_HOST',
                 ],
               ],
             'sticky' => true,
      ...
      

      Save and close the file when you are done editing. To test that everything works as expected, we can create a temporary route inside routes/web.php to pull some data from the database and show details about the connection being used. This way we will be able to see how the requests are being load balanced between the read-only nodes.

      Open the routes/web.php file:

      Include the following route:

      /var/www/travel_list/routes/web.php

      ...
      
      Route::get('/mysql-test', function () {
        $places = AppPlace::all();
        $results = DB::select( DB::raw("SHOW VARIABLES LIKE 'server_id'") );
      
        return "Server ID: " . $results[0]->Value;
      });
      

      Now go to your browser and access the /mysql-test application route:

      http://server_domain_or_IP/mysql-test
      

      You’ll see a page like this:

      mysql node test page

      Reload the page a few times and you will notice that the Server ID value changes, indicating that the requests are being randomly distributed between the two read-only nodes.

      Conclusion

      In this guide, we’ve prepared a Laravel 6 application for a highly available and scalable environment. We’ve outsourced the database system to an external managed MySQL service, and we’ve integrated an S3-compatible object storage service into the application to store files uploaded by users. Finally, we’ve seen how to scale up the application’s database by including additional read-only cluster nodes in the app’s configuration file.

      The updated demo application code containing all modifications made in this guide can be found within the 2.1 tag in the application’s repository on Github.

      From here, you can set up a Load Balancer to distribute load and scale your application among multiple nodes. You can also leverage this setup to create a containerized environment to run your application on Docker.



      Source link

      How to Install and Configure Laravel with LEMP on Ubuntu 18.04


      Introduction

      Laravel is an open-source PHP framework that provides a set of tools and resources to build modern PHP applications. With a complete ecosystem leveraging its built-in features, Laravel’s popularity has grown rapidly in the past few years, with many developers adopting it as their framework of choice for a streamlined development process.

      In this guide, you’ll install and configure a new Laravel application on an Ubuntu 18.04 server, using Composer to download and manage the framework dependencies. When you’re finished, you’ll have a functional Laravel demo application pulling content from a MySQL database.

      Prerequisites

      In order to complete this guide, you will first need to perform the following tasks on your Ubuntu 18.04 server:

      Step 1 — Installing Required PHP modules

      Before you can install Laravel, you need to install a few PHP modules that are required by the framework. We’ll use apt to install the php-mbstring, php-xml and php-bcmath PHP modules. These PHP extensions provide extra support for dealing with character encoding, XML and precision mathematics.

      If this is the first time using apt in this session, you should first run the update command to update the package manager cache:

      Now you can install the required packages with:

      • sudo apt install php-mbstring php-xml php-bcmath

      Your system is now ready to execute Laravel's installation via Composer, but before doing so, you'll need a database for your application.

      Step 2 — Creating a Database for the Application

      To demonstrate Laravel's basic installation and usage, we'll create a sample travel list application to show a list of places a user would like to travel to, and a list of places that they already visited. This can be stored in a simple places table with a field for locations that we'll call name and another field to mark them as visited or not visited, which we'll call visited. Additionally, we'll include an id field to uniquely identify each entry.

      To connect to the database from the Laravel application, we'll create a dedicated MySQL user, and grant this user full privileges over the travel_list database.

      To get started, log in to the MySQL console as the root database user with:

      To create a new database, run the following command from your MySQL console:

      • CREATE DATABASE travel_list;

      Now you can create a new user and grant them full privileges on the custom database you've just created. In this example, we're creating a user named travel_user with the password password, though you should change this to a secure password of your choosing:

      • GRANT ALL ON travel_list.* TO 'travel_user'@'localhost' IDENTIFIED BY 'password' WITH GRANT OPTION;

      This will give the travel_user user full privileges over the travel_list database, while preventing this user from creating or modifying other databases on your server.

      Following this, exit the MySQL shell:

      You can now test if the new user has the proper permissions by logging in to the MySQL console again, this time using the custom user credentials:

      Note the -p flag in this command, which will prompt you for the password used when creating the travel_user user. After logging in to the MySQL console, confirm that you have access to the travel_list database:

      This will give you the following output:

      Output

      +--------------------+ | Database | +--------------------+ | information_schema | | travel_list | +--------------------+ 2 rows in set (0.01 sec)

      Next, create a table named places in the travel_list database. From the MySQL console, run the following statement:

      • CREATE TABLE travel_list.places (
      • id INT AUTO_INCREMENT,
      • name VARCHAR(255),
      • visited BOOLEAN,
      • PRIMARY KEY(id)
      • );

      Now, populate the places table with some sample data:

      • INSERT INTO travel_list.places (name, visited)
      • VALUES ("Tokyo", false),
      • ("Budapest", true),
      • ("Nairobi", false),
      • ("Berlin", true),
      • ("Lisbon", true),
      • ("Denver", false),
      • ("Moscow", false),
      • ("Olso", false),
      • ("Rio", true),
      • ("Cincinati", false),

      To confirm that the data was successfully saved to your table, run:

      • SELECT * FROM travel_list.places;

      You will see output similar to this:

      Output

      +----+-----------+---------+ | id | name | visited | +----+-----------+---------+ | 1 | Tokyo | 0 | | 2 | Budapest | 1 | | 3 | Nairobi | 0 | | 4 | Berlin | 1 | | 5 | Lisbon | 1 | | 6 | Denver | 0 | | 7 | Moscow | 0 | | 8 | Oslo | 0 | | 9 | Rio | 1 | | 10 | Cincinati | 0 | +----+-----------+---------+ 10 rows in set (0.00 sec)

      After confirming that you have valid data in your test table, you can exit the MySQL console:

      You're now ready to create the application and configure it to connect to the new database.

      Step 3 — Creating a New Laravel Application

      You will now create a new Laravel application using the composer create-project command. This Composer command is typically used to bootstrap new applications based on existing frameworks and content management systems.

      Throughout this guide, we'll use travel_list as an example application, but you are free to change this to something else. The travel_list application will display a list of locations pulled from a local MySQL server, intended to demonstrate Laravel's basic configuration and confirm that you're able to connect to the database.

      First, go to your user's home directory:

      The following command will create a new travel_list directory containing a barebones Laravel application based on default settings:

      • composer create-project --prefer-dist laravel/laravel travel_list

      You will see output similar to this:

      Output

      Installing laravel/laravel (v5.8.17) - Installing laravel/laravel (v5.8.17): Downloading (100%) Created project in travel_list > @php -r "file_exists('.env') || copy('.env.example', '.env');" Loading composer repositories with package information Updating dependencies (including require-dev) Package operations: 80 installs, 0 updates, 0 removals - Installing symfony/polyfill-ctype (v1.11.0): Downloading (100%) - Installing phpoption/phpoption (1.5.0): Downloading (100%) - Installing vlucas/phpdotenv (v3.4.0): Downloading (100%) - Installing symfony/css-selector (v4.3.2): Downloading (100%) ...

      When the installation is finished, access the application's directory and run Laravel's artisan command to verify that all components were successfully installed:

      • cd travel_list
      • php artisan

      You'll see output similar to this:

      Output

      Laravel Framework 5.8.29 Usage: command [options] [arguments] Options: -h, --help Display this help message -q, --quiet Do not output any message -V, --version Display this application version --ansi Force ANSI output --no-ansi Disable ANSI output -n, --no-interaction Do not ask any interactive question --env[=ENV] The environment the command should run under -v|vv|vvv, --verbose Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug (...)

      This output confirms that the application files are in place, and the Laravel command-line tools are working as expected. However, we still need to configure the application to set up the database and a few other details.

      Step 4 — Configuring Laravel

      The Laravel configuration files are located in a directory called config, inside the application's root directory. Additionally, when you install Laravel with Composer, it creates an environment file. This file contains settings that are specific to the current environment the application is running, and will take precedence over the values set in regular configuration files located at the config directory. Each installation on a new environment requires a tailored environment file to define things such as database connection settings, debug options, application URL, among other items that may vary depending on which environment the application is running.

      Warning: The environment configuration file contains sensitive information about your server, including database credentials and security keys. For that reason, you should never share this file publicly.

      We'll now edit the .env file to customize the configuration options for the current application environment.

      Open the .env file using your command line editor of choice. Here we'll use nano:

      Even though there are many configuration variables in this file, you don't need to set up all of them now. The following list contains an overview of the variables that require immediate attention:

      • APP_NAME: Application name, used for notifications and messages.
      • APP_ENV: Current application environment.
      • APP_KEY: Used for generating salts and hashes, this unique key is automatically created when installing Laravel via Composer, so you don't need to change it.
      • APP_DEBUG: Whether or not to show debug information at client side.
      • APP_URL: Base URL for the application, used for generating application links.
      • DB_DATABASE: Database name.
      • DB_USERNAME: Username to connect to the database.
      • DB_PASSWORD: Password to connect to the database.

      By default, these values are configured for a local development environment that uses Homestead, a prepackaged Vagrant box provided by Laravel. We'll change these values to reflect the current environment settings of our example application.

      In case you are installing Laravel in a development or testing environment, you can leave the APP_DEBUG option enabled, as this will give you important debug information while testing the application from a browser. The APP_ENV variable should be set to development or testing in this case.

      In case you are installing Laravel in a production environment, you should disable the APP_DEBUG option, because it shows to the final user sensitive information about your application. The APP_ENV in this case should be set to production.

      The following .env file sets up our example application for development:

      Note: The APP_KEY variable contains a unique key that was auto generated when you installed Laravel via Composer. You don't need to change this value. If you want to generate a new secure key, you can use the php artisan key:generate command.

      /var/www/travel_list/.env

      APP_NAME=TravelList
      APP_ENV=development
      APP_KEY=APPLICATION_UNIQUE_KEY_DONT_COPY
      APP_DEBUG=true
      APP_URL=http://domain_or_IP
      
      LOG_CHANNEL=stack
      
      DB_CONNECTION=mysql
      DB_HOST=127.0.0.1
      DB_PORT=3306
      DB_DATABASE=travel_list
      DB_USERNAME=travel_user
      DB_PASSWORD=password
      
      ...
      

      Adjust your variables accordingly. When you are done editing, save and close the file to keep your changes. If you're using nano, you can do that with CTRL+X, then Y and Enter to confirm.

      Your Laravel application is now set up, but we still need to configure the web server in order to be able to access it from a browser. In the next step, we'll configure Nginx to serve your Laravel application.

      Step 5 — Setting Up Nginx

      We have installed Laravel on a local folder of your remote user's home directory, and while this works well for local development environments, it's not a recommended practice for web servers that are open to the public internet. We'll move the application folder to /var/www, which is the usual location for web applications running on Nginx.

      First, use the mv command to move the application folder with all its contents to /var/www/travel_list:

      • sudo mv ~/travel_list /var/www/travel_list

      Now we need to give the web server user write access to the storage and cache folders, where Laravel stores application-generated files:

      • sudo chown -R www-data.www-data /var/www/travel_list/storage
      • sudo chown -R www-data.www-data /var/www/travel_list/bootstrap/cache

      The application files are now in order, but we still need to configure Nginx to serve the content. To do this, we'll create a new virtual host configuration file at /etc/nginx/sites-available:

      • sudo nano /etc/nginx/sites-available/travel_list

      The following configuration file contains the recommended settings for Laravel applications on Nginx:

      /etc/nginx/sites-available/travel_list

      server {
          listen 80;
          server_name server_domain_or_IP;
          root /var/www/travel_list/public;
      
          add_header X-Frame-Options "SAMEORIGIN";
          add_header X-XSS-Protection "1; mode=block";
          add_header X-Content-Type-Options "nosniff";
      
          index index.html index.htm index.php;
      
          charset utf-8;
      
          location / {
              try_files $uri $uri/ /index.php?$query_string;
          }
      
          location = /favicon.ico { access_log off; log_not_found off; }
          location = /robots.txt  { access_log off; log_not_found off; }
      
          error_page 404 /index.php;
      
          location ~ .php$ {
              fastcgi_pass unix:/var/run/php/php7.2-fpm.sock;
              fastcgi_index index.php;
              fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
              include fastcgi_params;
          }
      
          location ~ /.(?!well-known).* {
              deny all;
          }
      }
      

      Copy this content to your /etc/nginx/sites-available/travel_list file and, if necessary, adjust the highlighted values to align with your own configuration. Save and close the file when you're done editing.

      To activate the new virtual host configuration file, create a symbolic link to travel_list in sites-enabled:

      • sudo ln -s /etc/nginx/sites-available/travel_list /etc/nginx/sites-enabled/

      Note: If you have another virtual host file that was previously configured for the same server_name used in the travel_list virtual host, you might need to deactivate the old configuration by removing the corresponding symbolic link inside /etc/nginx/sites-enabled/.

      To confirm that the configuration doesn't contain any syntax errors, you can use:

      You should see output like this:

      Output

      • nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
      • nginx: configuration file /etc/nginx/nginx.conf test is successful

      To apply the changes, reload Nginx with:

      • sudo systemctl reload nginx

      Now go to your browser and access the application using the server's domain name or IP address, as defined by the server_name directive in your configuration file:

      http://server_domain_or_IP
      

      You will see a page like this:

      Laravel splash page

      That confirms your Nginx server is properly configured to serve Laravel. From this point, you can start building up your application on top of the skeleton provided by the default installation.

      In the next step, we'll modify the application's main route to query for data in the database using Laravel's DB facade.

      Step 6 — Customizing the Main Page

      Assuming you've followed all the steps in this guide so far, you should have a working Laravel application and a database table named places containing some sample data.

      We'll now edit the main application route to query for the database and return the contents to the application's view.

      Open the main route file, routes/web.php:

      This file comes by default with the following content:

      routes/web.php

      <?php
      
      /*
      |--------------------------------------------------------------------------
      | Web Routes
      |--------------------------------------------------------------------------
      |
      | Here is where you can register web routes for your application. These
      | routes are loaded by the RouteServiceProvider within a group which
      | contains the "web" middleware group. Now create something great!
      |
      */
      
      Route::get('/', function () {
          return view('welcome');
      });
      
      

      Routes are defined within this file using the static method Route::get, which receives a path and a callback function as arguments.

      The following code replaces the main route callback function. It makes 2 queries to the database using the visited flag to filter results. It returns the results to a view named travel_list, which we're going to create next. Copy this content to your routes/web.php file, replacing the code that is already there:

      routes/web.php

      <?php
      
      use IlluminateSupportFacadesDB;
      
      Route::get('/', function () {
        $visited = DB::select('select * from places where visited = ?', [1]); 
        $togo = DB::select('select * from places where visited = ?', [0]);
      
        return view('travel_list', ['visited' => $visited, 'togo' => $togo ] );
      });
      

      Save and close the file when you're done editing. We'll now create the view that will render the database results to the user. Create a new view file inside resources/views:

      • nano resources/views/travel_list.blade.php

      The following template creates two lists of places based on the variables visited and togo. Copy this content to your new view file:

      resources/views/travel_list/blade.php

      <html>
      <head>
          <title>Travel List</title>
      </head>
      
      <body>
          <h1>My Travel Bucket List</h1>
          <h2>Places I'd Like to Visit</h2>
          <ul>
            @foreach ($togo as $newplace)
              <li>{{ $newplace->name }}</li>
            @endforeach
          </ul>
      
          <h2>Places I've Already Been To</h2>
          <ul>
                @foreach ($visited as $place)
                      <li>{{ $place->name }}</li>
                @endforeach
          </ul>
      </body>
      </html>
      

      Save and close the file when you're done. Now go to your browser and reload the application. You'll see a page like this:

      Demo Laravel Application

      You have now a functional Laravel application pulling contents from a MySQL database.

      Conclusion

      In this tutorial, you've set up a new Laravel application on top of a LEMP stack (Linux, Nginx, MySQL and PHP), running on an Ubuntu 18.04 server. You've also customized your default route to query for database content and exhibit the results in a custom view.

      From here, you can create new routes and views for any additional pages your application needs. Check the official Laravel documentation for more information on routes, views, and database support. If you're deploying to production, you should also check the optimization section for a few different ways in which you can improve your application's performance.

      For improved security, you should consider installing an TLS/SSL certificate for your server, allowing it to serve content over HTTPS. To this end, you can follow our guide on how to secure your Nginx installation with Let's Encrypt on Ubuntu 18.04.



      Source link