DEV Community

Glaucia Lemos for Microsoft Azure

Posted on • Updated on • Originally published at dev.to

Desenvolvendo Aplicação Node.js & Express.js com Docker

docker.png

artigo originalmente escrito pelo Chris Noring - AQUI

No último artigo vimos e aprendemos conceitos básicos sobre Docker, Imagens e Contêineres. Agora é a hora de desenvolvermos uma aplicação e aprender em código como usar o Docker numa aplicação.

Vamos nessa?!

Vamos por a mão na massa!

Agora que já entendemos o que é Docker e porque devemos usar, agora é aquele momento que precisamos praticar o uso do Docker em alguma aplicação!

Bom, vamos precisar de um arquivo que é bastante usado no Docker: DockerFile. Pois é no DockerFile que especificaremos tudo o que precisaremos no que diz respeito ao sistema operacional, variáveis de ambiete e como executar o nosso aplicativo.

Vamos nos aprofundar agora nessa parte. Vamos desenvolver uma aplicação e Dockerizar ele. Através dele iremos executar a nossa aplicação dentro de um contêiner isolado do mundo externo, porém disponível para demais portas.

Vamos seguir os seguintes passos:

  • Criar um aplicativo em Node.js

Vamos desenvolver um aplicativo Node.js com Express. Ele funcionará como um REST API.

  • Criar o arquivo 'Dockerfile'

Esse arquivo será responsável por informar ao Docker como construir nossa aplicação.

  • Criar uma imagem

Para que a nossa aplicação funcione, devemos criar uma imagem chamada Docker.

  • Criar um contêiner

E por último, veremos a nossa aplicação em funcionamento e criaremos um contêiner a partir de uma imagem Docker.

Vamos seguir esses passos agora?!

Criando a Aplicação Node.js & Express com Docker

Abre agora o seu Visual Studio Code, crie uma pasta chamada: exemplo-2 e dentro dessa pasta digite o seguinte comando:

> npm init -y
Enter fullscreen mode Exit fullscreen mode

Esse comando cria para nós um arquivo package.json padrão.

No final, o arquivo package.json ficará da seguinte forma:

{
  "name": "exemplo-2",
  "version": "1.0.0",
  "description": "Código do exemplo do artigo: Introdução a Docker - Parte I",
  "main": "app.js",
  "scripts": {
    "dev": "nodemon",
    "lint": "eslint --ext .js,.html -f ./node_modules/eslint-friendly-formatter . --fix",
    "prepush": "npm run lint",
    "start": "node app.js"
  },
  "keywords": [
    "nodejs",
    "javascript",
    "docker",
    "azure",
    "express"
  ],
  "author": "Glaucia Lemos",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/glaucia86/docker-zero-to-hero-series/issues"
  },
  "homepage": "https://github.com/glaucia86/docker-zero-to-hero-series/blob/master/README.md",
  "devDependencies": {
    "eslint": "^5.16.0",
    "eslint-config-airbnb-base": "^13.1.0",
    "eslint-plugin-import": "^2.17.3",
    "husky": "^2.3.0",
    "nodemon": "^1.19.1"
  },
  "dependencies": {
    "eslint-friendly-formatter": "^4.0.1",
    "eslint-plugin-html": "^5.0.5",
    "express": "^4.17.1"
  }
}
Enter fullscreen mode Exit fullscreen mode

Caso queiram ter o código fonte desenvolvido, criei um repositório no GitHub. Nesse caso, bastam fazer git clone ou download do projeto AQUI

Bom, dando continuidade vamos criar dentro da pasta exemplo-2 o arquivo app.js e depois o arquivo Dockerfile

No final, a estrutura do projeto ficará da seguinte maneira:

Screen-Shot-07-05-19-at-03-40-PM.png

Agora, dentro da pasta exemplo-2 execute o comando abaixo:

> npm install
Enter fullscreen mode Exit fullscreen mode

Esse comando instalará todas as dependências necessárias ao nosso projeto e criará uma pasta chamada node_modules

Ótimo! Agora vamos começar a desenvolver!

Desenvolvendo o arquivo 'App.js'

