Dockerizando uma App Rails para Desenvolvimento - Parte 2

Tutoriais - 09/Abr/2020 - por Henrique Morato

Na primeira parte nós montamos um Dockerfile com as dependências do nosso projeto para criar uma imagem que nos forneça o suficiente para rodar essa aplicação Rails.

FROM ruby:2.7.0

ENV NODE_VERSION 12
ENV INSTALL_PATH /opt/app

RUN curl -sL https://deb.nodesource.com/setup_$NODE_VERSION.x | bash -

RUN curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add -
RUN echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list

RUN apt-get update -qq
RUN apt-get install -y --no-install-recommends nodejs postgresql-client \
      locales yarn

RUN echo "en_US.UTF-8 UTF-8" > /etc/locale.gen
RUN locale-gen
RUN export LC_ALL="en_US.utf8"

RUN mkdir -p $INSTALL_PATH

WORKDIR $INSTALL_PATH
COPY Gemfile Gemfile
COPY Gemfile.lock Gemfile.lock
RUN gem install bundler
RUN bundle install

COPY . $INSTALL_PATH

Com essa imagem montada, podemos começar a pensar na estrutura do projeto. Até aqui o nosso banco de dados não está funcionando simplesmente porque não está instalado nesse contêiner. Levando em consideração a estrutura de aplicações Web modernas, é bastante comum que aplicação e banco de dados sejam hospedados em máquinas diferentes.

Então vamos montar uma estrutura parecida por meio de contêineres. Isso também dá a liberdade de estender essa estrutura para facilmente colocar um Redis ou outras opções de ferramentas no seu projeto. Pensando nisso, o pessoal da Docker criou o docker-compose, que facilita a integração de contêineres. Onde antes iríamos subir dois contêineres, criar uma conexão entre os dois, exportar portas, etc. agora é uma questão de criar um único arquivo de configuração.

No diretório raiz do nosso projeto, vamos criar o arquivo docker-compose.yml, que faz a ponte entre o banco de dados e a aplicação pra gente.

Se você usa Mac ou Windows, a instalação do Docker Desktop já vem com o compose. Em sistemas Linux ele é instalado separadamente, você pode ler mais aqui.

version: '3'
services:
  rails:
    build: .
    command: rails s -b 0.0.0.0
    container_name: meu_projeto_rails
    ports:
      - 3000:3000
    volumes:
      - .:/opt/app

Com esse arquivo criado, podemos executar o comando docker-compose run --service-ports rails bash para subir um contêiner com o nosso projeto. Se você já teve que passar pelo processo de subir duas máquinas e conectá-las, percebeu como o docker-compose tornou tudo muito mais fácil.

O arquivo de configuração define o local onde o contêiner irá buscar a imagem para ser construída (build), nesse caso é a pasta corrente definida pelo .. O Docker busca o arquivo Dockerfile na mesma pasta em que se encontra o docker-compose. A opção --service-ports garante a comunicação da porta 3000 da nossa máquina com a porta 3000 do contêiner. Além disso, como o volume já foi definido nesse arquivo não precisamos mais nos preocupar com isso. Então basta rodar o serviço com o nome que declaramos (no caso, rails) executando o bash.

Feito isso e garantindo que tudo está funcionando, podemos sair do nosso contêiner com o comando exit e começar a configurar o banco de dados.

No docker-compose.yml nós podemos declarar um segundo serviço que será responsável pelo banco de dados. Neste caso chamamos de db.

version: '3'
services:
  rails:
    build: .
    command: rails s -b 0.0.0.0
    container_name: meu_projeto_rails
    ports:
      - 3000:3000
    volumes:
      - .:/opt/app
    environment: 
      - POSTGRES_USER=postgres
      - POSTGRES_PASSWORD=postgres
      - POSTGRES_HOST=db
    links:
      - db
  db:
    image: postgres:12-alpine
    environment:
      - POSTGRES_USER=postgres
      - POSTGRES_PASSWORD=postgres
    volumes:
      - meu_projeto_rails_data:/var/lib/postgresql/data
volumes:
  meu_projeto_rails_data:

Declaramos o serviço de banco de dados (db) com algumas variáveis de ambiente importantes (você pode ler mais sobre outras opções para fazer isto aqui). Além disso, fizemos um link, de forma que ao criarmos um contêiner com o serviço rails, um contêiner com o serviço db deve ser criado automaticamente. Configuramos também as mesmas variáveis nesse serviço com um adicional, que é o nome do serviço que está hospedando o banco de dados, nesse caso, db.

Caso você faça modificações no Dockerfile é importante atualizar o cache com um docker-compose build.

Quando executarmos docker-compose run --service-ports rails bash, o script irá reconhecer que deve subir um segundo contêiner com um banco de dados usando a imagem do postgres disponível no Docker Hub.

Mas para fazer tudo funcionar temos que usar o arquivo config/database.yml da nossa aplicação Rails para usar as variáveis de ambiente que declaramos com a senha e host do banco.

O arquivo tem muitas linhas, mas parte importante é troca do default incluindo o usuário, senha e host do banco.

default: &default
  adapter: postgresql
  encoding: unicode
  # For details on connection pooling, see Rails configuration guide
  # https://guides.rubyonrails.org/configuring.html#database-pooling
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  username: <%= ENV.fetch("POSTGRES_USER") %>
  password: <%= ENV.fetch("POSTGRES_PASSWORD") %>
  host: <%= ENV.fetch("POSTGRES_HOST") %>

Assim, temos o nosso banco funcionando num outro contêiner e de forma extensível para inclusão de um Redis, camada de cache, entre outras coisas necessárias ao nosso ambiente de desenvolvimento.

Projeto rodando com docker-compose run --service-ports rails bash, fazer um bin/setup, e rails server --binding 0.0.0.0, bora codar!?

Foto de perfil do autor
Henrique Morato

Dev e instrutor na Campus Code