DEV Community

Júnior Mendes
Júnior Mendes

Posted on • Updated on

Dockerizando um projeto Node.js

Um dos princípios para ter um projeto bem estruturado é tornar a configuração inicial tão simples quanto possível. Com menos impedimento para de fato "rodar" o projeto, é possível inserir mais desenvolvedores no fluxo de trabalho de forma acelerada.

Um dos grandes gargalos, sem dúvidas, é montar a infraestrutura necessária, otimizada para o ambiente de desenvolvimento. As práticas e conceitos do mundo DevOps entram para auxiliar e, nesse artigo, vamos abordar docker e conteinerização de um backend feito com Nodejs e mongodb. Além disso, no fim vamos ver uma dica para visualizar melhor os dados.

Primeiro, vamos criar uma aplicação com node. Você pode utilizar algum projeto que já esteja configurado (e, caso o faça, pule pro próximo tópico). Verifique que ele possui um script "start" que possa ser utilizado.

Iniciando o projeto

Utilizando o yarn:

$ yarn init
yarn init v1.22.4
question name (example_docker): 
question version (1.0.0): 
question description: A simple backend
question entry point (index.js): 
question repository url: 
question author: jrmmendes <jrmmendes@outlook.com>
question license (MIT): 
question private: 
success Saved package.json
Done in 22.54s.
Enter fullscreen mode Exit fullscreen mode

Instalando pacotes necessários

Vamos instalar o express.js (para construir a aplicação) e o dotenv (para carregar variáveis de ambiente mais facilmente):

$ yarn add express dotenv
Enter fullscreen mode Exit fullscreen mode

Numa aplicação real, outros pacotes são de fato importantes, sobre tudo para questões ligadas à segurança de dados. Esse exemplo é apenas ilustrativo.

Além disso, para conexão com o banco de dados, vamos instalar o mongoose:

$ yarn add mongoose
Enter fullscreen mode Exit fullscreen mode

Escrevendo os arquivos da aplicação

Vamos criar o index.js com o seguinte conteúdo:

const express = require('express');
const dotenv = require('dotenv');
const mongoose = require('mongoose');

// Definição da aplicação
const app = express();
dotenv.config({ path: '.env' });
app.use(express.json());

// Configuração do acesso ao banco de dados
mongoose.connect(process.env.MONGO_URI, {
  useCreateIndex: true,
  useNewUrlParser: true,
  useUnifiedTopology: true,
});

mongoose.connection.once('open', () => {
  console.log('Conectado ao banco de dados');
});

mongoose.connection.on('error', (e) => {
  console.log('Error ao tentar conectar-se ao banco de dados');
  console.error(e);
});

// Rotas de teste
app.route('/ping').all((req, res) => {
  res.status(200).json({ data: 'PONG!' });
});

// Inicialização do servidor
app.listen(process.env.PORT || 3000, () => { 
  console.log('Servidor Iniciado');
});
Enter fullscreen mode Exit fullscreen mode

Vamos também criar o arquivo .env, com as variáveis de ambiente PORT e MONGO_URI:

MONGO_URI="mongodb://root:toor@mongo:27017/development-db?authSource=admin"
Enter fullscreen mode Exit fullscreen mode

Por fim, vamos adicionar ao arquivo package.json um script start, para iniciar o projeto. Ele deve estar assim:

{
  "name": "example_docker",
  "version": "1.0.0",
  "description": "A simple backend",
  "main": "index.js",
  "author": "jrmmendes <jrmmendes@outlook.com>",
  "license": "MIT",
  "dependencies": {
    "dotenv": "^8.2.0",
    "express": "^4.17.1"
  }
}
Enter fullscreen mode Exit fullscreen mode

De modo que vamos editá-lo, adicionando uma chave "scripts":

{
  "name": "example_docker",
  "version": "1.0.0",
  "description": "A simple backend",
  "scripts": {
    "start": "node index.js"
  },
  "main": "index.js",
  "author": "jrmmendes <jrmmendes@outlook.com>",
  "license": "MIT",
  "dependencies": {
    "dotenv": "^8.2.0",
    "express": "^4.17.1",
    "mongoose": "^5.9.7"
  }
}
Enter fullscreen mode Exit fullscreen mode

Essa é a estrutura que o projeto deve ter no fim:

example_docker
├── index.js
├── node_modules
├── package.json
└── yarn.lock
Enter fullscreen mode Exit fullscreen mode

Docker

O ponto de partida será criar um arquivo chamado Dockerfile. É nele onde vamos especificar como ocorre o setup da aplicação.

Caso não esteja familiarizado com os conceitos do Docker, imagine que é uma forma de documentar os passos iniciais para instalar dependências, similar a uma script, mas que é executado de forma mais segura e independente do ambiente do desenvolvedor (desde que seja Linux, nesse caso).