Depois de termos criado a estrutura do projeto e instalado as dependências necessárias na nossa aplicação, vamos abrir o arquivo app.js e adicione o seguinte código:

/**
 * Arquivo: app.js
 * Descrição: arquivo principal e responsável pela execução da aplicação.
 * Data: 05/07/2019
 * Author: Glaucia Lemos
 */

const express = require('express');

const app = express();

const port = process.env.PORT || 3000;

app.get('/', (req, res) => {
  res.status(200).send({
    success: 'true',
    message: 'Seja Bem-Vindo(a) ao mundo Docker!',
    version: '1.0.0',
  });
});

app.listen(port);
console.log('Aplicação executando na porta ', port);
Enter fullscreen mode Exit fullscreen mode

Agora vão até o prompt de comando e execute o comando: nodemon e depois abram o postman e coloquem a url: http://localhost:3000/ e vejam se Api está funcionando:

Screen-Shot-07-05-19-at-04-30-PM.png

Se aparecer a mensagem como na imagem acima é porque está tudo funcionando perfeitamente! Vamos continuar com o nosso tutorial!

Desenvolvendo o arquivo 'Dockerfile'

O próximo passo agora é criar o arquivo Dockerfile. Esse arquivo, como já dito antes, é como se fosse um manifesto, onde possui todas as instruções necessárias para compilar e executar a nossa aplicação. Mas, o que é necessário para executar a aplicação por meio do Dockerfile? Vamos precisar:

  • copiar todos os arquivos da aplicação através do contêiner docker.

  • instalar as dependências

  • abrir uma porta no contêiner que será acessado, por assim dizer, no lado de fora.

  • instruir o contêiner como iniciar a nossa aplicação

Numa aplicação mais complexa, precisamos informar mais coisas, tais como: configuração de variáveis de ambiente ou definição de credenciais para uma base de dados ou executar uma determinada base de dados entre outros pontos. Porém, como se trata de uma aplicação simples, vamos criar o Dockerfile de uma maneira bem simples:

//==> Dockerfile

FROM node:latest

WORKDIR /app

COPY . .

RUN npm install

EXPOSE 3000

ENTRYPOINT ["node", "app.js"]
Enter fullscreen mode Exit fullscreen mode

Vamos entender o que cada comando faz:

  • FROM aqui estamos selecionando uma imagem do sistema operacional do Docker Hub. O Docker Hub é um repositório global que contém imagens que podemos extrair localmente. No nosso caso, estamos escolhendo uma imagem baseada no Ubuntu que possui o Node.js instalado, chamado de node. Também especificamos que queremos uma versão mais recente do Node.js, usando a seguinte tag: latest

  • WORKDIR aqui significa simplesmente que definimos um diretório de trabalho. Esta é uma maneira de configurar o que vai acontecer mais tarde, no próximo comando abaixo.

  • COPY aqui estamos copiando os arquivos do diretório em que estamos, especificando um diretório através desse comando.

  • RUN aqui executaremos um comando no termina. No nosso caso, estaremos instalando todas as bibliotecas que precisamos para construir a nossa aplicação em Node.js com Express.

  • EXPOSE aqui significa que estaremos abrindo uma determinada porta. E é através dessa porta que nos comunicaremos com o nosso contêiner.

  • ENTRYPOINTS esse é o lugar onde devemos declarar como iniciaremos a nossa aplicação. Os comandos precisam ser especificados como um array, por exemplo: ["node", "app.js"]. É como se executássemos o comando no terminal: 'node app.js'

Criando uma Imagem

Para criarmos uma imagem, precisaremos seguir 2 passos, são elas:

  • Criando uma imagem: com ajuda do arquivo 'Dockerfile' e o comando docker build criaremos uma imagem

  • Iniciando o contêiner: agora que temos uma imagem, precisaremos criar um contêiner.

Vamos realizar esses passos agora! Abre a pasta do projeto exemplo-2 e execute o seguinte comando: (não se esqueçam de executar o Docker Desktop!!!)

> docker build -t exemplo-2-node-docker/node:latest .
Enter fullscreen mode Exit fullscreen mode

