Ambiente de desenvolvimento com Docker e Visual Studio Code
No final de dezembro resolvi criar uma aplicação web utilizando Node.js para validar se os meus conhecimentos de Aria Automation e Orchestrator seriam suficientes para fazer algo além de scripts. No final das contas entendi que é um pouco mais complexo do que imaginei, mas funcionou. Após iniciar o desenvolvimento, percebi que não estava feliz com o Node.js e MySQL instalados em uma VM, visto que não gosto da ideia de instalar diretamente na máquina física, mas gosto de trabalhar com os arquivos localmente (sim haha). Por este motivo, busquei alternativas para resolver o problema que eu mesmo criei. O que vou compartilhar aqui é a forma que melhor funcionou para mim, onde basicamente utilizo containers do Docker integrados ao Visual Studio Code.
O processo é relativamente simples:
- Imagens
- Vamos utilizar o Dockerfile com a imagem oficial do MySQL servindo como banco de dados e a imagem oficial do Ubuntu para o Node.js
- Serviço
- Com o uso do Docker Compose iremos configurar a forma como os containers irão trabalhar
- Execução & testes
- Só criar os containers, testar se tudo está funcionando e correr pro abraço
- Integração com Visual Studio Code
- Pronto, shoooow me the code!
É esperado que o Docker já esteja instalado no seu computador e que você tenha um conhecimento básico em como o Docker e containers em geral funcionam. A minha estação de trabalho é MacOS, mas poderia ser Windows ou Linux, os comandos são os mesmos.
Imagens
Node.js
Vamos começar preparando as imagens. Organização é tudo, então visando facilitar a execução vamos criar algumas pastas. Será uma pasta por imagem, vamos aos detalhes:
- nodejs-conza
- Dockerfile
- .dockerignore
- app.js
- package.json
- .env
- node_modules
- … e outros arquivos do projeto
- mysql-conza
- Dockerfile
- init.sql
Agora que as pastas e arquivos estão criados, vamos colocar um pouco de código dentro destes arquivos. A primeira imagem será o Ubuntu. O Dockerfile irá criar uma imagem baseado no ubuntu:latest, instalará o Node.js, npm, nodemon e dependências do projeto (baseado no package.json) e por fim, realiza a cópia de todo o conteúdo da pasta (basicamente seu projeto Node.js inteiro para dentro do container).
Wait, copia tuuuuudo? não exatamente. O arquivo .dockerignore serve para ignorar alguns arquivos, ou seja, tudo o que estiver dentro deste arquivo será ignorado pelo Docker durante o processo de cópia (mesma lógica do .gitignore). Neste caso, queremos ignorar a cópia do Dockerfile em si, a pasta dos módulos do Node.js e .DS_Store. Ficaria mais ou menos assim:
node_modules Dockerfile .DS_Store
O Dockerfile fica assim:
# imagem docker FROM ubuntu:latest # instalar os pacotes necessarios RUN apt-get update && \ apt-get install -y curl sudo && \ curl -fsSL https://deb.nodesource.com/setup_21.x | sudo -E bash - && \ sudo apt-get install -y nodejs && \ npm install -g npm@10.4.0 && \ npm install -g nodemon # cria e define o diretorio de trabalho WORKDIR /conza/world # instala as dependencias do projeto COPY package*.json ./ RUN npm install # copia o projeto inteiro para o container COPY / ./ # escuta a porta 3000 EXPOSE 3000 # roda o comando CMD ["nodemon", "app.js"]
Tem um ponto importante sobre a conexão entre a aplicação e o banco de dados e isso pode variar bastante, mas aqui estou usando um arquivo .env na raiz do projeto Node.js, então em DB_HOST eu simplesmente informei o nome do container e as credenciais (vamos configurar estas informações na criação da imagem do MySQL). Não se preocupe, o Docker se vira pra resolver este nome. Exemplo do meu arquivo .env.
# Environment NODE_ENV=development PORT=3000 # Database DB_HOST=world-database DB_NAME=ovo DB_USER=ovo_user DB_PASS=MyS3cur3Huev!to
Pronto, agora basta executar o comando docker build -t nome-da-imagem:tag . . No meu caso ficou docker build -t nodejs-conza:latest . (perceba que tem um PONTO no final do comando). Se tudo ocorreu bem, então você deve ver a imagem com o comando docker images.
Se quiser testar, basta criar um container com o comando docker run -p 3000:3000 -d nodejs-conza:latest e abrir localhost:3000 (pode ser via navegador ou curl -v localhost:3000).
É fundamental garantir que a imagem esteja funcionando conforme esperado. Se este for o caso, então vamos seguir para a criação da imagem do MySQL.
MySQL
O processo muda um pouquinho, mas a lógica é exatamente igual. Aqui precisaremos do Dockerfile e do init.sql. O Dockerfile irá criar uma imagem baseado no mysql:latest, configura a senha de root do MySQL, fica ouvindo na porta 3306, permite conexões externas (a partir do container do Node.js) e por fim copia o init.sql para docker-entrypoint-initdb.d. Calma, vamos por partes.
O Dockerfile ficaria assim:
# imagem docker FROM mysql:latest # variaveis do ambiente ENV MYSQL_ROOT_PASSWORD=MyS3cur3P@ss # expoem a porta EXPOSE 3306 # permite conexões externas RUN echo "[mysqld]" > /etc/mysql/my.cnf && \ echo "bind-address = 0.0.0.0" >> /etc/mysql/my.cnf # configura banco, tabelas, usuario, etc COPY init.sql /docker-entrypoint-initdb.d/ # inicia o MySQL CMD ["mysqld"]
A cereja do bolo fica por conta do init.sql, este arquivo pode criar o banco de dados, tabelas, usuários, enfim, tudo o que você faria diretamente no MySQL pode ser feito com nele. Claro, isso vai mudar para cada projeto, mas ficaria algo mais ou menos assim:
-- cria um banco de dados chamado ovo e seleciona ele CREATE DATABASE IF NOT EXISTS ovo; USE ovo; -- cria uma tabela chamada users no banco de dados ovo CREATE TABLE IF NOT EXISTS users ( user_id INT AUTO_INCREMENT PRIMARY KEY, user_name VARCHAR(100), user_user VARCHAR(50), user_pass VARCHAR(255), user_type INT ); -- cria o usuário ovo_user (perceba o '%') CREATE USER 'ovo_user'@'%' IDENTIFIED BY 'MyS3cur3Huev!to'; -- permite o usuário ovo_user no banco de dados ovo GRANT ALL PRIVILEGES ON ovo.* TO 'ovo_user'@'%' WITH GRANT OPTION; -- aplica tudo FLUSH PRIVILEGES;
É só isso, agora basta executar o comando para criar a imagem e ver a mágica acontecer. No meu caso, ficaria assim docker build -t mysql-conza . 🙂
Aquele teste básico para validar se a imagem está funcionando: docker run -p 3306:3306 -d mysql-conza:latest. Se estiver tudo certo, então pode remover os dois containers de teste e vamos partir para a criação do serviço.
Serviço
Para o Docker, serviços são um conjunto de containers executados juntos, como uma aplicação e banco de dados, por exemplo. É exatamente isso que vamos criar agora. Quem irá nos ajudar com esta tarefa é o Docker Compose. Precisamos basicamente criar um arquivo chamado docker-compose.yml usando este código:
version: '3' # configurações do serviço de banco e dados services: world-database: # imagem que acabamos de criar do MySQL image: mysql-conza:latest container_name: world-database ports: - '3306:3306' # volume para persistência dos dados (se não existir o Docker cria) volumes: - world-mysql-data:/var/lib/mysql # configurações do serviço da aplicação world-app: # imagem que acabamos de criar do Node.js image: nodejs-conza:latest container_name: world-app ports: - '3000:3000' # volume para persistência dos dados (se não existir o Docker cria) volumes: - world-nodejs-data:/conza/world # a aplicação depende do banco de dados depends_on: - world-database # rede do Docker (se não existir o Docker cria) networks: conza-net: driver: bridge volumes: world-mysql-data: driver: local name: world-mysql-data world-nodejs-data: driver: local name: world-nodejs-data
Estamos dando algumas instruções para o Docker saber como configurar o serviço que queremos, neste caso, pedimos para o Docker criar um serviço com dois containers com configurações bem especificas para cada um (imagem que acabamos de criar, porta, com volume externo para persitência dos dados e com uma rede do Docker). Não se preocupe em criar a rede e volumes previamente, se não existir, o Docker cria isso pra você, basta definir o nome que você deseja no Docker Compose.
Execução & testes
Se você chegou até aqui eu tenho uma excelente noticia: o pior já passou. Agora é só executar o comando para subir o serviço e ver a mágica acontecer. Para isso, manda bala no docker compose up.
Configurei o winston para os logs da minha aplicação, com isso é possível observar que a conexão com o banco de dados funcionou perfeitamente.
E por fim, basta chamar no localhost:3000 que já vejo o World rodando lindão.
Visual Studio Code
Eu gosto da ideia de desenvolver localmente, me parece otimizar bastante os processos, além de evitar depedências externas. Ah, quando escrevo “localmente” entenda diretamente na minha estação de trabalho e não em uma máquina virtual ou servidor remoto. Por este motivo, mesmo que o código está em um container e o banco em outro, ainda estão na minha máquina local e isso é bem diferente de estar em uma máquina virtual. O que precisamos aqui é apenas uma extensão do Visual Studio Code chamada Dev Containers. Essa maravilha consegue identificar os containers que estão em execução no Docker e abrir o código como se estivesse local, com isso, é possível usar todos os recursos que o Visual Studio Code oferece, incluindo, por exemplo, a sincronização do código com o Git, entendeu onde quero chegar, né?!
Vamos lá. Após instalar a extensão Dev Containers, navegue até a opção Remote Explorer. Você verá os seus containers, então basta selecionar o container da aplicação.
Na próxima tela, o Visual Studio irá permitir explorar os arquivos do container para abrir a pasta do seu projeto. No meu caso, configurei para ficar dentro de /conza/world. Então é só apontar para este diretório e voilà!
Prontoooooo!
Resultados
- Chega de instalar milhares de coisas na sua máquina local
- Aplicação Node.js e banco de dados MySQL rodando em containers diferentes
- Containers com volumes externos, ou seja, se você deletar os containers e recria-los com o arquivo do Docker Compose, as informações do seu banco de dados e o seu código estarão lá
- Não depende de um sistema operacional especifico (roda em MacOS, Linux ou Windows)
- Escalabilidade – novas funcionalidades podem ser novos containers
- Com o Visual Studio Code e a extensão a experiência é sensacional
- Último e mais importante: it’s so cool! Agora você pode falar que usa containers HAHA
E você, é do time que instala tudo local ou tem outra estratégia. Compartilhe que estou curioso para saber. Abraço e até a próxima!