One place for hosting & domains

      desarrollo

      Cómo disponer en contenedor una aplicación Laravel 6 para el desarrollo con Docker Compose en Ubuntu 18.04


      Introducción

      Cuando se habla de disponer en contenedores una aplicación, se hace referencia al proceso de adaptación de una aplicación y sus componentes para poder ejecutarla en entornos ligeros conocidos como contenedores. Estos entornos están aislados, son desechables y se pueden utilizar para desarrollar, probar e implementar aplicaciones en la producción.

      A lo largo de esta guía, utilizaremos Docker Compose para disponer en contendor una aplicación Laravel 6 para el desarrollo. Al finalizar, dispondrá de una aplicación Laravel de demostración funcional en tres contenedores de servicios separados:

      • un servicio app con PHP7.4-FPM;
      • un servicio db con MySQL 5.7;
      • un servicio nginx en el que se utilice el servicio app para analizar el código PHP antes de proporcionar la aplicación Laravel al usuario final.

      Para permitir un proceso de desarrollo simplificado y facilitar la depuración de aplicaciones, mantendremos sincronizados los archivos de la aplicación usando volúmenes compartidos. También veremos cómo usar comandos docker-compose exec para la ejecución de Composer y Artisan en el contenedor app.

      Requisitos previos

      Paso 1: Obtener la aplicación de demostración

      Para comenzar, obtendremos la aplicación Laravel de demostración de su repositorio de Github. Nos interesa la ramificación tutorial-01, que contiene la aplicación básica de Laravel que creamos en la primera guía de esta serie.

      Para obtener el código de la aplicación que es compatible con este tutorial, descargue la versión tutorial-1.0.1 en su directorio de inicio con lo siguiente:

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

      Necesitaremos el comando unzip para desempaquetar el código de la aplicación. En caso de que no haya instalado el paquete antes, hágalo ahora con lo siguiente:

      • sudo apt update
      • sudo apt install unzip

      Luego, descomprima el contenido de la aplicación y cambie el nombre del directorio desempaquetado para facilitar el acceso:

      • unzip travellist.zip
      • mv travellist-laravel-demo-tutorial-1.0.1 travellist-demo

      Diríjase al directorio travellist-demo:

      En el siguiente paso, crearemos un archivo de configuración .env para configurar la aplicación.

      Paso 2: Configurar el archivo .env de la aplicación

      Los archivos de configuración de Laravel se encuentran en un directorio llamado config, dentro del directorio root de la aplicación. Además, un archivo .env se utiliza para establecer una configuración dependiente del entorno, como las credenciales y cualquier información que pueda variar entre las implementaciones. Este archivo no está incluido en el control de revisiones.

      Advertencia: En el archivo de configuración del entorno se encuentra información confidencial sobre su servidor, incluidas las credenciales de bases de datos y las claves de seguridad. Por ese motivo, nunca debe compartir públicamente este archivo.

      Los valores incluidos en el archivo .env tendrán prioridad sobre los valores establecidos en los archivos de configuración normales que se encuentran en el directorio config. Para cada instalación en un nuevo entorno se requiere un archivo de entorno personalizado a fin de definir elementos como las configuraciones de conexión de bases de datos, las opciones de depuración y las URL de aplicación, entre otros elementos que pueden variar dependiendo del entorno en el que se ejecute la aplicación.

      Ahora, crearemos un nuevo archivo .env para personalizar las opciones de configuración para el entorno de desarrollo que configuraremos. En Laravel se incluye un archivo .env de ejemplo que podemos copiar para crear el nuestro:

      Abra este archivo utilzando nano o el editor de texto que prefiera:

      En el archivo .env actual de la aplicación de demostración travellist se incluyen las configuraciones para usar una base de datos local de MySQL, con 127.0.0.1 como host de base de datos. Necesitamos actualizar la variable DB_HOST para que esta apunte al servicio de base de datos que crearemos en nuestro entorno de Docker. En esta guía, usaremos el nombre db para el servicio de nuestra base de datos. Sustituya el valor de la lista de DB_HOST por el nombre del servicio de la base de datos:

      .env

      APP_NAME=Travellist
      APP_ENV=dev
      APP_KEY=
      APP_DEBUG=true
      APP_URL=http://localhost:8000
      
      LOG_CHANNEL=stack
      
      DB_CONNECTION=mysql
      DB_HOST=db
      DB_PORT=3306
      DB_DATABASE=travellist
      DB_USERNAME=travellist_user
      DB_PASSWORD=password
      ...
      

      Si lo desea, puede cambiar también el nombre, el nombre de usuario y la contraseña de la base de datos. Estas variables aprovecharán en un paso posterior en el que prepararemos el archivo docker-compose.yml para configurar nuestros servicios.

      Guarde el archivo cuando finalice la edición. Si utiliza nano, puede hacerlo presionando CTRL+X, luego Y y Enter para confirmar.

      Paso 3: Configurar el Dockerfile de la aplicación

      Aunque ambos servicios de MySQL y Nginx se basarán en imágenes predeterminadas obtenidas de Docker Hub, debemos de todas formas crear una imagen personalizada para el contenedor de la aplicación. Crearemos un nuevo Dockerfile para ello.

      Nuestra imagen de travellist se basará en la imagen oficial de PHP php:7.4-fpm de Docker Hub. Además de ese entorno básico de PHP-FPM, instalaremos algunos módulos de PHP adicionales y la herramienta de administración de dependencias Composer.

      También crearemos un nuevo usuario de sistema; esto es necesario para ejecutar los comandos artisan y composer mientras se desarrolla la aplicación. En la configuración uid se garantiza que el usuario dentro del contenedor tenga el mismo uid que el usuario de su sistema en su equipo host, donde Docker está en ejecución. De esta manera, todos los archivos creados por estos comandos se replican en el host con los permisos correctos. Esto también significa que podrá usar su editor de código preferido en la máquina host para desarrollar la aplicación que está en ejecución dentro de los contenedores.

      Cree un nuevo Dockerfile con lo siguiente:

      Copie el siguiente contenido en su Dockerfile:

      Dockerfile

      FROM php:7.4-fpm
      
      # Arguments defined in docker-compose.yml
      ARG user
      ARG uid
      
      # Install system dependencies
      RUN apt-get update && apt-get install -y 
          git 
          curl 
          libpng-dev 
          libonig-dev 
          libxml2-dev 
          zip 
          unzip
      
      # Clear cache
      RUN apt-get clean && rm -rf /var/lib/apt/lists/*
      
      # Install PHP extensions
      RUN docker-php-ext-install pdo_mysql mbstring exif pcntl bcmath gd
      
      # Get latest Composer
      COPY --from=composer:latest /usr/bin/composer /usr/bin/composer
      
      # Create system user to run Composer and Artisan Commands
      RUN useradd -G www-data,root -u $uid -d /home/$user $user
      RUN mkdir -p /home/$user/.composer && 
          chown -R $user:$user /home/$user
      
      # Set working directory
      WORKDIR /var/www
      
      USER $user
      
      

      No olvide guardar el archivo cuando termine.

      Nuestro Dockerfile comienza definiendo la imagen base que usaremos: php:7.4-fpm.

      Después de instalar los paquetes del sistema y las extensiones de PHP, instalamos Composer copiando el ejecutable composer de su imagen oficial más reciente en la propia imagen de nuestra aplicación.

      A continuación, se crea un nuevo usuario de sistema y se configura usando los argumentos user y uid que se declararon al inicio del Dockerfile. Docker Compose insertará estos valores en el tiempo de compilación.

      Por último, fijaremos el directorio de trabajo predeterminado como /var/www y cambiaremos al usuario recién creado. Esto garantizará que se conecte como un usuario normal y que se encuentre en el directorio correcto, al ejecutar comandos de composer y artisan en el contenedor de la aplicación.

      Paso 4: Preparar archivos de configuración de Nginx y de volcado de bases de datos

      Cuando se crean entornos de desarrollo con Docker Compose, a menudo es necesario compartir archivos de configuración o de inicialización con contenedores de servicios para que se puedan configurar o aplicar estos servicios. Esta práctica facilita la aplicación de cambios en los archivos de configuración para ajustar el entorno mientras desarrolla la aplicación.

      Ahora crearemos una carpeta con archivos que se utilizarán para configurar e inicializar nuestros contenedores de servicios.

      Para configurar Nginx, compartiremos un archivo travellist.conf en el que se configurará la forma en que se proporciona la aplicación. Cree la carpeta docker-compose/nginx con lo siguiente:

      • mkdir -p docker-compose/nginx

      Abra un nuevo archivo llamado travellist.conf dentro de ese directorio:

      • nano docker-compose/nginx/travellist.conf

      Copie la siguiente configuración de Nginx en ese archivo:

      docker-compose/nginx/travellist.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;
          }
      }
      

      En este archivo se configurará Nginx para que escuche en el puerto 80 y utilice index.php como la página de índice predeterminada. Establecerá el documento root en /var/www/public y luego configurará Nginx para que en este se utilice el servicio app en el puerto 9000 a fin de procesar archivos *.php.

      Guarde y cierre el archivo cuando finalice la edición.

      Para configurar la base de datos de MySQL, compartiremos un volcado de base de datos que se importará cuando se inicialice el contenedor. Esta es una característica que se proporciona a través de la imagen de MySQL 5.7 y que usaremos en ese contenedor.

      Cree una nueva carpeta para sus archivos de inicialización de MySQL dentro de la carpeta docker-compose:

      • mkdir docker-compose/mysql

      Abra un nuevo archivo .sql:

      • nano docker-compose/mysql/init_db.sql

      El siguiente volcado de MySQL deriva de la base de datos que configuramos en nuestra guía sobre Laravel en LEMP. Se creará una nueva tabla llamada places. Luego, se completará la tabla con un conjunto de lugares de ejemplo.

      Añada el siguiente código al archivo:

      docker-compose/mysql/db_init.sql

      DROP TABLE IF EXISTS `places`;
      
      CREATE TABLE `places` (
        `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
        `name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
        `visited` tinyint(1) NOT NULL DEFAULT '0',
        PRIMARY KEY (`id`)
      ) ENGINE=InnoDB AUTO_INCREMENT=12 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
      
      INSERT INTO `places` (name, visited) VALUES ('Berlin',0),('Budapest',0),('Cincinnati',1),('Denver',0),('Helsinki',0),('Lisbon',0),('Moscow',1),('Nairobi',0),('Oslo',1),('Rio',0),('Tokyo',0);
      

      En la tabla places se incluyen tres campos: id, name y visited. El campo visited es un indicador utilizado para identificar los lugares que aún quedan por recorrer. Puede cambiar los lugares de ejemplo o incluir casos nuevos si lo desea. Guarde y cierre el archivo cuando termine.

      Terminamos de preparar el Dockerfile de la aplicación y los archivos de configuración del servicio. A continuación, configuraremos Docker Compose para que se utilicen estos archivos al crear nuestros servicios.

      Paso 5: Crear un entorno en varios contenedores con Docker Compose

      Docker Compose le permite crear entornos en varios contenedores para aplicaciones que se ejecuten en Docker. Se utilizan definiciones de servicio para crear entornos totalmente personalizables con varios contenedores que pueden compartir redes y volúmenes de datos. Esto permite una integración sin problemas entre los componentes de la aplicación.

      Para establecer nuestras definiciones de servicios, crearemos un nuevo archivo llamado docker-compose.yml. Normalmente, este archivo se encuentra en la carpeta root de la aplicación y define su entorno en contenedor, incluidas las imágenes de base que usará para crear sus contenedores y la forma en que interactuarán sus servicios.

      Definiremos tres servicios diferentes en nuestro archivo docker-compose.yml: app, db y nginx.

      En el servicio app se creará una imagen llamada travellist, basada en el Dockerfile que creamos previamente. En el contenedor definido por este servicio se ejecutará un servidor php-fpm para analizar el código PHP y enviar los resultados de vuelta al servicio nginx, que estará en ejecución en un contenedor separado. A través del servicio mysql, se define un contenedor en un servidor MySQL 5.7. En nuestros servicios se compartirá una red de puente llamada travellist.

      Los archivos de la aplicación se sincronizarán en los servicios app y nginx mediante montajes “bind”. Los montajes “bind” son útiles en los entornos de desarrollo porque en estos se permite una sincronización bidireccional estable entre el equipo host y los contenedores.

      Cree un nuevo archivo docker-compose.yml en la carpeta root de la aplicación:

      Un archivo típico de docker-compose.yml comienza con una definición de versión, a la que le sigue un nodo services, donde se definen todos los servicios. Las redes compartidas suelen definirse al final de ese archivo.

      Para comenzar, copie el siguiente código estándar a su archivo docker-compose.yml:

      docker-compose.yml

      version: "3.7"
      services:
      
      
      networks:
        travellist:
          driver: bridge
      

      Ahora, editaremos el nodo services para incluir los servicios app, db y nginx.

      El servicio app

      A través del servicio app se configurará un contenedor llamado travellist-app. Crea una nueva imagen de Docker basada en un Dockerfile ubicado en la misma ruta que el archivo docker-compose.yml. La nueva imagen se guardará a nivel local con el nombre travellist.

      Aunque el root de documentos que se proporciona como la aplicación se encuentra en el contenedor nginx, también necesitamos los archivos de la aplicación en algún lugar dentro del contenedor app para poder ejecutar tareas de línea de comandos con la herramienta Laravel Artisan.

      Copie la siguiente definición de servicio en su nodo services, dentro del archivo docker-compose.yml:

      docker-compose.yml

        app:
          build:
            args:
              user: sammy
              uid: 1000
            context: ./
            dockerfile: Dockerfile
          image: travellist
          container_name: travellist-app
          restart: unless-stopped
          working_dir: /var/www/
          volumes:
            - ./:/var/www
          networks:
            - travellist
      

      Estas configuraciones tienen los siguientes atributos:

      • build: en esta configuración indica a Docker Compose que cree una imagen local para el servicio app, y usa la ruta especificada (context) y Dockerfile para las instrucciones. Los argumentos user y uid se insertan en Dockerfile para personalizar los comandos de creación del usuario en el tiempo de compilación.
      • image: nombre que se usará para la imagen que se creará.
      • container_name: configura el nombre del contenedor para este servicio.
      • restart: siempre ejecuta un reinicio, a menos que se detenga el servicio.
      • working_dir: establece el directorio predeterminado para este servicio como /var/www.
      • volumes: crea un volumen compartido en el que se sincronizarán los contenidos del directorio actual con /var/www dentro del contenedor. Tenga en cuenta que esto no es su root de documentos, ya que este residirá en el contenedor nginx.
      • networks: configura este servicio para que se utilice una red llamada travellist.

      El servicio db

      En el servicio db se utiliza una imagen previamente creada de MySQL 5.7 de Docker Hub. Debido a que en Docker Compose se cargan automáticamente los archivos de variable .env ubicados en el mismo directorio que el archivo docker-compose.yml, podemos obtener la configuración de nuestra base de datos del archivo Laravel .env que creamos en un paso anterior.

      Incluya la siguiente definición de servicio en el nodo services, justo después del servicio app:

      docker-compose.yml

        db:
          image: mysql:5.7
          container_name: travellist-db
          restart: unless-stopped
          environment:
            MYSQL_DATABASE: ${DB_DATABASE}
            MYSQL_ROOT_PASSWORD: ${DB_PASSWORD}
            MYSQL_PASSWORD: ${DB_PASSWORD}
            MYSQL_USER: ${DB_USERNAME}
            SERVICE_TAGS: dev
            SERVICE_NAME: mysql
          volumes:
            - ./docker-compose/mysql:/docker-entrypoint-initdb.d
          networks:
            - travellist
      

      Estas configuraciones hacen lo siguiente:

      • image: define la imagen de Docker que debe utilizarse para este contenedor. En este caso, usamos una imagen MySQL 5.7 de Docker Hub.
      • container_name: establece el nombre del contenedor para el servicio travellist-db.
      • restart: reinicie siempre este servicio, a menos que se detenga de forma explicita.
      • environment: define las variables de entorno en el nuevo contenedor. Usaremos valores obtenidos del archivo Laravel .env para establecer nuestro servicio de MySQL, que creará automáticamente una nueva base de datos y usuario basadas en las variables de entorno proporcionadas.
      • volumes: crea un volumen para compartir un volcado de base de datos .sql que se usará para inicializar la base de datos de la aplicación. En la imagen de MySQL se importarán automáticamente archivos .sql ubicados en el directorio /docker-entrypoint-initdb.d dentro del contenedor.
      • networks: configura este servicio para que se utilice una red llamada travellist.

      El servicio nginx

      En el servicio nginx se utiliza una imagen previamente creada de Nginx en la parte superior de Alpine, una distribución ligera de Linux. Se crea un contenedor llamado travellist-nginx y se utiliza la definición ports para crear un redireccionamiento del puerto 8000 en el sistema host al puerto 80 dentro del contenedor.

      Incluya la siguiente definición de servicio en su nodo services, justo después del servicio db:

      docker-compose.yml

        nginx:
          image: nginx:1.17-alpine
          container_name: travellist-nginx
          restart: unless-stopped
          ports:
            - 8000:80
          volumes:
            - ./:/var/www
            - ./docker-compose/nginx:/etc/nginx/conf.d
          networks:
            - travellist
      

      Esta configuración hace lo siguiente:

      • image: define la imagen de Docker que debe utilizarse para este contenedor. En este caso, usaremos la imagen Alpine Nginx 1.17.
      • container_name: establece el nombre del contenedor para el servicio travellist-nginx.
      • restart: reinicie siempre este servicio, a menos que se detenga de forma explicita.
      • ports: establece un redireccionamiento de puerto que permitirá el acceso externo a través del puerto 8000 al servidor web que se ejecuta en el puerto 80 dentro del contenedor.
      • volumes: crea dos volúmenes compartidos. El primero sincronizará los contenidos del directorio actual con /var/www dentro del contenedor. De esta manera, cuando realice cambios locales en los archivos de la aplicación, se reflejarán rápidamente en la aplicación proporcionada por Nginx dentro del contenedor. En el segundo volumen, se asegurará que nuestro archivo de configuración de Nginx, ubicado en docker-compose/nginx/travellist.conf, se copie a la carpeta de configuración de Nginx del contenedor.
      • networks: configura este servicio para que se utilice una red llamada travellist.

      Archivo docker-compose.yml terminado

      Este es el aspecto de nuestro archivo docker-compose.yml terminado:

      docker-compose.yml

      version: "3.7"
      services:
        app:
          build:
            args:
              user: sammy
              uid: 1000
            context: ./
            dockerfile: Dockerfile
          image: travellist
          container_name: travellist-app
          restart: unless-stopped
          working_dir: /var/www/
          volumes:
            - ./:/var/www
          networks:
            - travellist
      
        db:
          image: mysql:5.7
          container_name: travellist-db
          restart: unless-stopped
          environment:
            MYSQL_DATABASE: ${DB_DATABASE}
            MYSQL_ROOT_PASSWORD: ${DB_PASSWORD}
            MYSQL_PASSWORD: ${DB_PASSWORD}
            MYSQL_USER: ${DB_USERNAME}
            SERVICE_TAGS: dev
            SERVICE_NAME: mysql
          volumes:
            - ./docker-compose/mysql:/docker-entrypoint-initdb.d
          networks:
            - travellist
      
        nginx:
          image: nginx:alpine
          container_name: travellist-nginx
          restart: unless-stopped
          ports:
            - 8000:80
          volumes:
            - ./:/var/www
            - ./docker-compose/nginx:/etc/nginx/conf.d/
          networks:
            - travellist
      
      networks:
        travellist:
          driver: bridge
      

      Asegúrese de guardar el archivo cuando termine.

      Paso 6: Ejecutar la aplicación con Docker Compose

      Ahora, usaremos comandos docker-compose para crear la imagen de la aplicación y ejecutar los servicios que especificamos en nuestra configuración.

      Cree la imagen app con el siguiente comando:

      La aplicación de este comando puede tardar unos minutos en completarse. Verá un resultado similar a este:

      Output

      Building app Step 1/11 : FROM php:7.4-fpm ---> fa37bd6db22a Step 2/11 : ARG user ---> Running in f71eb33b7459 Removing intermediate container f71eb33b7459 ---> 533c30216f34 Step 3/11 : ARG uid ---> Running in 60d2d2a84cda Removing intermediate container 60d2d2a84cda ---> 497fbf904605 Step 4/11 : RUN apt-get update && apt-get install -y git curl libpng-dev libonig-dev ... Step 7/11 : COPY --from=composer:latest /usr/bin/composer /usr/bin/composer ---> e499f74896e3 Step 8/11 : RUN useradd -G www-data,root -u $uid -d /home/$user $user ---> Running in 232ef9c7dbd1 Removing intermediate container 232ef9c7dbd1 ---> 870fa3220ffa Step 9/11 : RUN mkdir -p /home/$user/.composer && chown -R $user:$user /home/$user ---> Running in 7ca8c0cb7f09 Removing intermediate container 7ca8c0cb7f09 ---> 3d2ef9519a8e Step 10/11 : WORKDIR /var/www ---> Running in 4a964f91edfa Removing intermediate container 4a964f91edfa ---> 00ada639da21 Step 11/11 : USER $user ---> Running in 9f8e874fede9 Removing intermediate container 9f8e874fede9 ---> fe176ff4702b Successfully built fe176ff4702b Successfully tagged travellist:latest

      Cuando se complete la compilación, podrá ejecutar el entorno en el modo de segundo plano con lo siguiente:

      Output

      Creating travellist-db ... done Creating travellist-app ... done Creating travellist-nginx ... done

      Con esto, se ejecutarán sus contenedores en segundo plano. Para mostrar información sobre el estado de sus servicios activos, ejecute lo siguiente:

      Verá resultados como este:

      Output

      Name Command State Ports ------------------------------------------------------------------------------- travellist-app docker-php-entrypoint php-fpm Up 9000/tcp travellist-db docker-entrypoint.sh mysqld Up 3306/tcp, 33060/tcp travellist-nginx nginx -g daemon off; Up 0.0.0.0:8000->80/tcp

      Su entorno ahora estárá configurado y en funcionamiento, pero aún debemos ejecutar algunos comandos para finalizar la configuración de la aplicación. Puede usar el comando docker-compose exec para ejecutar comandos en los contenedores de servicios, como ls -l. Este muestra información detallada sobre los archivos en el directorio de la aplicación:

      • docker-compose exec app ls -l

      Output

      total 256 -rw-rw-r-- 1 sammy 1001 738 Jan 15 16:46 Dockerfile -rw-rw-r-- 1 sammy 1001 101 Jan 7 08:05 README.md drwxrwxr-x 6 sammy 1001 4096 Jan 7 08:05 app -rwxr-xr-x 1 sammy 1001 1686 Jan 7 08:05 artisan drwxrwxr-x 3 sammy 1001 4096 Jan 7 08:05 bootstrap -rw-rw-r-- 1 sammy 1001 1501 Jan 7 08:05 composer.json -rw-rw-r-- 1 sammy 1001 179071 Jan 7 08:05 composer.lock drwxrwxr-x 2 sammy 1001 4096 Jan 7 08:05 config drwxrwxr-x 5 sammy 1001 4096 Jan 7 08:05 database drwxrwxr-x 4 sammy 1001 4096 Jan 15 16:46 docker-compose -rw-rw-r-- 1 sammy 1001 1015 Jan 15 16:45 docker-compose.yml -rw-rw-r-- 1 sammy 1001 1013 Jan 7 08:05 package.json -rw-rw-r-- 1 sammy 1001 1405 Jan 7 08:05 phpunit.xml drwxrwxr-x 2 sammy 1001 4096 Jan 7 08:05 public -rw-rw-r-- 1 sammy 1001 273 Jan 7 08:05 readme.md drwxrwxr-x 6 sammy 1001 4096 Jan 7 08:05 resources drwxrwxr-x 2 sammy 1001 4096 Jan 7 08:05 routes -rw-rw-r-- 1 sammy 1001 563 Jan 7 08:05 server.php drwxrwxr-x 5 sammy 1001 4096 Jan 7 08:05 storage drwxrwxr-x 4 sammy 1001 4096 Jan 7 08:05 tests -rw-rw-r-- 1 sammy 1001 538 Jan 7 08:05 webpack.mix.js

      Ahora, ejecutaremos composer install para instalar las dependencias de la aplicación:

      • docker-compose exec app composer install

      Verá resultados como este:

      Output

      Loading composer repositories with package information Installing dependencies (including require-dev) from lock file Package operations: 85 installs, 0 updates, 0 removals - Installing doctrine/inflector (1.3.1): Downloading (100%) - Installing doctrine/lexer (1.2.0): Downloading (100%) - Installing dragonmantank/cron-expression (v2.3.0): Downloading (100%) - Installing erusev/parsedown (1.7.4): Downloading (100%) - Installing symfony/polyfill-ctype (v1.13.1): Downloading (100%) - Installing phpoption/phpoption (1.7.2): Downloading (100%) - Installing vlucas/phpdotenv (v3.6.0): Downloading (100%) - Installing symfony/css-selector (v5.0.2): Downloading (100%) … Generating optimized autoload files > IlluminateFoundationComposerScripts::postAutoloadDump > @php artisan package:discover --ansi Discovered Package: facade/ignition Discovered Package: fideloper/proxy Discovered Package: laravel/tinker Discovered Package: nesbot/carbon Discovered Package: nunomaduro/collision Package manifest generated successfully.

      Lo último que debemos hacer antes de probar la aplicación es generar una clave de aplicación única con la herramienta de línea de comandos de Laravel artisan. Esta clave se utiliza para cifrar las sesiones de los usuarios y otros datos confidenciales:

      • docker-compose exec app php artisan key:generate

      Output

      Application key set successfully.

      Ahora diríjase a su navegador y acceda al nombre de dominio o dirección IP de su servidor en el puerto 8000:

      http://server_domain_or_IP:8000
      

      Verá una página como la siguiente:

      Aplicación de demostración de Laravel

      Puede usar el comando logs para verificar los registros generados por sus servicios:

      • docker-compose logs nginx
      Attaching to travellist-nginx
      travellist-nginx | 192.168.160.1 - - [23/Jan/2020:13:57:25 +0000] "GET / HTTP/1.1" 200 626 "-" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.88 Safari/537.36"
      travellist-nginx | 192.168.160.1 - - [23/Jan/2020:13:57:26 +0000] "GET /favicon.ico HTTP/1.1" 200 0 "http://localhost:8000/" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.88 Safari/537.36"
      travellist-nginx | 192.168.160.1 - - [23/Jan/2020:13:57:42 +0000] "GET / HTTP/1.1" 200 626 "-" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.88 Safari/537.36"
      …
      

      Si desea pausar su entorno de Docker Compose mientras mantiene el estado de todos sus servicios, ejecute lo siguiente:

      Output

      Pausing travellist-db ... done Pausing travellist-nginx ... done Pausing travellist-app ... done

      Luego podrá reanudar sus servicios con lo siguiente:

      Output

      Unpausing travellist-app ... done Unpausing travellist-nginx ... done Unpausing travellist-db ... done

      Para cerrar su entorno de Docker Compose y eliminar por completo sus contenedores, redes y volúmenes, ejecute lo siguiente:

      Output

      Stopping travellist-nginx ... done Stopping travellist-db ... done Stopping travellist-app ... done Removing travellist-nginx ... done Removing travellist-db ... done Removing travellist-app ... done Removing network travellist-laravel-demo_travellist

      Para acceder a una visión general de todos los comandos de Docker Compose, consulte la referencia de línea de comandos de Docker Compose.

      Conclusión

      A lo largo de esta guía, configuramos un entorno de Docker con tres contenedores usando Docker Compose para definir nuestra infraestructura en un archivo YAML.

      A partir de este punto, puede trabajar en su aplicación de Laravel sin necesidad de instalar y configurar un servidor web local para el desarrollo y las pruebas. Además, trabajará con un entorno disponible que se puede replicar y distribuir de forma sencilla, lo cual puede resultarle útil al desarrollar su aplicación y realizar la transición a un entorno de producción.



      Source link

      Disponer en contenedor una aplicación de Node.js para el desarrollo con Docker Compose


      Introducción

      Si desarrolla activamente una aplicación, usando Docker, puede simplificar su flujo de trabajo y el proceso de implementación de su aplicación para producción. El trabajo con contenedores en tareas de desarrollo tiene los siguientes beneficios:

      • Los entornos son uniformes, lo cual significa que puede elegir los lenguajes y las dependencias que desee para su proyecto sin tener que preocuparse por posibles conflictos del sistema.
      • Los entornos están aislados. Esto facilita la resolución de problemas y la admisión de nuevos miembros del equipo.
      • Los entornos son portátiles; esto permite empaquetar y compartir su código con otros.

      A través de este tutorial, verá la forma de configurar un entorno de desarrollo para una aplicación de Node.js usando Docker. Creará dos contenedores: uno para la aplicación de Node y otro para la base de datos de MongoDB, con Docker Compose. Debido a que esta aplicación funciona con Node y MongoDB, nuestra configuración realizará lo siguiente:

      • Sincronizar el código de la aplicación del host con el código del contenedor para facilitar los cambios durante el desarrollo.
      • Garantizar que los cambios al código de la aplicación funcionen sin un reinicio.
      • Crear una base de datos protegida por nombre de usuario y contraseña para los datos de la aplicación.
      • Hacer que los datos sean persistentes.

      Al finalizar este tutorial, contará con una aplicación de información sobre tiburones en funcionamiento en contenedores de Docker:

      Completar la colección de tiburones

      Requisitos previos

      Para seguir este tutorial, necesitará lo siguiente:

      Paso 1: Clonar el proyecto y modificar las dependencias

      El primer paso para crear esta configuración será clonar el código del proyecto y modificar su archivo package.json, que incluye las dependencias del proyecto. Añadiremos nodemon a devDependencies del proyectoy especificaremos que lo utilizaremos durante el desarrollo. Ejecutar la aplicación con nodemon garantiza que se reiniciará automáticamente cuando realice cambios en su código.

      Primero, clone el repositorio nodejs-mongo-mongoose desde la cuenta de GitHub de la comunidad de DigitalOcean. Este repositorio incluye el código de la configuración descrita en el artículo Cómo integrar MongoDB con su aplicación de Node, en el que se explica la manera de integrar una base de datos de MongoDB con una aplicación de Node existente usando Mongoose.

      Clone el repositorio en un directorio llamado node_project:

      • git clone https://github.com/do-community/nodejs-mongo-mongoose.git node_project

      Diríjase al directorio node_project:

      Abra el archivo package.json del proyecto usando nano o su editor favorito:

      Debajo de las dependencias del proyecto y encima de la llave de cierre, cree un nuevo objeto devDependencies que incluya nodemon:

      ~/node_project/package.json

      ...
      "dependencies": {
          "ejs": "^2.6.1",
          "express": "^4.16.4",
          "mongoose": "^5.4.10"
        },
        "devDependencies": {
          "nodemon": "^1.18.10"
        }    
      }
      

      Guarde y cierre el archivo cuando haya terminado de editar.

      Una vez que se implemente el código del proyecto y se modifiquen sus dependencias, podrá proceder a refactorizar el código para un flujo de trabajo en contenedor.

      Paso 2: Configurar su aplicación para que funcione con contenedores

      Modificar nuestra aplicación para un flujo de trabajo en contenedores implica hacer que nuestro código sea más modular. Los contenedores ofrecen portabilidad entre entornos, y nuestro código debería reflejar esto manteniendo un nivel de disociación lo más alto posible respecto del sistema operativo subyacente. A fin de lograr esto, refactorizaremos nuestro código para hacer un mayor uso de la propiedad process.env de Node, que muestra un objeto con información sobre su entorno de usuario en el tiempo de ejecución. Podemos usar este objeto en nuestro código para asignar de forma dinámica información de la configuración en el tiempo de ejecución con variables de entorno.

      Comenzaremos con apps.js, nuestro punto de entrada principal para la aplicación. Abra el archivo:

      Dentro, verá una definición para una constante port, además de una función listen que utiliza esta constante para especificar el puerto en el que la aplicación escuchará.

      ~/home/node_project/app.js

      ...
      const port = 8080;
      ...
      app.listen(port, function () {
        console.log('Example app listening on port 8080!');
      });
      

      Redefiniremos la constante port para permitir la asignación dinámica en el tiempo de ejecución usando el objeto process.env. Realice los siguientes cambios en la definición de la constante y la función listen:

      ~/home/node_project/app.js

      ...
      const port = process.env.PORT || 8080;
      ...
      app.listen(port, function () {
        console.log(`Example app listening on ${port}!`);
      });
      

      Con nuestra nueva definición de la constante, port se asigna de forma dinámica usando el valor transmitido en el tiempo de ejecución o 8080. De modo similar, reescribimos la función listen para que use un literal de plantilla que interpolará el valor del puerto al escuchar conexiones. Debido a que asignaremos nuestros puertos en otra parte, estas revisiones evitarán que debamos revisar continuamente este archivo a medida que nuestro entorno cambie.

      Una vez que finalice la edición, guarde y cierre el archivo.

      A continuación, modificaremos la información de conexión de nuestra base de datos para eliminar cualquier credencial de configuración. Abra el archivo db.js, que contiene esta información:

      Actualmente, el archivo hace lo siguiente:

      • Importa Mongoose, el asignador de objeto a documento (ODM) que usaremos para crear esquemas y modelos para los datos de nuestra aplicación.
      • Establece las credenciales de la base de datos como constantes, incluidos el nombre de usuario y la contraseña.
      • Establece conexión con la base de datos usando el método mongoose.connect.

      Para obtener más información sobre el archivo, consulte el paso 3 de Cómo integrar MongoDB con su aplicación de Node.

      Nuestro primer paso para modificar el archivo será redefinir las constantes que incluyan información confidencial. Actualmente, estas constantes tendrán este aspecto:

      ~/node_project/db.js

      ...
      const MONGO_USERNAME = 'sammy';
      const MONGO_PASSWORD = 'your_password';
      const MONGO_HOSTNAME = '127.0.0.1';
      const MONGO_PORT = '27017';
      const MONGO_DB = 'sharkinfo';
      ...
      

      En vez de realizar una codificación rígida de esta información, puede usar el objeto process.env a fin de capturar los valores del tiempo de ejecución para estas constantes: Modifique el bloque para que tenga este aspecto:

      ~/node_project/db.js

      ...
      const {
        MONGO_USERNAME,
        MONGO_PASSWORD,
        MONGO_HOSTNAME,
        MONGO_PORT,
        MONGO_DB
      } = process.env;
      ...
      

      Guarde y cierre el archivo cuando haya terminado de editar.

      En este punto, habrá modificado db.jspara que funcione con las variables de entorno de su aplicación, pero aún necesita una forma de pasar estas variables a su aplicación. Crearemos un archivo .env con valores que pueda pasar a su aplicación en el tiempo de ejecución.

      Abra el archivo:

      Este archivo incluirá la información que eliminó de db.js: el nombre de usuario y la contraseña para la base de su aplicación, además del ajuste del puerto y el nombre de la base de datos. Recuerde actualizar el nombre de usuario, la contraseña y el nombre de la base de datos que se muestran aquí con su propia información:

      ~/node_project/.env

      MONGO_USERNAME=sammy
      MONGO_PASSWORD=your_password
      MONGO_PORT=27017
      MONGO_DB=sharkinfo
      

      Tenga en cuenta que eliminamos el ajuste de host que originalmente aparecía en db.js. Ahora definiremos nuestro host a nivel del archivo de Docker Compose, junto con información adicional sobre nuestros servicios y contenedores.

      Guarde y cierre este archivo cuando concluya la edición.

      Debido a que su archivo .env contiene información confidencial, le convendrá asegurarse de que se incluya en los archivos .dockerignore y .gitignore de su proyecto para que no realice copias al control de su versión o a los contenedores.

      Abra su archivo .dockerignore:

      Añada la siguiente línea a la parte inferior del archivo:

      ~/node_project/.dockerignore

      ...
      .gitignore
      .env
      

      Guarde y cierre el archivo cuando haya terminado de editar.

      El archivo .gitignore de este repositorio ya incluye .env, pero verifique que esté allí:

      ~~/node_project/.gitignore

      ...
      .env
      ...
      

      En este punto, habrá extraído correctamente la información confidencial del código de su proyecto y tomado medidas para controlar la forma y la ubicación en que se copia esta información. Ahora, podrá aportar más solidez al código de conexión de su base de datos a fin de optimizarlo para un flujo de trabajo en contenedor.

      Paso 3: Modificar ajustes de conexión de la base de datos

      Nuestro siguiente paso será hacer que el método de conexión de nuestra base de datos sea más sólido añadiendo código que gestione los casos en los que nuestra aplicación no se conecte con nuestra base de datos. Sumar este nivel de resistencia al código de su aplicación es una práctica recomendada cuando se trabaja con contenedores usando Compose.

      Abra db.js para editarlo:

      Verá el código que añadimos antes, junto con la constante url para la URI de conexión de Mongo y el método connect de Mongoose:

      ~/node_project/db.js

      ...
      const {
        MONGO_USERNAME,
        MONGO_PASSWORD,
        MONGO_HOSTNAME,
        MONGO_PORT,
        MONGO_DB
      } = process.env;
      
      const url = `mongodb://${MONGO_USERNAME}:${MONGO_PASSWORD}@${MONGO_HOSTNAME}:${MONGO_PORT}/${MONGO_DB}?authSource=admin`;
      
      mongoose.connect(url, {useNewUrlParser: true});
      

      Actualmente, nuestro método connect acepta una opción que indica a Mongoose que utilice el nuevo analizador de URL de Mongo. Añadiremos algunas opciones más a este método para definir parámetros para intentos de reconexión. Podemos hacer esto creando una constante options que incluya la información pertinente, además de usar la opción del nuevo analizador de URL. En sus constantes de Mongo, añada la siguiente definición para una constante options:

      ~/node_project/db.js

      ...
      const {
        MONGO_USERNAME,
        MONGO_PASSWORD,
        MONGO_HOSTNAME,
        MONGO_PORT,
        MONGO_DB
      } = process.env;
      
      const options = {
        useNewUrlParser: true,
        reconnectTries: Number.MAX_VALUE,
        reconnectInterval: 500,
        connectTimeoutMS: 10000,
      };
      ...
      

      La opción reconnectTries indica a Mongoose que siga intentando establecer conexión indefinidamente, mientras que reconnectInterval define el período entre los intentos de conexión en milisegundos. connectTimeoutMS define 10 segundos como el período que el controlador de Mongo esperará antes de que falle el intento de conexión.

      Ahora podemos usar la nueva constante options en el método connect de Mongoose para ajustar nuestra configuración de conexión de Mongoose. También añadiremos una promesa para manejar los posibles errores de conexión.

      En este momento, el método connect de Mongoose tiene este aspecto:

      ~/node_project/db.js

      ...
      mongoose.connect(url, {useNewUrlParser: true});
      

      Elimine el método connect existente y sustitúyalo por el siguiente código, que incluye la constante options y una promesa:

      ~/node_project/db.js

      ...
      mongoose.connect(url, options).then( function() {
        console.log('MongoDB is connected');
      })
        .catch( function(err) {
        console.log(err);
      });
      

      En caso de que la conexión se realice correctamente, nuestra función registrará un mensaje correspondiente; de lo contrario, aplicará catch al error y lo registrará para que podamos resolverlo.

      El archivo terminado tendrá este aspecto:

      ~/node_project/db.js

      const mongoose = require('mongoose');
      
      const {
        MONGO_USERNAME,
        MONGO_PASSWORD,
        MONGO_HOSTNAME,
        MONGO_PORT,
        MONGO_DB
      } = process.env;
      
      const options = {
        useNewUrlParser: true,
        reconnectTries: Number.MAX_VALUE,
        reconnectInterval: 500,
        connectTimeoutMS: 10000,
      };
      
      const url = `mongodb://${MONGO_USERNAME}:${MONGO_PASSWORD}@${MONGO_HOSTNAME}:${MONGO_PORT}/${MONGO_DB}?authSource=admin`;
      
      mongoose.connect(url, options).then( function() {
        console.log('MongoDB is connected');
      })
        .catch( function(err) {
        console.log(err);
      });
      

      Guarde y cierre el archivo cuando concluya la edición.

      Con esto, habrá añadido resistencia al código de su aplicación para gestionar los casos en los cuales fuera posible que esta no pudiera establecer conexión con su base de datos. Una vez establecido este código, puede proceder a definir sus servicios con Compose.

      Paso 4: Definir servicios con Docker Compose

      Una vez refactorizado su código, estará listo para escribir el archivo docker-compose.yml con las definiciones de su servicio. Un servicio en Compose es un contenedor en ejecución y las definiciones del servicio, que incluirá en su archivo docker-compose.yml, contienen información sobre cómo se ejecutará cada imagen del contenedor. La herramienta Compose le permite definir varios servicios para crear aplicaciones en diferentes contenedores.

      Antes de definir nuestros servicios, sin embargo, añadiremos una herramienta a nuestro proyecto llamada wait-for para garantizar que nuestra aplicación solo intente establecer conexión con nuestra base de datos una vez que las tareas de inicio de esta última se completen. Esta secuencia de comandos utiliza netcat para determinar, mediante un sondeo, si un host y puerto específicos aceptan conexiones TCP o no. Usarla le permite controlar los intentos que su aplicación realiza para establecer conexión con su base de datos determinando, mediante una prueba, si la base de datos está lista o no para aceptar conexiones.

      Aunque Compose le permite especificar dependencias entre los servicios usando la opción depends_on, esta orden se basa más en el hecho que el contenedor se ejecute o no que en el hecho de que esté preparado. Usar depends_on no será la mejor opción para nuestra configuración, pues queremos que nuestra aplicación se conecte solo cuando las se completen tareas de la base de datos, incluida la de añadir usuario y contraseña a la base de datos de autenticación de admin. Para obtener más información sobre cómo usar wait-for y otras herramientas para controlar la orden de inicio, consulte las recomendaciones pertinentes en la documentación de Compose.

      Abra un archivo llamado wait-for.sh:

      Pegue el siguiente código en el archivo para crear la función de sondeo:

      ~/node_project/app/wait-for.sh

      #!/bin/sh
      
      # original script: https://github.com/eficode/wait-for/blob/master/wait-for
      
      TIMEOUT=15
      QUIET=0
      
      echoerr() {
        if [ "$QUIET" -ne 1 ]; then printf "%sn" "$*" 1>&2; fi
      }
      
      usage() {
        exitcode="$1"
        cat << USAGE >&2
      Usage:
        $cmdname host:port [-t timeout] [-- command args]
        -q | --quiet                        Do not output any status messages
        -t TIMEOUT | --timeout=timeout      Timeout in seconds, zero for no timeout
        -- COMMAND ARGS                     Execute command with args after the test finishes
      USAGE
        exit "$exitcode"
      }
      
      wait_for() {
        for i in `seq $TIMEOUT` ; do
          nc -z "$HOST" "$PORT" > /dev/null 2>&1
      
          result=$?
          if [ $result -eq 0 ] ; then
            if [ $# -gt 0 ] ; then
              exec "$@"
            fi
            exit 0
          fi
          sleep 1
        done
        echo "Operation timed out" >&2
        exit 1
      }
      
      while [ $# -gt 0 ]
      do
        case "$1" in
          *:* )
          HOST=$(printf "%sn" "$1"| cut -d : -f 1)
          PORT=$(printf "%sn" "$1"| cut -d : -f 2)
          shift 1
          ;;
          -q | --quiet)
          QUIET=1
          shift 1
          ;;
          -t)
          TIMEOUT="$2"
          if [ "$TIMEOUT" = "" ]; then break; fi
          shift 2
          ;;
          --timeout=*)
          TIMEOUT="${1#*=}"
          shift 1
          ;;
          --)
          shift
          break
          ;;
          --help)
          usage 0
          ;;
          *)
          echoerr "Unknown argument: $1"
          usage 1
          ;;
        esac
      done
      
      if [ "$HOST" = "" -o "$PORT" = "" ]; then
        echoerr "Error: you need to provide a host and port to test."
        usage 2
      fi
      
      wait_for "$@"
      

      Guarde y cierre el archivo cuando termine de añadir el código.

      Haga que la secuencia de comandos sea ejecutable:

      A continuación, abra el archivo docker-compose.yml:

      Primero defina el servicio de la aplicación nodejs agregando el siguiente código al archivo:

      ~/node_project/docker-compose.yml

      version: '3'
      
      services:
        nodejs:
          build:
            context: .
            dockerfile: Dockerfile
          image: nodejs
          container_name: nodejs
          restart: unless-stopped
          env_file: .env
          environment:
            - MONGO_USERNAME=$MONGO_USERNAME
            - MONGO_PASSWORD=$MONGO_PASSWORD
            - MONGO_HOSTNAME=db
            - MONGO_PORT=$MONGO_PORT
            - MONGO_DB=$MONGO_DB
          ports:
            - "80:8080"
          volumes:
            - .:/home/node/app
            - node_modules:/home/node/app/node_modules
          networks:
            - app-network
          command: ./wait-for.sh db:27017 -- /home/node/app/node_modules/.bin/nodemon app.js
      

      La definición del servicio nodejs incluye las siguientes opciones:

      • build: define las opciones de configuración, incluido el context y dockerfile, que se aplicarán cuando Compose cree la imagen de la aplicación. Si desea utilizar una imagen existente de un registro como Docker Hub, podría utilizar la instrucción image como alternativa, con información sobre su nombre de usuario, repositorio y etiqueta de imagen.
      • context: esto define el contexto de compilación para la compilación de la imagen; en este caso, el directorio del proyecto actual.
      • dockerfile: esto especifica el Dockerfile del directorio actual de su directorio como el archivo que Compose usará para complilar la imagen de la aplicación. Para obtener más información sobre este archivo, consulte Cómo crear una aplicación de Node.js con Docker.
      • image y container_name: aplican nombres a la imagen y al contenedor.
      • restart: define la política de reinicio. El valor predeterminado es no, pero configuramos el contenedor para reiniciarse a menos que se detenga.
      • env_file: indica a Compose que deseamos añadir variables de entorno de un archivo llamado .env, ubicado en nuestro contexto de compilación.
      • environment: esta opción le permite añadir los ajustes de conexión de Mongo que definió en el archivo .env. Tenga en cuenta que no fijaremos NODE_ENV en development, ya que éste es el comportamiento predeterminado de Express si NODE_ENV no se configura. Cuando procedamos con la producción, podrá fijarlo en production para permitir el almacenamiento de vistas en caché y recibir menos mensajes de error confusos. Observe, además, que especificamos el contenedor de la base de datos db como host, como se explicó en el paso 2.
      • ports: asigna el puerto 80 del host al puerto 8080 del contenedor.
      • volumes: incluiremos dos tipos de montajes aquí:
        • El primero es un montaje de enlace, que monta el código de nuestra aplicación del host en el directorio /home/node/app del contenedor. Esto facilitará un desarrollo rápido, ya que cualquier cambio que realice a su código de host se completará de inmediato en el contenedor.
        • El segundo es un volumen con nombre: node_modules. Cuando Docker ejecute la instrucción npm install que se indica en el Dockerfile de la aplicación, npm creará en el contenedor un nuevo directorio node_modules en el que se incluirán los paquetes necesarios para ejecutar la aplicación. El montaje de enlace que acabamos de crear ocultará, sin embargo, este directorio node_modules recién creado. Debido a que node_modules en el host está vacío, el bind asignará un directorio vacío al contenedor, con lo cual se anulará el nuevo directorio node_modules y se evitará el inicio de nuestra aplicación. El volumen llamado node_modules resuelve este problema haciendo que persista el contenido del directorio /home/node/app/node_modules y montándolo en el contenedor, con lo cual se ocultará el enlace.

      Tenga en cuenta lo siguiente cuando utilice este enfoque:

      • Su enlace montará los contenidos del directorio node_modules del contenedor en el host y este directorio será propiedad de root, ya que el volumen nombrado fue creado por Docker.
      • Si dispone de un directorio node_modules preexistente en el host, anulará el directorio node_modules creado en el contenedor. Para configuración que estamos creando en este tutorial, se supone que no dispone de un directorio node_modules preexistente y que no trabajará con npm en su host. Esto se ajusta a un enfoque de doce factores para el desarrollo de la aplicación, que minimiza las dependencias entre los entornos de ejecución.

        • networks: especifica que nuestro servicio de aplicación se unirá a la red app-network, que definiremos al final del archivo.
        • command: esta opción le permite establecer el comando que debería ejecutarse cuando Compose ejecute la imagen. Tenga en cuenta que con esto se anulará la instrucción CMD que establecimos en el Dockerfile de nuestra aplicación. En este caso, ejecutaremos la aplicación usando la secuencia de comandos wait-for que sondeará el servicio db en el puerto 27017 para probar si el servicio de la base de datos está listo o no. Una vez que esta prueba se realice, la secuencia de comandos ejecutará el comando que establecimos, /home/node/app/node_modules/.bin/nodemon app.js, para iniciar la aplicación con nodemon. Esto garantizará que cualquier cambio futuro que realicemos en nuestro código se recargue sin que debamos reiniciar la aplicación.

      A continuación, cree el servicio db agregando el siguiente código debajo de la definición del servicio de la aplicación:

      ~/node_project/docker-compose.yml

      ...
        db:
          image: mongo:4.1.8-xenial
          container_name: db
          restart: unless-stopped
          env_file: .env
          environment:
            - MONGO_INITDB_ROOT_USERNAME=$MONGO_USERNAME
            - MONGO_INITDB_ROOT_PASSWORD=$MONGO_PASSWORD
          volumes:  
            - dbdata:/data/db   
          networks:
            - app-network  
      

      Algunos de los ajustes que definimos para el servicio nodejs seguirán siendo los mismos, pero también realizamos los siguientes cambios en las definiciones de image, environment y volumes:

      • image: para crear este servicio, Compose extraerá la imagen de Mongo 4.1.8-xenial de Docker Hub. Fijaremos una versión concreta para evitar posibles conflictos futuros a medida que la imagen de Mongo cambie. Para obtener más información sobre la fijación de la versiones, consulte la documentación de Docker en Prácticas recomendadas de Dockerfile.
      • MONGO_INITDB_ROOT_USERNAME, MONGO_INITDB_ROOT_PASSWORD: la imagen de mongo pone estas variables de entorno a disposición para que pueda modificar la inicialización de la instancia de su base de datos. MONGO_INITDB_ROOT_USERNAME y MONGO_INITDB_ROOT_PASSWORD crean juntos un usuario root en la base de datos de autenticación admin y verifican que la autenticación esté habilitada cuando se inicie el contenedor. Configuramos MONGO_INITDB_ROOT_USERNAME y MONGO_INITDB_ROOT_PASSWORD usando los valores de nuestro archivo .env, que pasamos al servicio db usando la opción env_file. Hacer esto significa que nuestro usuario de la aplicación sammy será un usuario root en la instancia de la base de datos, con acceso a todos los privilegios administrativos y operativos de esa función. Cuando trabaje en producción, le convendrá crear un usuario de aplicación dedicado con privilegios de alcance correspondiente.

        Nota: Tenga en cuenta que estas variables no se aplicarán si inicia el contenedor con un directorio de datos existente implementado.
      • dbdata:/data/db: el volumen llamado dbdata preservará los datos almacenados en /data/db, el directorio de datos predeterminado de Mongo. Esto garantizará que no pierda datos en los casos en los que detenga o elimine contenedores.

      También agregamos el servcio db a la red app-network con la opción networks.

      Como paso final, añada las definiciones de volumen y red al final del archivo:

      ~/node_project/docker-compose.yml

      ...
      networks:
        app-network:
          driver: bridge
      
      volumes:
        dbdata:
        node_modules:  
      

      La red de puente definida por el usuario app-network permite la comunicación entre nuestros contenedores, ya que están en el mismo host de demonio de Docker. Esto agiliza el tráfico y la comunicación dentro de la aplicación, ya que abre todos los puertos entre contenedores en la misma red de puente y, al mismo tiempo, no expone ningún puerto al exterior. Por lo tanto, nuestros contenedores db y nodejs pueden comunicarse entre sí y solo debemos exponer el puerto 80 para el acceso de front-end a la aplicación.

      Nuestra clave volumes de nivel nivel superior define dbdata y node_modules de los volúmenes. Cuando Docker crea volúmenes, el contenido de estos se almacena en una parte del sistema de archivos host, /var/ib/docker/volume/, que Docker administra. El contenido de cada volumen se almacena en un directorio en /var/lib/docker/volume/ y se monta en cualquier contenedor que utilice el volumen. De esta forma, los datos de la información sobre tiburones que nuestros usuarios crearán persistirán en el volumen dbdata, incluso si eliminamos y volvemos a crear el contenedor db.

      El archivo docker-compose.yml terminado tendrá este aspecto:

      ~/node_project/docker-compose.yml

      version: '3'
      
      services:
        nodejs:
          build:
            context: .
            dockerfile: Dockerfile
          image: nodejs
          container_name: nodejs
          restart: unless-stopped
          env_file: .env
          environment:
            - MONGO_USERNAME=$MONGO_USERNAME
            - MONGO_PASSWORD=$MONGO_PASSWORD
            - MONGO_HOSTNAME=db
            - MONGO_PORT=$MONGO_PORT
            - MONGO_DB=$MONGO_DB
          ports:
            - "80:8080"
          volumes:
            - .:/home/node/app
            - node_modules:/home/node/app/node_modules
          networks:
            - app-network
          command: ./wait-for.sh db:27017 -- /home/node/app/node_modules/.bin/nodemon app.js
      
        db:
          image: mongo:4.1.8-xenial
          container_name: db
          restart: unless-stopped
          env_file: .env
          environment:
            - MONGO_INITDB_ROOT_USERNAME=$MONGO_USERNAME
            - MONGO_INITDB_ROOT_PASSWORD=$MONGO_PASSWORD
          volumes:     
            - dbdata:/data/db
          networks:
            - app-network  
      
      networks:
        app-network:
          driver: bridge
      
      volumes:
        dbdata:
        node_modules:  
      

      Guarde y cierre el archivo cuando haya terminado de editar.

      Una vez implementadas las definiciones de su servicio, estará listo para iniciar la aplicación.

      Paso 5: Probar la aplicación

      Una vez implementado su archivo docker-compose.yml, puede crear sus servicios con el comando docker-compose up. También puede comprobar que sus datos persisitirán deteniendo y eliminando sus contenedores con docker-compose down.

      Primero, compile las imágenes del contenedor y cree los servicios ejecutando docker-compose up con el indicador -d, que luego ejecutará los contenedores nodejs y db en segundo plano.

      Verá un resultado que confirmará la creación de sus servicios:

      Output

      ... Creating db ... done Creating nodejs ... done

      También puede obtener información más detallada sobre los procesos de inicio mostrando el resultado del registro de los servicios:

      Si todo se inició de forma correcta, verá algo similar a esto:

      Output

      ... nodejs | [nodemon] starting `node app.js` nodejs | Example app listening on 8080! nodejs | MongoDB is connected ... db | 2019-02-22T17:26:27.329+0000 I ACCESS [conn2] Successfully authenticated as principal sammy on admin

      También puede verificar el estado de sus contenedores con docker-compose ps:

      Verá un resultado que indicará que sus contenedores están en ejecución:

      Output

      Name Command State Ports ---------------------------------------------------------------------- db docker-entrypoint.sh mongod Up 27017/tcp nodejs ./wait-for.sh db:27017 -- ... Up 0.0.0.0:80->8080/tcp

      Una vez que los servicios estén en ejecución, podrá visitar http://your_server_ip en el navegador. Verá una página de aterrizaje similar a esta:

      Página de destino de la aplicación

      Haga clic en el botón Get Shark Info. Verá una página con un formulario de entrada en el que podrá introducir un nombre del tiburón y una descripción del carácter general de este:

      Formulario de Información sobre tiburones

      En el formulario, agregue un tiburón que elija. A los efectos de esta demostración, añadiremos Megalodon Shark en el campo Shark Name y Ancient en el campo Shark Character:

      Formulario de tiburones completado

      Haga clic en el botón Submit. Visualizará una página con la siguiente información sobre tiburones que se le mostrará de nuevo:

      Resultado de tiburones

      Como paso final, podemos hacer una prueba para verificar que los datos que acaba de introducir persistan si elimina el contenedor de su base de datos.

      Cuando regrese a su terminal, escriba el siguiente comando para detener y eliminar sus contenedores y su red:

      Tenga en cuenta que no incluiremos la opción --volumes; por lo tanto, no se eliminará nuestro volumen dbdata.

      El siguiente resultado confirma que se eliminaron sus contenedores y su red:

      Output

      Stopping nodejs ... done Stopping db ... done Removing nodejs ... done Removing db ... done Removing network node_project_app-network

      Vuelva a crear los contenedores:

      Ahora, vuelva al formulario de información de tiburones:

      Formulario de información  sobre tiburones

      Introduzca un nuevo tiburón que elija. Usaremos Whale Shark y Large:

      Introduzca un nuevo tiburón

      Una vez que haga clic en Submit, verá que se agregó el nuevo tiburón a la colección de tiburones de su base de datos sin pérdida de datos que ya introdujo:

      Colección completa de tiburones

      Su aplicación ahora se ejecuta en los contenedores de Docker con la persistencia de datos y la sincronización de códigos habilitadas.

      Conclusión

      Siguiendo este tutorial, creó una configuración de desarrollo para su aplicación de Node usando contenedores de Docker. Hizo que su proyecto fuera más modular y portátil mediante la extracción de información confidencial y la desvinculación del estado de su aplicación del código de esta. También configuró un archivo docker-compose.yml estándar que podrá revisar a medida que sus necesidades y requisitos de desarrollo cambien.

      A medida que realice desarrollos, es posible que le interese aprender más sobre cómo diseñar aplicaciones para flujos de trabajo en contenedores y de Cloud Native. Consulte Crear aplicaciones para Kubernetes y Modernizar aplicaciones para Kubernetes para obtener más información sobre estos temas.

      Para obtener más información sobre el código utilizado en este tutorial, consulte Cómo crear una aplicación de Node.js con Docker y Cómo integrar MongoDB con su aplicación de Node. Para obtener información sobre cómo implementar una aplicación de Node con un proxy inverso de Nginx usando contenedores, consulte Cómo proteger una aplicación de Node.js en contenedor con Nginx, Let´s Encrypt y Docker Compose.



      Source link