Notem que tem um ponto (.) no final. Ele precisa ser incluso para criar a nossa imagem! Pois ele instrui o Docker e informa onde o Dockerfile está localizado, no nosso caso no diretório em que vocês estão. Se você não tiver a imagem do sistema operacional, pedimos no comando FROM e nos levará para que seja baixado pelo Docker Hub e enfim, criará a imagem especificada no Dockerfile. O prompt de comando de vocês deverá parecer como no gif abaixo (dependendo da conexão da internet esse comando pode levar alguns minutos para ser concluído):

gif-3d3a08bc2e12687ca.gif

O que vemos no gif acima é como a imagem do SO node:latest está sendo trazido e baixado pelo Docker Hub. E em seguida, cada um dos nossos comandos está sendo executado como WORKDIR, RUN e assim por diante. O que vemos aqui é o Docker executando de maneira muito inteligente e armazenando em cache todas as camadas de arquivos diferentes após cada comando, para que ele seja mais rápido. No final, vemos a mensagem: successfully built indicando que a nossa imagem foi construída com sucesso! Vamos dar uma olhada e ver como foi criada a nossa imagem. Abre o prompt e digite o seguinte comando:

> docker images
Enter fullscreen mode Exit fullscreen mode

Screen-Shot-07-05-19-at-08-08-PM.png

Se aparecer como a figura acima é porque foi criado com sucesso a imagem!

Criando um Contêiner

O próximo passo é pegar a nossa imagem criada e construir um contêiner dela. Um contêiner é essa peça isolada que executa a nossa aplicação dentro dele. Nós criamos um contêiner executando o comando: docker run. Abre o prompt novamente e execute o seguinte comando abaixo:

> docker run -p 8000:3000 exemplo-2-node-docker/node
Enter fullscreen mode Exit fullscreen mode

Lembra que no arquivo Dockerfile definimos uma porta? E que já explicamos no artigo anterior que precisamos de uma porta interna mapeando para uma porta externa, na máquina host. Lembrem-se que esta é uma aplicação que queremos executar no browser. Por isso devemos mapear da seguinte maneira:

-p [porta externa]:[porta interna]
Enter fullscreen mode Exit fullscreen mode

Observem no gif a execução do comando acima:

gif-4.gif

Notem que no browser, devemos colocar a porta externa que no nosso caso é a porta: 8000.

Porém, para parar de executar esse container precisamos executar alguns comandos:

  1. Primeiro devemos listar os contêineres que estão executando
> docker container ls 
Enter fullscreen mode Exit fullscreen mode
  1. Depois copiar o Id do container e executar o seguinte comando:
> docker kill <Id do Container>
Enter fullscreen mode Exit fullscreen mode

Vejam no gif abaixo:

gif-5.gif

Melhorando a Configuração com variáveis de ambiente

Show de bola! Já aprendemos a criar nossa primeira imagem do Docker com Node.js & Express, aprendemos como devemos executar um contêiner e enfim executamos a nossa aplicação usando o arquivo DockerFile. Porém, podemos melhorar a variável PORT no arquivo DockerFile. Pois da maneira que está, estará propenso a erros.

Para resolver esse problema, podemos incluir uma variável de ambiente. Com isso, vamos fazer duas alteraçõe:

  • adicionar uma variável de ambiente ao Dockerfile
  • ler a partir da variável de ambiente no arquivo app.js

Adicionnado uma variável de ambiente

Para isso, precisamos usar o comando ENV. Abram o arquivo DockerFile e alteram conforme abaixo:

FROM node:latest

WORKDIR /app

COPY . .

ENV PORT=3000

RUN npm install

EXPOSE 3000

ENTRYPOINT ["node", "app.js"]
Enter fullscreen mode Exit fullscreen mode

Vamos fazer mais uma alteração! Vamos atualizar agora a variável EXPOSE, daí então nos livraremos de valores estáticos e confiaremos em variáveis, da seguinte forma:

FROM node:latest

WORKDIR /app

COPY . .

ENV PORT=3000

RUN npm install

EXPOSE $PORT

ENTRYPOINT ["node", "app.js"]
Enter fullscreen mode Exit fullscreen mode

