Contenerizar una aplicación de Ruby on Rails
Prerrequisitos
- Has instalado la última versión de Docker Desktop.
- Tienes un cliente de Git. Los ejemplos de esta sección muestran la CLI de Git, pero puedes utilizar cualquier cliente.
Resumen
Esta sección te acompaña a lo largo de todo el proceso de contenerización y ejecución de una aplicación de Ruby on Rails.
A partir de Rails 7.1, Docker es compatible de forma nativa. Esto significa que se generarán los archivos Dockerfile, .dockerignore y bin/docker-entrypoint automáticamente cuando crees una nueva aplicación de Rails.
Si tienes una aplicación de Rails existente, deberás crear los recursos de Docker manualmente. Lamentablemente, el comando docker init aún no es compatible con Rails. Esto significa que si estás trabajando con Rails, deberás copiar el Dockerfile y las demás configuraciones relacionadas manualmente a partir de los ejemplos que se muestran a continuación.
1. Inicializa los recursos de Docker
Rails 7.1 y versiones posteriores generan un Dockerfile de múltiples etapas (multistage) de forma nativa. A continuación se presentan dos versiones de dicho archivo: una utilizando Imágenes de Docker Reforzadas (DHIs) y otra utilizando la Imagen Oficial de Docker (DOIs). Aunque el Dockerfile se genera automáticamente, es importante comprender su propósito y funcionalidad. Se recomienda encarecidamente revisar el siguiente ejemplo.
Las Imágenes de Docker Reforzadas (DHIs) son imágenes base y de aplicación de contenedores mínimas, seguras y listas para producción, mantenidas por Docker. Se recomiendan las DHIs siempre que sea posible para una mejor seguridad. Están diseñadas para reducir vulnerabilidades y simplificar el cumplimiento, y están disponibles de forma gratuita para todo el mundo sin necesidad de suscripción, sin restricciones de uso y sin dependencia de un proveedor (vendor lock-in).
Los Dockerfiles de múltiples etapas ayudan a crear imágenes más pequeñas y eficientes al separar las dependencias de compilación y de ejecución, garantizando que solo se incluyan los componentes necesarios en la imagen final. Lee más en la guía de compilaciones de múltiples etapas.
Debes autenticarte en dhi.io antes de poder descargar Imágenes de Docker Reforzadas. Ejecuta docker login dhi.io para autenticarte.
# syntax=docker/dockerfile:1
# check=error=true
# Este Dockerfile está diseñado para producción, no para desarrollo.
# docker build -t app .
# docker run -d -p 80:80 -e RAILS_MASTER_KEY=<value from config/master.key> --name app app
# Para un entorno de desarrollo contenerizado, consulta Dev Containers: https://guides.rubyonrails.org/getting_started_with_devcontainer.html
# Asegúrate de que RUBY_VERSION coincida con la versión de Ruby en .ruby-version
ARG RUBY_VERSION=3.4.8
FROM dhi.io/ruby:$RUBY_VERSION-dev AS base
# La aplicación de Rails reside aquí
WORKDIR /rails
# Instalar paquetes base
# Reemplazar libpq-dev por sqlite3 si se usa SQLite, o libmysqlclient-dev si se usa MySQL
RUN apt-get update -qq && \
apt-get install --no-install-recommends -y curl libjemalloc2 libvips libpq-dev && \
rm -rf /var/lib/apt/lists /var/cache/apt/archives
# Configurar el entorno de producción
ENV RAILS_ENV="production" \
BUNDLE_DEPLOYMENT="1" \
BUNDLE_PATH="/usr/local/bundle" \
BUNDLE_WITHOUT="development"
# Etapa de compilación temporal para reducir el tamaño de la imagen final
FROM base AS build
# Instalar los paquetes necesarios para compilar gemas
RUN apt-get update -qq && \
apt-get install --no-install-recommends -y build-essential curl git pkg-config libyaml-dev && \
rm -rf /var/lib/apt/lists /var/cache/apt/archives
# Instalar dependencias de JavaScript y Node.js para la compilación de recursos
#
# Descomentar las siguientes líneas si estás utilizando NodeJS y necesitas compilar recursos
#
# ARG NODE_VERSION=18.12.0
# ARG YARN_VERSION=1.22.19
# ENV PATH=/usr/local/node/bin:$PATH
# RUN curl -sL https://github.com/nodenv/node-build/archive/master.tar.gz | tar xz -C /tmp/ && \
# /tmp/node-build-master/bin/node-build "${NODE_VERSION}" /usr/local/node && \
# npm install -g yarn@$YARN_VERSION && \
# npm install -g mjml && \
# rm -rf /tmp/node-build-master
# Instalar gemas de la aplicación
COPY Gemfile Gemfile.lock ./
RUN bundle install && \
rm -rf ~/.bundle/ "${BUNDLE_PATH}"/ruby/*/cache "${BUNDLE_PATH}"/ruby/*/bundler/gems/*/.git && \
bundle exec bootsnap precompile --gemfile
# Instalar módulos de node
#
# Descomentar las siguientes líneas si estás utilizando NodeJS y necesitas compilar recursos
#
# COPY package.json yarn.lock ./
# RUN --mount=type=cache,id=yarn,target=/rails/.cache/yarn YARN_CACHE_FOLDER=/rails/.cache/yarn \
# yarn install --frozen-lockfile
# Copiar el código de la aplicación
COPY . .
# Precompilar el código de bootsnap para tiempos de arranque más rápidos
RUN bundle exec bootsnap precompile app/ lib/
# Precompilar recursos para producción sin requerir la clave secreta RAILS_MASTER_KEY
RUN SECRET_KEY_BASE_DUMMY=1 ./bin/rails assets:precompile
# Etapa final para la imagen de la aplicación
FROM base
# Copiar artefactos compilados: gemas, aplicación
COPY --from=build "${BUNDLE_PATH}" "${BUNDLE_PATH}"
COPY --from=build /rails /rails
# Ejecutar y poseer únicamente los archivos de ejecución como un usuario no root por seguridad
RUN groupadd --system --gid 1000 rails && \
useradd rails --uid 1000 --gid 1000 --create-home --shell /bin/bash && \
chown -R rails:rails db log storage tmp
USER 1000:1000
# El punto de entrada (entrypoint) prepara la base de datos.
ENTRYPOINT ["/rails/bin/docker-entrypoint"]
# Iniciar el servidor a través de Thruster por defecto, esto se puede sobrescribir en tiempo de ejecución
EXPOSE 80
CMD ["./bin/thrust", "./bin/rails", "server"]# syntax=docker/dockerfile:1
# check=error=true
# Este Dockerfile está diseñado para producción, no para desarrollo.
# docker build -t app .
# docker run -d -p 80:80 -e RAILS_MASTER_KEY=<value from config/master.key> --name app app
# Para un entorno de desarrollo contenerizado, consulta Dev Containers: https://guides.rubyonrails.org/getting_started_with_devcontainer.html
# Asegúrate de que RUBY_VERSION coincida con la versión de Ruby en .ruby-version
ARG RUBY_VERSION=3.4.8
FROM docker.io/library/ruby:$RUBY_VERSION-slim AS base
# La aplicación de Rails reside aquí
WORKDIR /rails
# Instalar paquetes base
# Reemplazar libpq-dev por sqlite3 si se usa SQLite, o libmysqlclient-dev si se usa MySQL
RUN apt-get update -qq && \
apt-get install --no-install-recommends -y curl libjemalloc2 libvips libpq-dev && \
rm -rf /var/lib/apt/lists /var/cache/apt/archives
# Configurar el entorno de producción
ENV RAILS_ENV="production" \
BUNDLE_DEPLOYMENT="1" \
BUNDLE_PATH="/usr/local/bundle" \
BUNDLE_WITHOUT="development"
# Etapa de compilación temporal para reducir el tamaño de la imagen final
FROM base AS build
# Instalar los paquetes necesarios para compilar gemas
RUN apt-get update -qq && \
apt-get install --no-install-recommends -y build-essential curl git pkg-config libyaml-dev && \
rm -rf /var/lib/apt/lists /var/cache/apt/archives
# Instalar dependencias de JavaScript y Node.js para la compilación de recursos
#
# Descomentar las siguientes líneas si estás utilizando NodeJS y necesitas compilar recursos
#
# ARG NODE_VERSION=18.12.0
# ARG YARN_VERSION=1.22.19
# ENV PATH=/usr/local/node/bin:$PATH
# RUN curl -sL https://github.com/nodenv/node-build/archive/master.tar.gz | tar xz -C /tmp/ && \
# /tmp/node-build-master/bin/node-build "${NODE_VERSION}" /usr/local/node && \
# npm install -g yarn@$YARN_VERSION && \
# npm install -g mjml && \
# rm -rf /tmp/node-build-master
# Instalar gemas de la aplicación
COPY Gemfile Gemfile.lock ./
RUN bundle install && \
rm -rf ~/.bundle/ "${BUNDLE_PATH}"/ruby/*/cache "${BUNDLE_PATH}"/ruby/*/bundler/gems/*/.git && \
bundle exec bootsnap precompile --gemfile
# Instalar módulos de node
#
# Descomentar las siguientes líneas si estás utilizando NodeJS y necesitas compilar recursos
#
# COPY package.json yarn.lock ./
# RUN --mount=type=cache,id=yarn,target=/rails/.cache/yarn YARN_CACHE_FOLDER=/rails/.cache/yarn \
# yarn install --frozen-lockfile
# Copiar el código de la aplicación
COPY . .
# Precompilar el código de bootsnap para tiempos de arranque más rápidos
RUN bundle exec bootsnap precompile app/ lib/
# Precompilar recursos para producción sin requerir la clave secreta RAILS_MASTER_KEY
RUN SECRET_KEY_BASE_DUMMY=1 ./bin/rails assets:precompile
# Etapa final para la imagen de la aplicación
FROM base
# Copiar artefactos compilados: gemas, aplicación
COPY --from=build "${BUNDLE_PATH}" "${BUNDLE_PATH}"
COPY --from=build /rails /rails
# Ejecutar y poseer únicamente los archivos de ejecución como un usuario no root por seguridad
RUN groupadd --system --gid 1000 rails && \
useradd rails --uid 1000 --gid 1000 --create-home --shell /bin/bash && \
chown -R rails:rails db log storage tmp
USER 1000:1000
# El punto de entrada (entrypoint) prepara la base de datos.
ENTRYPOINT ["/rails/bin/docker-entrypoint"]
# Iniciar el servidor a través de Thruster por defecto, esto se puede sobrescribir en tiempo de ejecución
EXPOSE 80
CMD ["./bin/thrust", "./bin/rails", "server"]El Dockerfile anterior asume que estás utilizando Thruster junto con Puma como servidor de aplicaciones. En caso de que utilices cualquier otro servidor, puedes reemplazar las últimas tres líneas con lo siguiente:
# Iniciar el servidor de la aplicación
EXPOSE 3000
CMD ["./bin/rails", "server"]Este Dockerfile utiliza un script en ./bin/docker-entrypoint como el punto de entrada del contenedor. Este script prepara la base de datos y ejecuta el servidor de aplicaciones. A continuación, se muestra un ejemplo de dicho script.
#!/bin/bash -e
# Habilitar jemalloc para un uso de memoria y latencia reducidos.
if [ -z "${LD_PRELOAD+x}" ]; then
LD_PRELOAD=$(find /usr/lib -name libjemalloc.so.2 -print -quit)
export LD_PRELOAD
fi
# Si se está ejecutando el servidor de rails, crear o migrar la base de datos existente
if [ "${@: -2:1}" == "./bin/rails" ] && [ "${@: -1:1}" == "server" ]; then
./bin/rails db:prepare
fi
exec "${@}"Además de los dos archivos anteriores, también necesitarás un archivo .dockerignore. Este archivo se utiliza para excluir archivos y directorios del contexto de compilación. A continuación, se muestra un ejemplo de un archivo .dockerignore.
# Consulta https://docs-docker.esdocu.com/engine/reference/builder/#dockerignore-file para más detalles sobre cómo ignorar archivos.
# Ignorar el directorio de git.
/.git/
/.gitignore
# Ignorar la configuración de bundler.
/.bundle
# Ignorar todos los archivos de entorno.
/.env*
# Ignorar todos los archivos de claves predeterminados.
/config/master.key
/config/credentials/*.key
# Ignorar todos los archivos de registro (log) y archivos temporales.
/log/*
/tmp/*
!/log/.keep
!/tmp/.keep
# Ignorar archivos pid, pero mantener el directorio.
/tmp/pids/*
!/tmp/pids/.keep
# Ignorar el almacenamiento (archivos subidos en desarrollo y bases de datos SQLite).
/storage/*
!/storage/.keep
/tmp/storage/*
!/tmp/storage/.keep
# Ignorar recursos (assets).
/node_modules/
/app/assets/builds/*
!/app/assets/builds/.keep
/public/assets
# Ignorar archivos de servicios de CI.
/.github
# Ignorar archivos de desarrollo
/.devcontainer
# Ignorar archivos relacionados con Docker
/.dockerignore
/Dockerfile*El último archivo opcional que podrías necesitar es el archivo compose.yaml, que Docker Compose utiliza para definir los servicios que componen la aplicación. Dado que SQLite se utiliza como base de datos, no es necesario definir un servicio separado para la base de datos. El único servicio requerido es la propia aplicación de Rails.
services:
web:
build: .
environment:
- RAILS_MASTER_KEY
ports:
- "3000:80"Ahora deberías tener los siguientes archivos en la carpeta de tu aplicación:
.dockerignorecompose.yamlDockerfilebin/docker-entrypoint
Para obtener más información sobre los archivos, consulta lo siguiente:
2. Ejecuta la aplicación
Para ejecutar la aplicación, ejecuta el siguiente comando en una terminal dentro del directorio de la aplicación.
$ RAILS_MASTER_KEY=<master_key_value> docker compose up --build
Abre un navegador y visualiza la aplicación en http://localhost:3000. Deberías ver una aplicación sencilla de Ruby on Rails.
En la terminal, presiona ctrl+c para detener la aplicación.
3. Ejecuta la aplicación en segundo plano
Puedes ejecutar la aplicación desconectada de la terminal (detached mode) agregando la opción -d. Dentro del directorio docker-ruby-on-rails, ejecuta el siguiente comando en una terminal:
$ docker compose up --build -d
Abre un navegador y visualiza la aplicación en http://localhost:3000.
Deberías ver una aplicación sencilla de Ruby on Rails.
En la terminal, ejecuta el siguiente comando para detener la aplicación.
$ docker compose down
Para obtener más información sobre los comandos de Compose, consulta la referencia de la CLI de Compose.
Resumen
En esta sección, aprendiste cómo puedes contenerizar y ejecutar tu aplicación de Ruby utilizando Docker.
Información relacionada:
Próximos pasos
En la siguiente sección, aprenderás cómo configurar un flujo de trabajo de CI/CD utilizando GitHub Actions.