Saltar para o conteúdo

Dev Containers

Fonte: Wikiversidade

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]

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.

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"
}

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"
}

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".

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 desenvolvimento
  • Rebuild 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.

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!".

{
   "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.