Observem que, mudamos o comando EXPOSE para o $PORT. Um ponto importante a ser falado aqui: qualquer variável que usamos, precisa ser prefixada com um caractere $

> EXPOSE $PORT
Enter fullscreen mode Exit fullscreen mode

Lendo o valor da variável de ambiente no arquivo App.js

Podemos ler valores de variáveis de ambiente em Node.js da seguinte forma:

> process.env.PORT
Enter fullscreen mode Exit fullscreen mode

Então, vamos atualizar o arquivo app.js, conforme abaixo:

/**
 * Arquivo: app.js
 * Descrição: arquivo principal e responsável pela execução da aplicação.
 * Data: 05/07/2019
 * Author: Glaucia Lemos
 */

const express = require('express');

const app = express();

const port = process.env.PORT;

app.get('/', (req, res) => {
  res.status(200).send({
    success: 'true',
    message: 'Seja Bem-Vindo(a) ao mundo Docker!',
    version: '1.0.0',
  });
});

app.listen(port);
console.log(`Aplicação executando na porta..: ${port}`);

Enter fullscreen mode Exit fullscreen mode

Observem que, quando fizemos a alteração no arquivo app.js ou no arquivo Dockerfile, precisamos reconstruir nossa imagem. Isso significa que precisamos executar o comando de compilação do docker novamente e, antes disso, precisamos derrubar nosso contêiner com o comandos:

> docker stop
Enter fullscreen mode Exit fullscreen mode
> docker rm
Enter fullscreen mode Exit fullscreen mode

Vamos aprender um pouco mais sobre esses dois comandos nas próximas seções! 😉

Gerenciando o contêiner

Ótimo! Você acabou de iniciar o seu contêiner e percebe que não terminar/fechar a execução no terminal. É aí que bate aquele pânico! 😳😳

Nesse caso, você pode fazer o seguinte, abre uma outra janela do terminal e execute o seguinte comando abaixo:

> docker ps
Enter fullscreen mode Exit fullscreen mode

Esse comando listará todos os contêineres que estão em execução na sua máquina. Você poderá ver o nome dos contêineres e os seus respectivos Id's, conforme a imagem abaixo:

Screen-Shot-08-03-19-at-04-41-PM.png

Como você pode perceber acima, temos a coluna CONTAINER_ID e a coluna NAMES. Ambos esses valores funcionarão para interromper nosso contêiner, porque é isso que precisamos fazer, assim:

> docker stop 3da
Enter fullscreen mode Exit fullscreen mode

Nós optamos por usar a coluna CONTAINER_ID e só precisamos colocar os 3 primeiros dígitos, sem necessidade de incluir todo o ID. Depois de executar esse comando, percebam que após digitar docker ps não aparecerá mais nenhum contêiner e parará de executar a nossa imagem!

Modo Daemon

Podemos fazer como fizemos acima e abrir um novo terminal separado. Porém, executar no Modo Daemon, que nesse caso é a melhor opção. Isso significa que executaremos o contêiner em segundo plano e todas as saídas dele não estarão visíveis. Para que isso aconteça, basta adicionar a flag -d. Vamos tentar fazer isso?!

> docker run -d -p 8000:3000 exemplo-2-node-docker/node
Enter fullscreen mode Exit fullscreen mode

Screen-Shot-08-03-19-at-04-56-PM.png

Observem que agora temos o Id do Contêiner. Agora fica mais fácil parar de executar! Basta digitarmos os 3 primeiros dígitos do ID acima. Exemplo:

> docker stop 014 
Enter fullscreen mode Exit fullscreen mode

Docker kill vs Docker stop

Até agora temos usado o comando docker stop como forma de parar o contêiner. Porém, existe uma outra maneira de pararmos o contêiner, ou seja, executando o comando docker kill. Mas, qual é a diferença?

  • docker stop: esse comando envia o sinal SIGTERM seguido por SIGKILL após um período de carência. Resumindo, essa é uma maneira de reduzir o contêiner de uma maneira mais elegante, o que significa que ele libera recursos e salva o estado.

  • docker kill: esse comando envia SIGKILL imediatamente. Isso significa que a liberação de recursos ou a salvaguarda de estado podem não funcionar como planejado. Em desenvolvimento, não importa qual dos dois comandos está sendo usado, mas, em um cenário de produção é mais prudente confiar no comando docker stop

