DEV Community

Andrey Araújo
Andrey Araújo

Posted on

Usando Docker e docker-composer no dia a dia

Vou mostrar aqui apenas como se inicia um projeto em Node e Typescript, com Docker e banco de dados Postgres. Espero que o que tem aqui os inspire a ir em busca de mais conhecimento sobre o assunto.

Sumário

  1. Iniciando o projeto
  2. Arquivos iniciais
  3. Criando o arquivo Dockerfile
  4. Docker Compose
    1. Mão na massa...
  5. Usando variáveis de ambiente no docker compose
  6. Conclusão
  7. Links úteis

Iniciando o projeto

Para iniciar o projeto vou rodar o comando yarn init -y, caso esteja usando o npm é só trocar por npm init -y. Com isso vai ser criado o arquivo package.json.

Logo em seguida vamos instalar todas as dependências do projeto:

  • yarn add express
  • yarn add -D @types/express
  • yarn add -D typescript ts-node nodemon
  • yarn tsc —init (Para criar o arquivo tsconfig.json)

Com todas as dependências instaladas, agora vamos começar a codar.

Arquivos iniciais

Na raiz do seu projeto crie uma pasta chamada src e dentro dela crie dois arquivos, index.ts e routes.ts. No arquivo index.ts vamos ter o seguinte código:

// 1
import express from 'express';

// 2
import routes from './routes';

// 3
const app = express();

// 4
app.use(express.json());

// 5
app.use(routes);

// 6
app.listen(3000, () => console.log('🔥 Server started at http://localhost:3000'));
Enter fullscreen mode Exit fullscreen mode
  1. Importamos o express.
  2. Importamos o arquivo de rotas.
  3. Criamos uma variável chamada app e atribuímos a ela o módulo com funções do express.
  4. Configuramos para que o app faça o parse do JSON.
  5. Dizemos para o app usar o arquivo de rotas.
  6. Dizemos para o app subir o servidor na porta 3000.

Agora vamos para o arquivo de rotas. No arquivo routes.ts coloque o seguinte código:

import { Router } from 'express';

const routes = Router();

routes.get('/', (req, res) => {
  res.send('Olá Mundo!');
});

export default routes;
Enter fullscreen mode Exit fullscreen mode

Apenas criamos uma rota do tipo GET que devolve uma resposta "Olá Mundo!", sem muita complicação, aqui é moleza!

Por último, mas não menos importante, no arquivo package.json temos que inserir um script para subir a aplicação, então, coloque o seguinte código logo antes das declarações das dependências do projeto:

"scripts": {
  "dev": "npx nodemon --exec ts-node ./src/index.ts --ignore-watch node_modules"
},
Enter fullscreen mode Exit fullscreen mode

Aqui estamos dizendo pro nodemon executar o ts-node partindo do arquivo index.ts ignorando a pasta node_modules. Nada de outro mundo aqui.

E para testar tudo no seu terminal, rode o comando yarn dev ou npm run dev, o resultado deve ser mais ou menos esse:

Alt Text

Criando o arquivo Dockerfile

Depois de ter criado a aplicação e testado, vamos criar o arquivo Dockerfile. Nesse arquivo vai conter apenas as configurações iniciais do projeto para criar nossa imagem, como por exemplo, versão do node.

Mas antes disso você sabe o que é o Dockerfile? Para que serve?

Dockerfile é o arquivo onde definimos as instruções para criar as nossas próprias imagens. Ele tem sua própria sintaxe com os seus respectivos comandos. Nele é como se tivesse uma receita de bolo, só que no nosso caso o bolo é a aplicação, é uma receita para criar nossa imagem da aplicação.

Para este exemplo vamos colocar o seguinte conteúdo no nosso arquivo:

FROM node:alpine

WORKDIR /usr/src/app

COPY package*.json ./

RUN yarn

COPY . .

EXPOSE 3000

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

Vamos saber pra que serve cada instrução dessa.

FROM → De onde vai baixar a imagem que vamos utilizar, no caso vamos usar a ver alpine do node, que é uma versão mais simplificada.

WORKDIR → Define o diretório onde a aplicação vai ficar no disco do container, aqui você pode usar o diretório que preferir.

COPY → Copia tudo que começa com package e termina com .json para dentro da pasta /usr/src/app.

RUN → Executa o yarn ou npm install para adicionar as dependências do projeto e criar a pasta node_modules.

