One place for hosting & domains

      contenedor

      Cómo disponer en contenedor una aplicación de Ruby on Rails 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 Ruby on Rails usando Docker. Con Docker Compose, creará varios contenedores para la propia aplicación, la base de datos de PostgreSQL, Redis y un servicio Sidekiq. La configuración hará 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.
      • Conservar los datos de la aplicación entre reinicios del contenedor.
      • Configurar trabajadores de Sidekiq para que procesen las tareas como se espera

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

      Inicio de la aplicación de Sidekiq

      Requisitos previos

      Para este tutorial, necesitará lo siguiente:

      Paso 1: Clonar el proyecto y agregar dependencias

      Nuestro primer paso será clonar el repositorio de rails-sidekiq de la cuenta de GitHub comunitaria de DigitalOcean. En este repositorio se incluye el código de la configuración descrita en el artículo Cómo agregar Sidekiq y Redis a una aplicación de Ruby on Rails, en el que se explica la forma de añadir Sidekiq a un proyecto de Rails 5 existente.

      Clone el repositorio en un directorio llamado rails-docker:

      • git clone https://github.com/do-community/rails-sidekiq.git rails-docker

      Diríjase al directorio rails-docker​​​:

      En este tutorial, usaremos PostgreSQL como base de datos. Para trabajar con PostgreSQL en lugar de SQLite 3, deberá añadir la gema pg a las dependencias del proyecto, que se enumeran en su Gemfile. Abra ese archivo para la edición usando nano o su editor favorito:

      Añada la gema en cualquier punto de las dependencias del proyecto principal (encima de las dependencias de desarrollo):

      ~/rails-docker/Gemfile

      . . .
      # Reduces boot times through caching; required in config/boot.rb
      gem 'bootsnap', '>= 1.1.0', require: false
      gem 'sidekiq', '~>6.0.0'
      gem 'pg', '~>1.1.3'
      
      group :development, :test do
      . . .
      

      También se puede excluir la gema sqlite, ya que dejaremos de usarla:

      ~/rails-docker/Gemfile

      . . .
      # Use sqlite3 as the database for Active Record
      # gem 'sqlite3'
      . . .
      

      Por último, excluya la gema spring-watcher-listen en development:

      ~/rails-docker/Gemfile

      . . .
      gem 'spring'
      # gem 'spring-watcher-listen', '~> 2.0.0'
      . . .
      

      Si no desactivamos esta gema, veremos mensajes de error persistentes al acceder a la consola de Rails. Estos mensajes de error derivan del hecho de que esta gema hace que Rails use listen para mantenrse informado sobre cambios en el desarrollo, en lugar de sondear el sistema de archivos en busca de cambios. Debido a que esta gema supervisa la raíz del proyecto, incluido el directorio node_modules, emitirá mensajes de error sobre los directorios controlados, lo que sobrecargará la consola. Sin embargo, si se ocupa de conservar los recursos de CPU, es posible que la desactivación de esta gema no funcione para usted. En este caso, puede ser recomendable actualizar su aplicación de Rails a Rails 6.

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

      Una vez que se establezca el repositorio de su proyecto, se añada la gema pg a su Gemfile y se excluya la gema spring-watcher-listen, estará listo para configurar su aplicación y trabajar con PostgreSQL.

      Paso 2: Configurar la aplicación para que funcione con PostgreSQL y Redis

      Para trabajar con PostgreSQL y Redis en proyectos de desarrollo, nos convendrá hacer lo siguiente:

      • Configurar la aplicación para que funcione con PostgreSQL como adaptador predeterminado.
      • Añadir un archivo .env al proyecto con el nombre de usuario y la contraseña de nuestra base de datos y el host de Redis.
      • Crear una secuencia de comandos init.sql a fin de generar un usuario sammy para la base de datos.
      • Añadir un inicializador para Sidekiq, de modo que pueda funcionar con nuestro servicio redis en contenedor.
      • Añadir el archivo .env y otros archivos pertinentes a los archivos gitignore y dockerignore del proyecto.
      • Crear semillas de base de datos para que nuestra aplicación tenga algunos registros con los que podamos trabajar cuando la iniciemos.

      Primero, abra el archivo de configuración de su base de datos, ubicado en config/database.yml:

      Actualmente, en el archivo se incluye la siguiente configuración predeterminada, que se aplica en caso de que no haya otra disponible:

      ~/rails-docker/config/database.yml

      default: &default
        adapter: sqlite3
        pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
        timeout: 5000
      

      Debemos cambiarla para que refleje el hecho de que usaremos el adaptador postgresql, ya que crearemos un servicio PostgreSQL con Docker Compose para conservar los datos de nuestra aplicación.

      Elimine el código que establece SQLite como adaptador y sustitúyalo por la siguiente configuración, que acondicionará el adaptador de forma adecuada junto con las demás variables necesarias para establecer conexión:

      ~/rails-docker/config/database.yml

      default: &default
        adapter: postgresql
        encoding: unicode
        database: <%= ENV['DATABASE_NAME'] %>
        username: <%= ENV['DATABASE_USER'] %>
        password: <%= ENV['DATABASE_PASSWORD'] %>
        port: <%= ENV['DATABASE_PORT'] || '5432' %>
        host: <%= ENV['DATABASE_HOST'] %>
        pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
        timeout: 5000
      . . .
      

      A continuación, modificaremos la configuración del entorno development, ya que este es el entorno que usaremos en esta configuración.

      Elimine la configuración de base de datos de SQLite existente de modo que la sección tenga el siguiente aspecto:

      ~/rails-docker/config/database.yml

      . . .
      development:
        <<: *default
      . . .
      

      Por último, elimine la configuración de database para los entornos production y test:

      ~/rails-docker/config/database.yml

      . . .
      test:
        <<: *default
      
      production:
        <<: *default
      . . .
      

      Estas modificaciones de la configuración de nuestra base de datos predeterminada nos permitirán establecer la información de nuestra base de datos de forma dinámica usando variables de entorno definidas en los archivos .env, que no se asignarán al control de versiones.

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

      Tenga en cuenta que si crea un proyecto de Rails desde cero, puede configurar el adaptador con el comando de rails new, como se indica en el Paso 3 de Cómo usar PostgreSQL con su aplicación de Ruby on Rails en Ubuntu 18.04. De esta forma, su adaptador quedará configurado en config/database.yml y se agregará automáticamente la gema pg al proyecto.

      Ahora que hicimos referencia a nuestras variables de entorno, podemos crear un archivo para ellas con nuestra configuración preferida. Extraer ajustes de configuración de esta manera forma parte del enfoque de 12 factores para el desarrollo de aplicaciones, que define las prácticas recomendadas para lograr resiliencia en aplicaciones en entornos distribuidos. Ahora, cuando configuremos nuestro entorno de producción y prueba en el futuro, definir los ajustes de nuestra base de datos implicará crear archivos adicionales .env y hacer referencia al archivo correspondiente de nuestros archivos de Docker Compose.

      Abra un archivo .env:

      Añada los siguientes valores al archivo:

      ~/rails-docker/.env

      DATABASE_NAME=rails_development
      DATABASE_USER=sammy
      DATABASE_PASSWORD=shark
      DATABASE_HOST=database
      REDIS_HOST=redis
      

      Además de establecer el nombre, el usuario y la contraseña de nuestra base de datos, fijamos un valor para DATABASE_HOST. El valor, database, hace referencia al servicio database de PostgreSQL que crearemos con Docker Compose. También configuramos un REDIS_HOST para especificar nuestro servicio de redis.

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

      Para crear el usuario de base de datos sammy, podemos escribir una secuencia de comandos init.sql que podemos montar en el contenedor de base de datos cuando se inicia.

      Abra el archivo de secuencia de comandos:

      Añada el siguiente código para crear un usuario sammy con privilegios administrativos:

      ~/rails-docker/init.sql

      CREATE USER sammy;
      ALTER USER sammy WITH SUPERUSER;
      

      Esta secuencia de comandos creará el usuario apropiado en la base de datos y otorgará a este privilegios administrativos.

      Establezca los permisos apropiados en la secuencia de comandos:

      A continuación, configuraremos Sidekiq para que funcione con nuestro servicio de redis en contenedor. Podemos añadir un inicializador al directorio config/initializers, en el que Rails busca ajustes de configuración una vez que se cargan los marcos y los complementos, con lo cual se establece un valor para un host de Redis.

      Abra un archivo sidekiq.rb para especificar estas configuraciones:

      • nano config/initializers/sidekiq.rb

      Añada el siguiente código al archivo para especificar valores de REDIS_HOST y REDIS_PORT:

      ~/rails-docker/config/initializers/sidekiq.rb

      Sidekiq.configure_server do |config|
        config.redis = {
          host: ENV['REDIS_HOST'],
          port: ENV['REDIS_PORT'] || '6379'
        }
      end
      
      Sidekiq.configure_client do |config|
        config.redis = {
          host: ENV['REDIS_HOST'],
          port: ENV['REDIS_PORT'] || '6379'
        }
      end
      

      Al igual que los ajustes de configuración de nuestra base de datos, estos ajustes nos permiten establecer los parámetros de hosts y puertos de forma dinámica, lo cual nos permite sustituir los valores apropiados en el tiempo de ejecución sin necesidad de modificar el propio código de la aplicación. Además de un REDIS_HOST, tenemos un valor predeterminado establecido para REDIS_PORT en caso de que no esté configurado en otro lugar.

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

      A continuación, para garantizar que los datos confidenciales de nuestra aplicación no se copien al control de versiones, podemos añadir .env al archivo .gitignore de nuestro proyecto, lo cual indica a Git los archivos que se deben ignorar en nuestro proyecto. Abra el archivo para editarlo:

      En la parte inferior del archivo, añada una entrada para .env:

      ~/rails-docker/.gitignore

      yarn-debug.log*
      .yarn-integrity
      .env
      

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

      A continuación, crearemos un archivo .dockerignore para establecer lo que no debe copiarse a nuestros contenedores. Abra el archivo para editarlo:

      Añada al archivo el siguiente código, el cual indica a Docker que ignore algunas de las cosas que no necesitamos copiar a nuestros contenedores:

      ~/rails-docker/.dockerignore

      .DS_Store
      .bin
      .git
      .gitignore
      .bundleignore
      .bundle
      .byebug_history
      .rspec
      tmp
      log
      test
      config/deploy
      public/packs
      public/packs-test
      node_modules
      yarn-error.log
      coverage/
      

      Añada .env al final de este archivo:

      ~/rails-docker/.dockerignore

      . . .
      yarn-error.log
      coverage/
      .env
      

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

      Como paso final, crearemos algunos datos semilla de modo que nuestra aplicación tenga algunos registros cuando la iniciemos.

      Abra un archivo para los datos semilla del directorio db:

      Añada el siguiente código al archivo para crear cuatro tiburones y una publicación de muestra:

      ~/rails-docker/db/seeds.rb

      # Adding demo sharks
      sharks = Shark.create([{ name: 'Great White', facts: 'Scary' }, { name: 'Megalodon', facts: 'Ancient' }, { name: 'Hammerhead', facts: 'Hammer-like' }, { name: 'Speartooth', facts: 'Endangered' }])
      Post.create(body: 'These sharks are misunderstood', shark: sharks.first)
      

      Estos datos semilla crearán cuatro tiburones y una publicación asociada al primero.

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

      Una vez que configure su aplicación para que funcione con PostgreSQL y cree sus variables de entorno, estará listo para escribir el Dockerfile de su aplicación.

      Paso 3: Escribir las secuencias de comandos de Dockerfile y puntos de entrada.

      Su Dockerfile especifica lo que se incluirá en el contenedor de su aplicación cuando se cree. Usar Dockerfile le permite definir el entorno de su contenedor y evitar discrepancias con las dependencias o versiones de tiempo de ejecución.

      Siguiendo estas directrices sobre la creación de contenedores optimizados, haremos que nuestra imagen sea lo más eficiente posible usando una base de Alpine e intentando minimizar las capas de nuestra imagen en general.

      Abra un Dockerfile en su directorio actual:

      Las imágenes de Docker se crean usando una sucesión de imágenes en capas que se acumulan unas sobre otras. El primer paso será añadir la imagen de base para nuestra aplicación, que formará el punto de partida de la compilación de la aplicación.

      Añada el siguiente código al archivo para agregar la imagen alpine de Ruby como base:

      ~/rails-docker/Dockerfile

      FROM ruby:2.5.1-alpine
      

      La imagen alpine se deriva del proyecto Alpine Linux, y nos ayudará a mantener el tamaño de las imágenes bajo. Para obtener más información sobre si la imagen alpine es la opción correcta para su proyecto, consulte la discusión completa en la sección de variantes de imagen de la página de imágenes de Ruby de Docker Hub.

      Algunos factores que es necesario tener en cuenta a la hora de usar alpine para el desarrollo:

      • Una imagen de tamaño reducido hará que se acorten los tiempos de carga de páginas y recursos, en particular si también mantiene los volúmenes en valores mínimos. Esto le permite lograr una experiencia de usuario de desarrollo rápida y más cercana a la que habría disponible si trabajara a nivel local en un entorno no dispuesto en contenedor.
      • La paridad entre imágenes de desarrollo y producción facilita la implementación correcta. Debido a que en los equipos se suele optar por usar imágenes de Alpine en la producción para lograr beneficios en términos de velocidad, el desarrollo con una base de Alpine ayuda a resolver problemas al realizar la transición hacia la producción.

      A continuación, establezca una variable de entorno para especificar la versión de Bundler:

      ~/rails-docker/Dockerfile

      . . .
      ENV BUNDLER_VERSION=2.0.2
      

      Este es uno de los pasos que daremos para evitar conflictos de versiones entre la versión predeterminada de bundler disponible en nuestro entorno y el código de nuestra aplicación, que requiere de Bundler 2.0.2.

      A continuación, añada al Dockerfile los paquetes que necesita para trabajar con la aplicación:

      ~/rails-docker/Dockerfile

      . . .
      RUN apk add --update --no-cache 
            binutils-gold 
            build-base 
            curl 
            file 
            g++ 
            gcc 
            git 
            less 
            libstdc++ 
            libffi-dev 
            libc-dev 
            linux-headers 
            libxml2-dev 
            libxslt-dev 
            libgcrypt-dev 
            make 
            netcat-openbsd 
            nodejs 
            openssl 
            pkgconfig 
            postgresql-dev 
            python 
            tzdata 
            yarn
      

      Entre estos paquetes se incluyen nodejs y yarn. Debido a que nuestra aplicación proporciona activos con webpack, debemos incluir Node.js y Yarn para que funcione como se espera.

      Tenga en cuenta que el contenido de la imagen alpine es extremadamente reducido: los paquetes que se mencionan aquí no contemplan por completo lo que usted podría desear o necesitar durante el desarrollo, cuando disponga en un contenedor su propia aplicación.

      A continuación, instale la versión de bundler apropiada:

      ~/rails-docker/Dockerfile

      . . .
      RUN gem install bundler -v 2.0.2
      

      Este paso garantizará la paridad entre nuestro entorno en contenedor y las especificaciones del archivo Gemfile.lock de este proyecto.

      Ahora, establezca el directorio de trabajo para la aplicación en el contenedor:

      ~/rails-docker/Dockerfile

      . . .
      WORKDIR /app
      

      Copie su Gemfile y Gemfile.lock:

      ~/rails-docker/Dockerfile

      . . .
      COPY Gemfile Gemfile.lock ./
      

      Si se copian estos archivos como paso independiente y luego se aplica bundle install, no es necesario volver a compilar las gemas del proyecto cada vez que se hacen cambios en el código de su aplicación. Esto funcionará junto con el volumen de gema que incluiremos en nuestro archivo Compose, que montará gemas en el contenedor de su aplicación en los casos en que se vuelva a crear el servicio, pero las gemas del proyecto siguen siendo iguales.

      A continuación, establezca las opciones de configuración para la compilación de la gema nokogiri:

      ~/rails-docker/Dockerfile

      . . .
      RUN bundle config build.nokogiri --use-system-libraries
      . . .
      

      Con este paso se crea nokigiri con las versiones de bibliotecas libxml2 y libxslt que añadimos al contenedor de la aplicación en el paso anterior con apk RUN add

      A continuación, instale las gemas del proyecto:

      ~/rails-docker/Dockerfile

      . . .
      RUN bundle check || bundle install
      

      Antes del proceso, esta instrucción verifica que las gemas no estén ya instaladas.

      A continuación, repetiremos el mismo procedimiento que usamos con gemas con nuestros paquetes y dependencias de JavaScript. Primero, copiaremos los metadatos de paquetes, luego instalaremos dependencias y, por último, copiaremos el código de la aplicación a la imagen del contenedor.

      Para comenzar con la sección Javascript de nuestro Dockerfile, copie el package.json y yarn.lock desde el directorio de su proyecto actual del host al contenedor:

      ~/rails-docker/Dockerfile

      . . .
      COPY package.json yarn.lock ./
      

      A continuación instale los paquetes necesarios con yarn install:

      ~/rails-docker/Dockerfile

      . . .
      RUN yarn install --check-files
      

      Esta instrucción incluye una bandera --check-files con el comando yarn, una función que garantiza que no se hayan eliminado archivos previamente instalados. Como en el caso de nuestras gemas, administraremos la persistencia de los paquetes en el directorio node_modules con un volumen cuando escribamos nuestro archivo Compose.

      Por último, copie el resto del código de la aplicación e iniícela con una secuencia de comandos de punto de entrada:

      ~/rails-docker/Dockerfile

      . . .
      COPY . ./
      
      ENTRYPOINT ["./entrypoints/docker-entrypoint.sh"]
      

      El uso de una secuencia de comandos de punto de entrada nos permite ejecutar el contenedor como un ejecutable.

      El Dockerfile final tendrá el siguiente aspecto:

      ~/rails-docker/Dockerfile

      FROM ruby:2.5.1-alpine
      
      ENV BUNDLER_VERSION=2.0.2
      
      RUN apk add --update --no-cache 
            binutils-gold 
            build-base 
            curl 
            file 
            g++ 
            gcc 
            git 
            less 
            libstdc++ 
            libffi-dev 
            libc-dev 
            linux-headers 
            libxml2-dev 
            libxslt-dev 
            libgcrypt-dev 
            make 
            netcat-openbsd 
            nodejs 
            openssl 
            pkgconfig 
            postgresql-dev 
            python 
            tzdata 
            yarn
      
      RUN gem install bundler -v 2.0.2
      
      WORKDIR /app
      
      COPY Gemfile Gemfile.lock ./
      
      RUN bundle config build.nokogiri --use-system-libraries
      
      RUN bundle check || bundle install
      
      COPY package.json yarn.lock ./
      
      RUN yarn install --check-files
      
      COPY . ./
      
      ENTRYPOINT ["./entrypoints/docker-entrypoint.sh"]
      

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

      A continuación, cree un directorio llamado entry points para las secuencias de comandos de punto de entrada:

      En este directorio se incluirán nuestra secuencia de comandos de punto de entrada principal y una secuencia de comandos para nuestro servicio Sidekiq.

      Abra el archivo de la secuencia de comandos de punto de entrada de la aplicación:

      • nano entrypoints/docker-entrypoint.sh

      Añada el siguiente código al archivo:

      rails-docker/entrypoints/docker-entrypoint.sh

      #!/bin/sh
      
      set -e
      
      if [ -f tmp/pids/server.pid ]; then
        rm tmp/pids/server.pid
      fi
      
      bundle exec rails s -b 0.0.0.0
      

      La primera línea importante es set -e, e indica al shell /bin/sh que ejecuta la secuencia de comandos que genere un fallo inmediato si hay problemas más adelante en la secuencia de comandos. A continuación, la secuencia de comandos verifica que tmp/pids/server.pid no esté presente para garantizar que no se produzcan conflictos de servidores cuando iniciemos la aplicación. Por último, la secuencia de comandos inicia el servidor de Rails con el comando bundle exec rails s. Usamos la opción -b con este comando para vincular el servidor a todas las direcciones IP y no a la predeterminada, localhost. Esta invocación hace que el servidor de Rails dirija las solicitudes a la dirección IP del contenedor en lugar de usar el localhost predeterminado.

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

      Haga que la secuencia de comandos sea ejecutable:

      • chmod +x entrypoints/docker-entrypoint.sh

      A continuación, crearemos una secuencia de comandos para iniciar nuestro servicio sidekiq que procesará nuestros trabajos de Sidekiq. Para obtener más información sobre la forma en que esta aplicación usa Sidekiq, consulte Cómo agregar Sidekiq y Redis a una aplicación de Ruby on Rails.

      Abra un archivo para la secuencia de comandos de punto de entrada de Sidekiq:

      • nano entrypoints/sidekiq-entrypoint.sh

      Añada el siguiente código al archivo para iniciar Sidekiq:

      ~/rails-docker/entrypoints/sidekiq-entrypoint.sh

      #!/bin/sh
      
      set -e
      
      if [ -f tmp/pids/server.pid ]; then
        rm tmp/pids/server.pid
      fi
      
      bundle exec sidekiq
      

      Esta secuencia de comandos inicia Sidekiq en el contexto de nuestro paquete de aplicaciones.

      Guarde y cierre el archivo cuando concluya la edición. Haga que sea ejecutable:

      • chmod +x entrypoints/sidekiq-entrypoint.sh

      Con sus secuencias de comandos de punto de entrada y Dockerfile, estará listo para definir sus servicios en su archivo de Compose.

      Paso 4: Definir servicios con Docker Compose

      Usando Docker Compose, podremos ejecutar los diferentes contenedores necesarios para nuestra configuración. Definiremos nuestros servicios de Compose en nuestro archivo docker-compose.yml principal: 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.

      En la configuración de nuestra aplicación se incluirán los siguientes servicios:

      • La propia aplicación
      • La base de datos de PostgreSQL
      • Redis
      • Sidekiq

      También incluiremos un montaje bind como parte de nuestra configuración, de modo que cualquier cambio de código que hagamos durante el desarrollo se sincronice de inmediato con los contenedores que necesiten acceso a este código.

      Tenga en cuenta que no definiremos un servicio test, ya que estas encuentran fuera del alcance de este tutorial y esta serie, pero podría hacerlo siguiendo el anterior que usaremos aquí para el servicio sidekiq.

      Abra el archivo docker-compose.yml:

      Primero, añada la definición del servicio de la aplicación:

      ~/rails-docker/docker-compose.yml

      version: '3.4'
      
      services:
        app:
          build:
            context: .
            dockerfile: Dockerfile
          depends_on:
            - database
            - redis
          ports:
            - "3000:3000"
          volumes:
            - .:/app
            - gem_cache:/usr/local/bundle/gems
            - node_modules:/app/node_modules
          env_file: .env
          environment:
            RAILS_ENV: development
      

      La definición del servicio app 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.
      • depends_on: esto establece primero database y los contenedores de redis para que estén listos y en ejecución antes que app.
      • ports: asigna el puerto 3000 del host al puerto 3000 del contenedor.
      • volumes: incluiremos dos tipos de montajes aquí:
        • El primero es un montaje bind, que monta el código de nuestra aplicación del host en el directorio /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: gem_cache. Cuando la instrucción bundle install se ejecute en el contenedor, instalará las gemas del proyecto. Añadir este volumen significa que si recrea el contenedor, las gemas se montarán en el nuevo contenedor. Para este montaje se prevé que no se produjeron cambios en el proyecto, de modo que si realiza cambios en las gemas de su proyecto, deberá recordar eliminar este volumen antes de recrear el servicio de su aplicación.
        • El tercero es un volumen con nombre para el directorio node_modules. En lugar de contar con node_modules montados en el host, que pueden generar discrepancias de paquetes y conflictos de permisos durante el desarrollo, este volumen garantizará que los paquetes de este directorio se mantengan y reflejen el estado actual del proyecto. Una vez más, si modifica las dependencias del proyecto de Node, deberá eliminar y recrear este volumen.
      • env_file: indica a Compose que deseamos añadir variables de entorno de un archivo llamado .env, ubicado en el contexto de compilación.
      • environment: usar esta opción nos permite establecer una variable de entorno no sensible y pasar información sobre el entorno de Rails al contenedor.

      A continuación, debajo de la definición del servicio app, añada el siguiente código para definir su servicio database:

      ~/rails-docker/docker-compose.yml

      . . .
        database:
          image: postgres:12.1
          volumes:
            - db_data:/var/lib/postgresql/data
            - ./init.sql:/docker-entrypoint-initdb.d/init.sql
      

      A diferencia del servicio app, el servicio database obtiene una imagen de postgres directamente del Hub de Docker. Tenga en cuenta que también anclaremos la versión aquí, en lugar de fijarla en latest o no especificarla (su valor predeterminado es latest). De esta forma, podemos garantizar que esta configuración funcione con las versiones aquí especificadas y evitar sorpresas inesperadas con cambios por código dañado en la imagen.

      Aquí, también incluiremos un volumen db_data, que conservará los datos de nuestra aplicación entre uno y otro inicio del contenedor. Además, montamos nuestra secuencia de comandos de inicio init.sql en el directorio apropiado, docker-entrypoint-initdb.d/ del contenedor, a fin de crear nuestro usuario de base de datos sammy. Después de que el punto de entrada de imagen cree el usuario y la base de datos de postgres predeterminados, ejecutará cualquier secuencia de comandos del directorio docker-entrypoint-initdb.d/ que puede usar para las tareas de inicialización necesarias. Para obtener más información, consulte la sección Secuencias de comandos de inicialización de la documentación de imagen de PostgreSQL.

      A continuación, añada la definición del servicio de redis:

      ~/rails-docker/docker-compose.yml

      . . .
        redis:
          image: redis:5.0.7
      

      Al igual que el servicio database, el servicio de redis utiliza una imagen del Hub de Docker. En este caso, no conservaremos la caché de trabajo de Sidekiq.

      Por último, añada la definición del servicio sidekiq:

      ~/rails-docker/docker-compose.yml

      . . .
        sidekiq:
          build:
            context: .
            dockerfile: Dockerfile
          depends_on:
            - app      
            - database
            - redis
          volumes:
            - .:/app
            - gem_cache:/usr/local/bundle/gems
            - node_modules:/app/node_modules
          env_file: .env
          environment:
            RAILS_ENV: development
          entrypoint: ./entrypoints/sidekiq-entrypoint.sh
      

      Nuestro servicio sidekiq se asemeja a nuestro servicio app en algunos aspectos: el contexto y la imagen de compilación, las variables de entorno y el volumen que usa son los mismos. Sin embargo, depende de los servicios app, redis y database, y se iniciará en último lugar. Además, utiliza un entrypoint que anulará el punto de entrada establecido en el Dockerfile. Este ajuste entrypoint apunta a entrypoints/sidekiq-entrypoint.sh, que incluye el comando indicado para iniciar el servicio sidekiq.

      Como paso final, añada las definiciones de volumen que se encuentran debajo de la definición del servicio sidekiq:

      ~/rails-docker/docker-compose.yml

      . . .
      volumes:
        gem_cache:
        db_data:
        node_modules:
      

      Nuestra clave de volúmenes de nivel superior define lo volúmenes gem_cache, db_data y node_modules. Cuando Docker crea volúmenes, el contenido de estos se almacena en una parte del sistema de archivos de 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 sobre tiburones que nuestros usuarios crearán persistirán en el volumen db_data, incluso si eliminamos y volvemos a crear el servicio database.

      El archivo terminado tendrá este aspecto:

      ~/rails-docker/docker-compose.yml

      version: '3.4'
      
      services:
        app:
          build:
            context: .
            dockerfile: Dockerfile
          depends_on:     
            - database
            - redis
          ports:
            - "3000:3000"
          volumes:
            - .:/app
            - gem_cache:/usr/local/bundle/gems
            - node_modules:/app/node_modules
          env_file: .env
          environment:
            RAILS_ENV: development
      
        database:
          image: postgres:12.1
          volumes:
            - db_data:/var/lib/postgresql/data
            - ./init.sql:/docker-entrypoint-initdb.d/init.sql
      
        redis:
          image: redis:5.0.7
      
        sidekiq:
          build:
            context: .
            dockerfile: Dockerfile
          depends_on:
            - app      
            - database
            - redis
          volumes:
            - .:/app
            - gem_cache:/usr/local/bundle/gems
            - node_modules:/app/node_modules
          env_file: .env
          environment:
            RAILS_ENV: development
          entrypoint: ./entrypoints/sidekiq-entrypoint.sh
      
      volumes:
        gem_cache:
        db_data:
        node_modules:     
      

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

      Una vez escritas sus definiciones de su servicios, 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 y propagar su base de datos. También puede comprobar que sus datos persisitirán deteniendo y eliminando sus contenedores con docker-compose down y volviendo a crearlos.

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

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

      Output

      Creating rails-docker_database_1 ... done Creating rails-docker_redis_1 ... done Creating rails-docker_app_1 ... done Creating rails-docker_sidekiq_1 ... 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

      sidekiq_1 | 2019-12-19T15:05:26.365Z pid=6 tid=grk7r6xly INFO: Booting Sidekiq 6.0.3 with redis options {:host=>"redis", :port=>"6379", :id=>"Sidekiq-server-PID-6", :url=>nil} sidekiq_1 | 2019-12-19T15:05:31.097Z pid=6 tid=grk7r6xly INFO: Running in ruby 2.5.1p57 (2018-03-29 revision 63029) [x86_64-linux-musl] sidekiq_1 | 2019-12-19T15:05:31.097Z pid=6 tid=grk7r6xly INFO: See LICENSE and the LGPL-3.0 for licensing details. sidekiq_1 | 2019-12-19T15:05:31.097Z pid=6 tid=grk7r6xly INFO: Upgrade to Sidekiq Pro for more features and support: http://sidekiq.org app_1 | => Booting Puma app_1 | => Rails 5.2.3 application starting in development app_1 | => Run `rails server -h` for more startup options app_1 | Puma starting in single mode... app_1 | * Version 3.12.1 (ruby 2.5.1-p57), codename: Llamas in Pajamas app_1 | * Min threads: 5, max threads: 5 app_1 | * Environment: development app_1 | * Listening on tcp://0.0.0.0:3000 app_1 | Use Ctrl-C to stop . . . database_1 | PostgreSQL init process complete; ready for start up. database_1 | database_1 | 2019-12-19 15:05:20.160 UTC [1] LOG: starting PostgreSQL 12.1 (Debian 12.1-1.pgdg100+1) on x86_64-pc-linux-gnu, compiled by gcc (Debian 8.3.0-6) 8.3.0, 64-bit database_1 | 2019-12-19 15:05:20.160 UTC [1] LOG: listening on IPv4 address "0.0.0.0", port 5432 database_1 | 2019-12-19 15:05:20.160 UTC [1] LOG: listening on IPv6 address "::", port 5432 database_1 | 2019-12-19 15:05:20.163 UTC [1] LOG: listening on Unix socket "/var/run/postgresql/.s.PGSQL.5432" database_1 | 2019-12-19 15:05:20.182 UTC [63] LOG: database system was shut down at 2019-12-19 15:05:20 UTC database_1 | 2019-12-19 15:05:20.187 UTC [1] LOG: database system is ready to accept connections . . . redis_1 | 1:M 19 Dec 2019 15:05:18.822 * Ready to accept connections

      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 ----------------------------------------------------------------------------------------- rails-docker_app_1 ./entrypoints/docker-resta ... Up 0.0.0.0:3000->3000/tcp rails-docker_database_1 docker-entrypoint.sh postgres Up 5432/tcp rails-docker_redis_1 docker-entrypoint.sh redis ... Up 6379/tcp rails-docker_sidekiq_1 ./entrypoints/sidekiq-entr ... Up

      A continuación, cree y propague su base de datos, y ejecute migraciones en ella con el siguiente comando docker-compose exec:

      • docker-compose exec app bundle exec rake db:setup db:migrate

      El comando docker-compose exec le permite ejecutar comandos en sus servicios; lo usaremos aquí para que ejecute rake db:setup y db:migrate en el contexto del paquete de nuestra aplicación, a fin de crear y sembrar la base de datos y ejecutar migraciones. A medida que trabaje en el desarrollo, docker-compose exec será útil para usted cuando desee ejecutar migraciones con su base de datos de desarrollo.

      Verá el siguiente resultado después de ejecutar este comando:

      Output

      Created database 'rails_development' Database 'rails_development' already exists -- enable_extension("plpgsql") -> 0.0140s -- create_table("endangereds", {:force=>:cascade}) -> 0.0097s -- create_table("posts", {:force=>:cascade}) -> 0.0108s -- create_table("sharks", {:force=>:cascade}) -> 0.0050s -- enable_extension("plpgsql") -> 0.0173s -- create_table("endangereds", {:force=>:cascade}) -> 0.0088s -- create_table("posts", {:force=>:cascade}) -> 0.0128s -- create_table("sharks", {:force=>:cascade}) -> 0.0072s

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

      Inicio de la aplicación de Sidekiq

      Ahora, podemos probar la persistencia de datos. Cree un nuevo tiburón haciendo clic en el botón Get Shark Info, que lo dirigirá a la ruta sharks/index:

      Página de índice de tiburones con datos propagados

      Para verificar que la aplicación funcione, podemos añadirle información de prueba. Haga clic en New Shark. Gracias a la configuración de autenticación del proyecto, se le solicitará un nombre de usuario (sammy) y una contraseña (tiburón).

      En la página New Shark, ingrese “Mako” en el campo Name y “Fast” en el campo Facts.

      Haga clic en el botón Create Shark para crear el tiburón. Una vez que haya creado el tiburón, haga clic en Home en la barra de navegación del sitio para volver a la página de aterrizaje de la aplicación. Ahora, podemos probar que Sidekiq funciona.

      Haga clic en el botón** ¿Which Sharks Are In Danger?** . Debido a que no cargó ningún tiburón en peligro, esto lo dirigirá a la vista de endangered index:

      Vista del índice de tiburones en peligro

      Haga clic en Import Endangered Sharks para importar los tiburones. Verá un mensaje de estado que le indicará que los tiburones se importaron:

      Inicio de la importación

      También verá el comienzo de la importación. Actualice su página para ver la tabla entera:

      Actualización de tabla

      Gracias a Sidekiq, realizamos la carga del gran lote de tiburones en peligro sin cerrar el navegador ni interferir con otras funciones de la aplicación.

      Haga clic en el botón Home en la parte inferior de la página, que lo llevará de vuelta a la página principal de la aplicación:

      Inicio de la aplicación de Sidekiq

      Aquí, haga clic en Which Sharks Are in Danger? nuevamente. Verá los tiburones cargados otra vez.

      Ahora que sabemos que nuestra aplicación funciona correctamente, podemos probar la persistencia de nuestros datos.

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

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

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

      Output

      Stopping rails-docker_sidekiq_1 ... done Stopping rails-docker_app_1 ... done Stopping rails-docker_database_1 ... done Stopping rails-docker_redis_1 ... done Removing rails-docker_sidekiq_1 ... done Removing rails-docker_app_1 ... done Removing rails-docker_database_1 ... done Removing rails-docker_redis_1 ... done Removing network rails-docker_default

      Vuelva a crear los contenedores:

      Abra la consola de Rails en el contenedor app con docker-compose exec y bundle exec rails console:

      • docker-compose exec app bundle exec rails console

      En el símbolo de sistema, inspeccione el registro last de Tiburón de la base de datos:

      Verá el registro que acaba de crear:

      IRB session

      Shark Load (1.0ms) SELECT "sharks".* FROM "sharks" ORDER BY "sharks"."id" DESC LIMIT $1 [["LIMIT", 1]] => "#<Shark id: 5, name: "Mako", facts: "Fast", created_at: "2019-12-20 14:03:28", updated_at: "2019-12-20 14:03:28">"

      Luego, puede verificar que sus tiburones endangered se hayan conservado con el siguiente comando:

      IRB session

      (0.8ms) SELECT COUNT(*) FROM "endangereds" => 73

      Su volumen db_data se montó correctamente en el servicio database recreado, lo cual posibilitó que su servicio app accediera a los datos guardados. Si accede directamente a la página index shark visitando localhost:3000/sharks o http://your_server_ip:3000/sharks, también verá el registro:

      Página de índice de tiburones con Mako

      Sus tiburones en peligro también se encontrarán en la vista localhost:3000/endangered/data o http://your_server_ip:3000/endangered/data:

      Actualización de la tabla

      Su aplicación ahora se ejecuta en los contenedores de Docker con la persistencia de datos y la sincronización de códigos habilitadas. Puede probar los cambios de código local en su host, que se sincronizarán con su contenedor gracias al montaje que definimos como parte del servicio de app.

      Conclusión

      Siguiendo este tutorial, creó una configuración de desarrollo para su aplicación de Rails 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 de su código. 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. O bien, si desea invertir en una secuencia de aprendizaje de Kubernetes, consulte Kubernetes para desarrolladores Full-Stack.

      Para obtener más información sobre el código de la aplicación en sí, consulte los otros tutoriales de esta serie:



      Source link

      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 "[email protected]"
            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 "[email protected]"
      

      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