Dev Containers
O que são Dev Containers?
[editar | editar código]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]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]"name"
[editar | editar código]Uma string> que define o nome do ambiente, útil para manter a organização.
{
"name": "My Node Project"
}
"containerUser"
[editar | editar código]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]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]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]Define o caminho do Dockerfile a ser usado para construir a imagem do ambiente.
"build"."context"
[editar | editar código]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]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]É 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]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]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]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]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]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]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]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]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]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]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]Testagem de software
[editar | editar código]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]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]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]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]{
"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.