Dev Containers
O que são Dev Containers?
[editar | editar código-fonte]Dev Containers são uma tecnologia que se usa de contêineres para a criação de ambientes de desenvolvimento reprodutíveis. Através deles é possível realizar o setup de um projeto para desenvolvimento de forma ágil e sem conflitos com softwares já instalados na máquina alvo.
Existem diferentes implementações de Dev Containers, aqui vamos abordar mais especificamente a implementação usada pelo Visual Studio Code que usa Docker como motor de conteinerização através da extensão Dev Containers.
Como criar Dev Containers?
[editar | editar código-fonte]Dev Containers são definidos por um arquivo .devcontainer.json
ou .devcontainer/devcontainer.json
. Dentro desse arquivo existem muitas propriedades que podem ser definidas para customizar o ambiente de desenvolvimento. A documentação completa pode ser vista em Dev Containers metadata reference, mas aqui vamos apresentar as propriedades principais para uma configuração básica.
Configurando o devcontainer.json
[editar | editar código-fonte]"name"
[editar | editar código-fonte]Uma string> que define o nome do ambiente, útil para manter a organização.
{ "name": "My Node Project" }
"containerUser"
[editar | editar código-fonte]Especifica qual usuário ser usado dentro do contêiner. Por padrão o usuário root
será usado e isso não é uma boa prática.
{ "containerUser": "dev" }
Note que essa propriedade define qual usuário vai ser usado, ela não vai criar o usuário por você, mas sim usar um usuário já criado pela imagem ou Dockerfile.
"image"
[editar | editar código-fonte]Define qual a imagem deve ser usada para o contêiner do ambiente. A Microsoft disponibiliza dezenas de imagens prontas para determinados ambientes através do Microsoft Container Registry. Mas é possível usar imagens de qualquer registro de contêiner (Dockerhub, por exemplo).
A título de contexto, uma imagem define um modelo para criação dos contêineres. Dois contêineres criados a partir de uma mesma imagem devem ter comportamento idêntico. Assim, dois dev containers criados a partir de uma mesma imagem definirão ambientes de desenvolvimento idêntico para os desenvolvedores.
{ "image": "mcr.microsoft.com/devcontainers/typescript-node" }
"build"
[editar | editar código-fonte]Alternativamente, ao invés de usar uma imagem pronta, pode-se definir a imagem usando um Dockerfile. Essa propriedade é definida com um objeto, abaixo vamos indicar os atributos mais importantes desse objeto.
Apenas vale ressaltar, que toda vez que citarmos um caminho, ele é relativo ao local onde se encontra o .json.
"build"."dockerfile"
[editar | editar código-fonte]Define o caminho do Dockerfile a ser usado para construir a imagem do ambiente.
"build"."context"
[editar | editar código-fonte]Contexto do build da imagem (similar ao context usado com o comando docker build [CONTEXT]
).
Esse contexto indica a que os caminhos são relativos em instruções como COPY
no Dockerfile.
"build"."args"
[editar | editar código-fonte]Uma lista com os argumentos definidos no Dockerfile com a instrução ARG
.
{ "build": { "dockerfile": "Dockerfile", "context": "..", "args": { "USER": "dev", "NODE_VERSION": "22" } } }
"dockerComposeFile"
[editar | editar código-fonte]É possível usar uma composição de contêineres como ambiente de desenvolvimento, ao invés de um contêiner único definido com "image" ou "build". Aqui, se define onde está o arquivo docker-compose.yaml
.
{ "dockerComposeFile": "docker-compose.yaml" }
"service"
[editar | editar código-fonte]Quando usando "dockerComposeFile"
é necessário indicar qual dos serviços definidos é o serviço principal, onde o VS Code irá operar.
{ "dockerComposeFile": "docker-compose.yaml", "service": "app" }
"forwardPorts"
[editar | editar código-fonte]Quando criando projetos que vão expor aplicações à rede, pode-se realizar port forwarding das portas de dentro do contêiner para o host, para que a aplicação possa ser acessada de fora do ambiente de desenvolvimento.
{ "forwardPorts": [3000] }
Em um cenário com Docker Compose, você pode especificar de qual serviço é a porta com a sintaxe "service:port"
.
"mounts"
[editar | editar código-fonte]Permite montar diretórios do hospedeiro dentro do contêiner do ambiente de desenvolvimento.
{ "mounts": [ { "type": "volume", "source": "some-volume", "target": "/folder/in/container" }, { "type": "bind", "source": "/folder/in/host", "target": "/folder/in/container" } ] }
"workspaceMount" e "workspaceFolder"
[editar | editar código-fonte]Usados em conjunto para definir como o projeto será montado dentro do ambiente conteinerizado.
Por exemplo:
{ "workspaceMount": "source=${localWorkspaceFolder},target=/workspace,type=bind,consistency=cached", "workspaceFolder": "/workspace" }
Com isso, a pasta do projeto (pasta aberta no VS Code) será montada em /workspace dentro do contêiner assim que o ambiente for iniciado.
"customizations"."vscode"
[editar | editar código-fonte]Permite que definam configurações para o VS Code que irá executar dentro do ambiente. Dentre as customizações disponíveis estão, por exemplo: extensões.
{ "customizations": { "vscode": { "extensions": [ "dbaeumer.vscode-eslint", "esbenp.prettier-vscode" ] } } }
"postCreateCommand"
[editar | editar código-fonte]Define um comando a ser executado imediatamente após o ambiente ser criado. Isso é muito útil para instalar as dependências do projeto que precisam que a pasta do projeto seja montada dentro do contêiner. Por exemplo:
{ "postCreateCommand": "bash -i -c 'npm install'" }
"postStartCommand"
[editar | editar código-fonte]Define um comando a ser executado toda vez que o ambiente é iniciado, diferentemente do "postCreateCommand" que é executado apenas uma vez.
Variáveis de Ambiente
[editar | editar código-fonte]Em algumas das strings acima é possível usar variáveis de ambiente para definir valores.
${localEnv:VAR}
: usa uma variável de ambiente do host${containerEnv:VAR}
: usa uma variável de ambiente do contêiner${localWorkspaceFolder}
: diretório onde se encontra o projeto no host${containerWorkspaceFolder}
: diretório onde se encontra o projeto no contêiner
Como Usar Dev Containers?
[editar | editar código-fonte]O VS Code disponibiliza a extensão Dev Containers que é usada para gerenciar espaços de desenvolvimento. Normalmente, assim que um diretório for aberto e ele tiver um .devcontainer.json
ou .devcontainer/devcontainer.json
um pop-up será exibido sugerindo Reopen in Container. Além disso, a command palette do VS Code (pode ser aberta com Ctrl+Shift+P
) fornece alguns comandos, alguns dos mais importantes são:
Reopen in Container
: abre o VS Code dentro do contêirner do ambiente de desenvolvimentoRebuild and Reopen in Container
: similar ao anterior, mas realiza o rebuild do(s) contêiner(es) previamente, isso faz com que o postCreateCommand seja executado novamente.Rebuild without Cache and Reopen in Container
: similar ao anterior, mas descarta o cache antes do rebuild.New Dev Container
: cria um novo DevContainer, a partir de uma diversa seleção de imagens preparadas.
O reopen vai fazer o VS Code reabrir conectado ao ambiente de desenvolvimento do contêiner pronto para uso.
Resolvendo Problemas
[editar | editar código-fonte]Muitas vezes, sua definição de Dev Container não estará correta e o ambiente não poderá ser iniciado. Caso isso ocorra, o VS Code irá sugerir Open devcontainer.json locally, isso irá fazer o VS Code voltar ao seu ambiente local (fora do contêiner) com um log de erros do ambiente para que você possa corrigir sua configuração e depois realizar o rebuild.
Talvez sua configuração não esteja correta, mas não esteja errada o suficiente para que o ambiente não possa ser iniciado. Assim, você pode editar o arquivo de definição do Dev Container e imediatamente o VS Code irá sugerir um rebuild do ambiente.
Aplicações
[editar | editar código-fonte]Testagem de software
[editar | editar código-fonte]Para facilitar a testagem, é necessário ter um ambiente controlado para que cada rodada de testes seja o mais parecida uma com a outra possível, em relação a uso de processamento, memória e tempo de execução. Isso facilita a análise dos possíveis problemas e possíveis pontos de melhoria, além de observar os impactos dessas melhorias e/ou mudanças.de melhoria, além de observar os impactos dessas melhorias e/ou mudanças.
Isso inclui, entre outras coisas, a automação dessa testagem, que pode ser feita utilizando postCreateCommand ou postStartCommand.
Isolamento de ambientes
[editar | editar código-fonte]Para desenvolvedores, que possuem uma grande quantidade de projetos em trabalho, isolar cada um desses projetos é importante para que as dependências e configurações não possam interferir no funcionamento de uns dos outros. Isso é importante principalmente em projetos que utilizam dependências e linguagens muito parecidas, o que pode confundir tanto o desenvolvedor, quanto o sistema, caso os Dev Containers não sejam usados.
Portabilidade
[editar | editar código-fonte]Caso o desenvolvedor possua mais de uma máquina ou faça alterações nessa máquina, os Dev Containers serão responsáveis por proporcionar o mesmo ambiente, independente do ambiente atual da máquina usada. Claro que, é necessário que o Container deve ser configurado de forma que seja um "piso" de capacidade de processamento, em relação aos ambientes de desenvolvimento disponíveis.
Reprodutibilidade
[editar | editar código-fonte]Para garantir que a execução, desenvolvimento e testes feitos em ambiente sejam reproduzidos da forma mais fiel possível, garantindo ambiente imutáveis. Isso evita problemas do tipo: "ah, mas no meu funciona!".
Exemplo
[editar | editar código-fonte]{ "name": "node-app", "image": "docker.io/node:22", "containerUser": "node", "forwardPorts": [ 3000 ], "workspaceFolder": "/app", "workspaceMount": "source=${localWorkspaceFolder},target=/app,type=bind,consistency=cached", "customizations": { "vscode": { "extensions": [ "dbaeumer.vscode-eslint", "esbenp.prettier-vscode" ] } }, "postCreateCommand": "bash -i -c 'cd /app && npm install'" }
O exemplo acima cria um simples ambiente para desenvolvimento de aplicações usando Node.js. Nesse ambiente temos um contêiner criado com Node 22, que expõe a porta 3000 para o sistema e vai montar o diretório do projeto no diretório /app
do contêiner. Sempre que dispararmos um rebuild o iremos executar um npm install
para instalar as dependências do projeto.
Observe que tudo isso ocorre dentro de um contêiner e portanto, as dependências de projeto são completamente independentes do que você tiver instalado no seu computador, evitando assim conflitos durante o setup.