COPY → Copia tudo que está no diretório onde o arquivo Dockerfile está para dentro da pasta que definimos no WORKDIR.

EXPOSE → Expomos uma porta para o container ficar ouvindo os acessos.

CMD → Executa o comando yarn dev que está nos scripts do package.json para iniciar a aplicação. Aqui separamos todas as palavras por vírgula dentro de um array.

Crie um arquivo .dockerignore para ignorar algumas coisas, neste exemplo vamos adicionar a pasta node_modules para ser ignorada.

Agora para verificar se está tudo certo vamos rodar o comando:

docker build -t dockernode .
Enter fullscreen mode Exit fullscreen mode
  • docker build cria uma imagem a partir do Dockerfile
  • -t é o nome da imagem
  • dockernode é o nome que escolhi para essa imagem
  • . é onde o Dockerfile está, o comando será executado no mesmo diretório que o Dockerfile se encontra.

Se a saída no terminal for algo semelhante a isso, deu tudo certo na criação da imagem:
Alt Text

Se você chegou até aqui sem nenhum erro, ótimo, mas ainda faltam algumas coisinhas. Até agora só criamos a imagem, falta criar o container. E para isso temos e executar o comando abaixo:

docker run -p 3000:3000 -d dockernode
Enter fullscreen mode Exit fullscreen mode
  • docker run cria um container.
  • -p 3000:3000 libera a porta 3000 do container para que ele possa ouvir as requisições de fora acessando a porta 3000. Alt Text
  • -d detach, o terminal fica livre e o processo roda em segundo plano. (Se você não passar essa tag você não conseguirá mais usar a aba do terminal, ficará travada mostrando o processo.)
  • dockernode o nome da imagem que estou usando para criar o container.

Alt Text

Rodando o comando será exibido o ID do container e executando no terminal docker ps será listado o processo em execução no Docker.