Após isso, iremos configurar os outros serviços relacionados à nossa aplicação (como o banco de dados) e a interação entre eles com o Docker Compose. Aqui já podemos notar um benefício bem clássico dessa abordagem: não será necessário instalar nenhum SGBD no sistema operacional, removendo uma possível fonte de problemas de compatibilidade/configuração.

Definição da Aplicação

Vamos criar o arquivo Dockerfile. Ele terá a seguinte estrutura:

# Imagem base
FROM node:12.16

# Configuração do usuário/permissões
USER node
WORKDIR /home/node/

# Instalação das dependências
COPY package.json .
COPY yarn.lock .
RUN yarn install

# Copia dos arquivos do projeto
COPY . .

# Execução
CMD ["yarn", "start"]
Enter fullscreen mode Exit fullscreen mode

Vamos estudar mais a fundo cada parte.

Base

FROM node:12.16
Enter fullscreen mode Exit fullscreen mode

No mundo do Docker, existe o DockerHub, que funciona de maneira análoga ao Github, nos fornecendo um lugar para enviar e utilizar partes reutilizáveis. Nesse caso, vamos tirar proveito da existência de imagens já configuradas do node, em específico das versões 12.16.x, nos livrando da necessidade de instalar o próprio node e suas ferramentas, como o yarn.

Configuração do usuário/permissões

USER node
WORKDIR /home/node/
Enter fullscreen mode Exit fullscreen mode

Nessa parte, estamos definindo qual usuário será utilizado dentro do contêiner da aplicação. Essa parte é importante para evitar que todos os comandos sejam executados como superusuário (o que, dentre outros impactos, causa um problema de permissões em alguns arquivos, sendo no mínimo inconveniente).

Também alteramos a pasta onde estaremos copiando e executando instruções RUN, COPY, ADD, CMD e ENTRYPOINT.

Instalação das dependências

COPY package.json .
COPY yarn.lock .
RUN yarn install
Enter fullscreen mode Exit fullscreen mode

Aqui instalamos os pacotes que a aplicação necessita. É possível substituir essa fase por algo mais complexo como um multistage build, mas isso é algo que não vamos ver nesse artigo.

Copia dos arquivos do projeto

COPY . .
Enter fullscreen mode Exit fullscreen mode

Nessa fase os arquivos que escrevemos (.env, index.js) são copiados para dentro do contêiner. Apenas para ficar claro, estamos copiando da mesma pasta em que o arquivo Dockerfile se encontra para a que definimos com o comando WORKDIR (/home/node). Vale também lembrar que a segunda se refere ao contêiner, não ao nosso sistema de arquivos normal.

Execução

CMD ["yarn", "start"]
Enter fullscreen mode Exit fullscreen mode

Aqui, iniciamos o projeto. Indicamos qual comando deve ser executado após o setup da aplicação.

Serviços e integração

Para definir os outros serviços e conectar todos os contêineres, além de facilitar a execução do projeto, vamos criar o arquivo docker-compose.yml, com o seguinte conteúdo:

version: '3'

services:
  api:
    build: 
      dockerfile: ./Dockerfile
      context: .
    volumes:
      - .:/home/node
      - /home/node/node_modules
    ports:
      - 3000:3000
    command: yarn start
    depends_on: 
      - mongo

  mongo-express:
    image: mongo-express
    ports:
      - 8081:8081
    environment:
      ME_CONFIG_BASICAUTH_USERNAME: mendes
      ME_CONFIG_BASICAUTH_PASSWORD: dotmendes
      ME_CONFIG_MONGODB_PORT: 27017
      ME_CONFIG_MONGODB_ADMINUSERNAME: root
      ME_CONFIG_MONGODB_ADMINPASSWORD: toor
    depends_on:
      - mongo

  mongo:
    image: mongo
    command: [--auth]
    environment:
      MONGO_INITDB_ROOT_USERNAME: root
      MONGO_INITDB_ROOT_PASSWORD: toor
    ports:
      - 27017:27017
    volumes:
      - ./volumes/db:/data/db
Enter fullscreen mode Exit fullscreen mode

Explicando de maneira rápida, estamos definindo três serviços: api, mongo e mongo-express. O primeiro é construído a partir do Dockerfile que definimos anteriormente; o seguinte, é criado diretamente da imagem do mongo no Dockerhub (similar ao que fizemos com a imagem do node, contudo não modificamos nada).

O terceiro serviço é uma interface que nos permite visualizar o banco de dados e manusear documentos e coleções.

Existe, por fim, a criação de alguns volumes, que serão utilizados para sincronizar modificações entre os arquivos e o que está dentro do contêiner. Isso é especialmente útil durante o desenvolvimento, para que possamos adicionar novas funcionalidades e testá-las sem que precise ser realizado outro processo de build da aplicação.

Conclusão

Após criar todos os arquivos, podemos instalar e executar a aplicação com um simples comando:

$ docker-compose up
Enter fullscreen mode Exit fullscreen mode

De modo que teremos como acessar a aplicação em http://localhost:3000/ping e a interface do mongo-express em http://localhost:8081.

Top comments (0)