Limpando os Contêineres

Em algum momento, durante o percurso do desenvolvimento de alguma aplicação, você acabará criando inúmeros contêineres. Então, para garantir de remover/limpar esses contêineres, bastam digitar o comando abaixo:

> docker rm <id-do-contêiner>
Enter fullscreen mode Exit fullscreen mode

Exemplo:

> docker rm -f ef8
Enter fullscreen mode Exit fullscreen mode
> docker ps
Enter fullscreen mode Exit fullscreen mode

Palavras Finais

Agora sim...aprendemos nesse artigo como criar uma aplicação Node.js & Express.js usando Docker de maneira prática e passo a passo. Aprendemos alguns comandos importantes do Docker e isso não ficará por aqui. Pois ainda teremos muitas coisas importantes para aprender, como por exemplo: como trabalhar com bancos de dados, volumes, como vincular contêineres e por que e como gerenciar vários contêineres, também conhecidos como orquestração.

Mas esta é uma série de artigos! E temos que parar em algum lugar ou este artigo será muito longo. Fiquem ligados e atentos para a próxima parte, onde falaremos sobre Volumes e Bancos de Dados.

Palavras Finais

Hoje aprendemos na prática como é fácil criar uma aplicação Node.js & Express.js usando o Docker.

Caso queiram aprender e aprofundar mais os estudos em Docker, recomendo os cursos de Docker abaixo:

Curso Grátis - Criando um aplicativo Web em contêiner com o Docker

Vários Cursos Grátis de Docker

E, para ficarem por dentro das últimas atualizações não deixem de me seguir no Twitter!

Twitter

E desde já, agradeço novamente ao Chris Noring por ceder essa excelente série de artigos sobre Docker. E sigam ao Chris Noring também no twitter:

Screen-Shot-08-03-19-at-05-14-PM.png

Até a próxima pessoal!

Discussion (8)

Collapse
leocavalcante profile image
Leo Cavalcante

Me diz uma coisa: você tá de alguma forma ignorando a node_modules do seu host e evitando que ela vá pro contexto do build e consequentemente evitando que ela vá pra imagem com o COPY . .?

Collapse
glaucia86 profile image
Glaucia Lemos Author

é realmente uma boa prática e você pode fazer da seguinte maneira:

Copie seu package.json e package-lock.json antes de copiar seu código no contêiner. O Docker armazenará em cache os node_modules instalados como uma camada separada e, em seguida, se você alterar o código do aplicativo e executar o comando de compilação, o node_modules não será instalado novamente se você não tiver alterado o package.json

Se desejar saber mais detalhes a respeito, você poderá dar uma conferida nesse artigo aqui: dev.to/alex_barashkov/using-docker...

Collapse
leocavalcante profile image
Leo Cavalcante

É, hehe. O segredo é o .dockerignore, que é praticamente o primeiro passo do artigo original, hehe; aí da pra meter um COPY . . com mais tranquilidade e também além de reduzir o tamanho final da imagem, reduz o tempo de build junto porque a primeira coisa que ele faz é mover os arquivos para contexto. Além da pasta node_modules, é legal evitar enviar a .git e o arquivo .env que em muitos projetos contem dados sensíveis como senhas e keys de apis... aí se alguém pega sua imagem, roda, entra nela, vai ter tudo lá :S

Thread Thread
glaucia86 profile image
Glaucia Lemos Author

Posso colocar esse adendo no próximo post?

Thread Thread
leocavalcante profile image
Leo Cavalcante

Pode, claro :)

Collapse
renatosuero profile image
Renato Suero

Primeiro parabéns pelo post, está muito bom. Não sei se conhece, imagens alpine, se não, elas vão deixar tuas imagens menores e mais rápidas para serem criadas/baixadas

Collapse
glaucia86 profile image
Glaucia Lemos Author

Legal! Vou dar uma olhada!