É interessante observar, a aplicação está rodando dentro do container do Docker, e não na nossa máquina local. Para acessar basta colocar no navegador [http://localhost:3000](http://localhost:3000) que será exibida a mensagem "Olá Mundo!"

O comando docker run só precisa ser executado uma vez para criar o container, para outras operações usamos: docker start <id do container> para iniciar, docker stop <id do container> para parar e docker logs <id do container> para ver os logs.

Docker Compose

Estamos chegando a última parte do nosso exemplo de uso de Dockerfile e Docker Compose, agora vamos ver o que é e como funciona o Docker Compose.

Basicamente o Docker compose é um orquestrador de containers no Docker. Ele vai definir como o container deve se comportar. Anteriormente no dockerfile definimos como a aplicação vai funcionar, o Docker compose vai fazer o banco de dados subir, a aplicação ficar no ar e se conectar com o banco de dados, neste exemplo, mas ele pode fazer muito mais.

Vou mostrar também um recurso muito legal que são os volumes, usamos eles para espelhar os arquivos do projeto na máquina local com o volume do container. Dessa forma toda vez que alterarmos qualquer arquivo na máquina local ele enviará para o container do Docker. (Para isso que instalamos o nodemon).

Mão na massa...

Na raiz do projeto crie o arquivo docker-compose.yml e dentro dele coloque o seguinte código:

version: "3"
services: 
  api:
    image: dockernode
    container_name: "app"
    ports: 
      - "3000:3000"
    links: 
      - link-db
    volumes: 
      - ./:/usr/src/app
  link-db:
    image: postgres
    container_name: "postgres"
    volumes: 
      - ./postgres:/var/lib/postgres
    ports: 
      - "5432:5432"
    environment: 
      - POSTGRES_USER=your_user
      - POSTGRES_DB=your_db
      - POSTGRES_PASSWORD=your_pass
Enter fullscreen mode Exit fullscreen mode
  • version → Especifica a versão do docker-compose file.
  • services → Define um serviço.
  • api → Nome do serviço, aqui você pode colocar o nome que preferir.
  • image → imagem que o serviço vai usar.
  • container_name → Como o próprio nome diz, é o nome do container.
  • ports → Portas que serão usadas no host e no container.
  • links → Link para containers em outro serviço.
  • volumes → Diretório que usamos para espelhar, antes dos dois pontos é o diretório que vamos pegar os arquivos e depois dos dois pontos o diretório de destino, que vai ser o do container.
  • environment → Contém as variáveis de ambiente do banco de dados, aqui definimos o usuário, senha e banco de dados que a aplicação vai usar para se conectar com o banco de dados.

Aqui coloquei a pasta para os arquivos do banco de dados na mesma pasta do projeto, mas só como exemplo, você deve definir uma outra pasta para poder guardar esses arquivos do banco de dados. (Volumes do service link-db)

Antes de executar o comando do docker-compose vamos parar o container e excluí-lo.

Execute no terminal docker ps para conferir se o container está executando, pegue o ID do container e para parar o container rode o comando docker stop <id> e depois execute docker rm <id>  para remover o container, por fim execute o comando abaixo para criar o container e subir o serviço:

docker-compose up
Enter fullscreen mode Exit fullscreen mode

Pronto, ele vai iniciar o serviço fazendo build do projeto conforme o Dockerfile, liberar a porta 3000 e ficará monitorando a pasta do projeto a partir da rootDir e enviar para /usr/src/app.

Para parar o serviço tecle CTRL+C. Pode rodar com docker-compose up -d para rodar em background e liberar o terminal.

Agora sim tudo pronto, já temos o serviço sendo executado, e acessando http://localhost:3000 teremos "Olá Mundo" como retorno.

Usando variáveis de ambiente no docker compose

Um arquivo que nos ajuda e poupa um bom trabalho nos projetos é o arquivo .env , (eu espero que todos usem 😄), com ele definimos todas as variáveis de ambiente que nosso projeto usar, usuário de banco de dados, host, senha, por exemplo.

E como vimos anteriormente, no arquivo docker-compose.yml existem algumas dessas variáveis, mas setamos elas manualmente, isso pode expor dados sensíveis da nossa aplicação, já que esse arquivo fica exposto para todos. Na sessão de environment no banco de dados, vamos substituir algumas coisas. Então vamos lá.

Na raiz do projeto crie um arquivo chamado .env e nele coloque o seguinte código:

# Database
DB_USER=your_user
DB_NAME=your_db
DB_PASSWORD=your_password
DB_PORT=5432
Enter fullscreen mode Exit fullscreen mode

São as mesmas variáveis que estão no arquivo docker-compose.yml, substitua os valores delas pelos seus dados.

No arquivo docker-compose.yml faça as seguinte alterações:

ports: 
  - "${DB_PORT}:5432"
environment: 
  - POSTGRES_USER=${DB_USER}
  - POSTGRES_DB=${DB_NAME}
  - POSTGRES_PASSWORD=${DB_PASSWORD}
Enter fullscreen mode Exit fullscreen mode

Na parte do serviço do banco de dados, coloque os mesmos nomes das variáveis de ambiente que você definiu anteriormente.

Certo, mas como o arquivo do docker-compose.yml vai entender essas variáveis de ambiente se eu não tenho nenhuma configurada na minha máquina?

Para isso temos que criar um arquivo makefile, que vai criar essas variáveis pra gente e fazer com que o arquivo do docker entenda essas variáveis. E para subir a aplicação, ao invés de usar docker-compose up, vamos usar make up.

Então na raiz do seu projeto crie um arquivo chamado Makefile. Esse arquivo é bastante simples, não tem nada demais, apenas algumas instruções:

include .env

.PHONY: up

up:
    docker-compose up -d 

.PHONY: down

down:
    docker-compose down

.PHONY: logs

logs: 
    docker-compose logs -f
Enter fullscreen mode Exit fullscreen mode
  • include vai incluir o arquivo .env e no escopo da execução o arquivo vai entender as variáveis de ambiente como se todas tivessem exportadas.
  • .PHONY força a criação de um rótulo.

Agora você pode executar o comando make up no terminal.

Pronto, acessando http://localhost:3000 no navegador você verá que a aplicação está no ar. E acessando o banco de dados com um aplicativo de sua preferência você verá que o banco de dados foi criado e também já está no ar.

Conclusão

Apesar desse app ter sido bem simples, as vantagens de usar o Docker são grandes, ainda mais quando começamos a usar mais de um banco de dados, vários serviços, e temos que trabalhar em equipe, todos com as mesmas versões e configurações do projeto.

Outra coisa que agrada bastante é que se formos apagar os containers e as imagens, não fica nenhum arquivo no nosso computador, aquele lixo na máquina.

Links úteis

gomex/docker-para-desenvolvedores

Docker e Docker Compose um guia para iniciantes.

Discussion (1)

Collapse
devbaraus profile image
Bruno de Araujo Alves