Cell CMS — Utilizando Docker para criar Containers
Tags: csharp, dockercompose, github, devops
Published: 2020-06-24 23:59:35

---
title: Cell CMS — Utilizando Docker para criar Containers
published: true
date: 2020-04-18 14:01:00 UTC
tags: csharp,dockercompose,github,devops
canonical_url: 
series: CellCms
---

### Cell CMS — Utilizando Docker para criar Containers

![](https://cdn-images-1.medium.com/max/1024/0*U0Hph8m2HqsyDWTq)<figcaption>Photo by <a href="https://unsplash.com/@jonassmith?utm_source=medium&amp;utm_medium=referral">Jonas Smith</a> on <a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption>

No [último post](https://dev.to/ardc_overflow/cell-cms-criando-logs-robustos-e-monitorando-uma-api-2ddg-temp-slug-3701851)falamos sobre **geração de logs** , utilizando o **Serilog** e o **ApplicationInsights** para termos um monitoramento robusto, com métricas de performance e erros! No post de hoje vamos permitir que nossa API seja executada em um **Container** através do **Docker**! Além disso vamos dar uma olhada em como o Visual Studio 2019 permite a integração, durante o desenvolvimento, com os containers executados pelo **Docker Desktop.**

Os branches para o código de hoje serão 2: feature/dockerfile e feature/docker-compose.

**O que são Containers e o qual problema eles resolvem**

> Mas na minha máquina funciona! — _Vários desenvolvedores, circa sempre._

Quem nunca, em algum ponto, não soltou a exclamação acima? Quando estamos desenvolvendo costumamos estar no “melhor ambiente” possível. Todas as dependências estão instaladas (e normalmente atualizadas), temos todas as nossas ferramentas de Debug e temos acesso administrativo ao sistema operacional.

Eis que chega a hora de publicar a aplicação e… não dá certo. É erro aqui, erro ali, configuração faltante e assim vai. Isso pra não falarmos de problemas de rede, que ficam mais para o lado do pessoal de operações, normalmente.

Com a chegada de maior poder computacional começamos a utilizar **Máquinas Virtuais** para executar nossos sistemas de maneira mais isolada. Com máquinas virtuais ainda temos todo o trabalho de manter um sistema operacional e configura-lo, mas temos um isolamento melhor das dependências.

**Nas clouds/vps** temos essa opção que normalmente acaba sendo chamada de **IAAS** (_Infrastructure As A Service_), basicamente é assim:

> “Aqui está uma máquina virtual com X processamento, Y ram e Z storage. A gente garante que ela fica no ar 99.99999% do tempo mas você fica responsável por instalar dependências, configurar e aplicar atualizações.”

Porém ainda temos um problema. **Usar uma VM** , completa, **ainda requer** muita **manutenção e configuração** , especialmente se nosso foco for agilidade. Eis que surgem o conceito de **PAAS** (_Platform As A Service_). Basicamente nesse modelo seu fornecedor de cloud/vps se encarrega de atualizar, manter e configurar o **ambiente onde seu sistema será executado.** Basicamente é assim:

> _“Aqui está um ambiente baseado em Linux, com NGINX instalado e com limites de X Y Z. A gente garante que estará sempre atualizado e ficará no ar 99.99999999% do tempo mas você ficará responsável por copiar sua aplicação e as configurações necessárias”_

O pulo do gato aqui é que agora **essa plataforma não necessariamente é dedicada a você**. Não entrarei em detalhes mas neste modelo mas, através de configurações do sistema base e da máquina virtual, o fornecedor pode dedicar cotas de performance diferentes a aplicações diferentes e segregar o que está instalado para cada aplicação. Isso permite um custo mais baixo para o fornecedor e para você cliente!

E então temos, recentemente, a popularização do conceito de **Containers.** De maneira resumida:

> Containers são “pacotes” para distribuição da sua aplicação junto com o ambiente e as dependências necessárias para sua execução. Ao contrário de máquinas virtuais, que simulam o Hardware, os Containers focam em simular o Sistema Operacional.

Em termos práticos: Você cria uma **Imagem** que irá conter todos os passos e dependências para rodar sua aplicação, como:

1. Compilar sua aplicação
2. Com o resultado da compilação, copiar para uma pasta X (por exemplo onde seu WebServer estará servindo)
3. Aplicar as configurações necessárias (seja por variáveis de ambiente dentro da imagem, cópia de arquivos ou linha de comando. Podemos inclusive utilizar _apt get_ para instalar dependências embarcadas)
4. Executar a sua aplicação

Com a **Imagem** pronta você finalmente pode executa-la. Nesse ponto temos o **Container da sua aplicação.**

Agora vem a questão de um milhão de reais:

> Mas Rodolpho, como criamos essas imagens!?

Meus amigos, como quase tudo em TI, temos diversas tecnologias para criarmos Imagens, algumas são:

- **Docker** (o queridinho da vez!)
- Rkt
- Containerd

Então vamos falar, finalmente, sobre Docker!

**Docker, Imagens, Containers e o DockerHub**

![](https://cdn-images-1.medium.com/max/601/0*QxoazngPihW25Z8K.png)<figcaption>Moby, o mascote do Docker (fonte: <a href="https://www.docker.com/sites/default/files/d8/2019-07/Moby-logo.png">https://www.docker.com/sites/default/files/d8/2019-07/Moby-logo.png</a>)</figcaption>

Antes de partirmos para os detalhes técnicos, vamos conhecer um pouco sobre a empresa. [Docker](https://www.docker.com/) é a desenvolvedora de duas soluções que facilitam, bastante, a utilização de containers por desenvolvedores:

- **Docker Desktop** : Ferramenta para facilitar a instalação e uso de containers em ambiente Windows e Mac OS
- **DockerHub** : Repositório central para publicação de imagens (_um NuGet ou NPM para imagens Docker_)

Existem várias outras contribuições da empresa para a comunidade Open Source e na criação da [OCI (Open Container Initiative)](https://www.opencontainers.org/), porém essa parte escapa da ideia do post de focarmos em como utilizar! Recomendo uma boa lida no site da Docker e do OCI, vale a pena!

Anyway, vamos para a parte prática! Não vou abordar neste post como instalar e configurar o Docker Desktop porém se você precisa deste passo a passo pode conferir um [tutorial muito bom no própio site da Docker](https://docs.docker.com/docker-for-windows/install/).

![](https://cdn-images-1.medium.com/max/1024/1*A0Apys9Aee_SLShT0rTd4g.png)<figcaption>Partiremos daqui, Docker Desktop instalado e pronto para rodar!</figcaption>

Vamos abrir um terminal e digitar a sugestão do Docker Desktop.

![](https://cdn-images-1.medium.com/max/1024/1*IyIZLpzqLcMyJf2qWRyF7g.gif)<figcaption>Executando a imagem do Tutorial</figcaption>

Após rodar o comando você poderá acessar [http://localhost](http://localhost) e acompanhar o propio tutorial do pessoal da Docker! Porém, vamos destrinchar aqui o comando que foi executado:

- docker indica que vamos utilizar o docker-cli 😅
- run indica que vamos **executar** uma imagem
- -dp 80:80 indica duas coisas: **-d** sinaliza que o daemon execute em background e **-p 80:80** indica que queremos que a **porta 80 do sistema atual seja mapeada para a porta 80 do container** (ou seja, quando acessarmos a porta 80 da nossa máquina a chamada será encaminhada para a porta 80 do container)
- docker/getting-started sinaliza a **imagem** a ser executada. Podemos identificar que será uma imagem com o nome de **getting-started** e virá do repositório/usuário **docker**. Note que não descrevemos nenhuma tag (através de :tag no fim da imagem) então a tag **latest** será utilizada

Existem outros argumentos importantes e que utilizaremos bastante, eles são:

- --volume ou -v: Permite **mapearmos uma pasta/volume** da nossa máquina **para dentro do container**
- --env ou -e : Permite que definamos valores para **variáveis de ambiente** dentro do container
- --network: Permite que defininamos **uma rede** (virtual) onde **o container será conectado**

A referência completa dos parâmetros pode ser encontrada [aqui](https://docs.docker.com/engine/reference/commandline/run/). Agora vamos discutir o local de onde pegamos a imagem: DockerHub.

O [DockerHub](https://hub.docker.com/) é o **Registry** (ou seja um repositório de imagens) oficial da **Docker**. Existem outros e é possível executar seu próprio registry (inclusive atrás de Containers!), mas o mais famoso é o DockerHub. Nele podemos encontrar imagens para várias aplicações, como:

- [MariaDb](https://hub.docker.com/_/mariadb)
- [MongoDb](https://hub.docker.com/_/mongo)
- [RabbitMQ](https://hub.docker.com/_/rabbitmq)
- [SqlServer](https://hub.docker.com/_/microsoft-mssql-server)
- [E o próprio Getting-Started que baixamos!](https://hub.docker.com/r/docker/getting-started)

Antes de falarmos sobre como as imagens são criadas, vamos abordar mais uma questão:

> “Como desenvolvedor, qual a vantagem de utilizar Containers ao invés de instalar tudo na minha máquina!?”

Do ponto de vista técnico eu diria que a principal vantagem é **poder aproximar ao máximo seu ambiente de desenvolvimento ao ambiente de produção** mantendo as mesmas versões das dependências. Inclusive, **você pode garantir que todos os desenvolvedores estarão utilizando as mesmas versões de dependências** para seu projeto!

Outra vantagem em qualidade de “vida”, especialmente no caso de quem utiliza o Windows, é que **containers podem ser criados e deletados sob demanda e facilmente**. Assim **quando você estiver trabalhando em outro projeto** basta parar ou remover os containers e **os recursos serão liberados**.

> Por exemplo, eu gosto de experimentar com diferentes bancos de dados (inclusive armazeno vários docker-compose [em meu GitHub](https://github.com/rodolphocastro/Docker-Databases)para facilitar o scaffold de projetos) e se eu fosse instalar todos em meu PC (que também uso para jogar! hahaha) seria um inferno de performance. Ter uma VM para cada um? Haja disco! Então, pra mim, a flexibilidade de poder subir e descer estes containers é algo que me ajudou bastante a estudar e utilizar diferentes bancos de dados em vários projetos .NET!

Bem, vamos agora ver **como** estas imagens **são criadas**?

**Criando o Dockerfile para nossa API**

Vamos realizar o processo da maneira fácil, utilizando o Visual Studio 2019 (_também dá para fazer no 2017_), mas pegarei o resultado e comentarei passo-a-passo o que está sendo feito 😄.

Abra a Solution no Visual Studio e no Solution Explorer clique com o direito sobre a API e escolha _Add > Docker Support…_ e, quando aparecer o Dialog, escolha a opção de _Linux Containers_

![](https://cdn-images-1.medium.com/max/482/1*6d1GS9s_bd9thsg-Ncq8cw.png)

![](https://cdn-images-1.medium.com/max/366/1*fCCEl1aUqFyraBaEXmU6Ng.png)

Após finalizar o processo o Visual Studio fará as seguintes operações:

1. Um novo arquivo, Dockerfile, será criado na raiz do projeto
2. Uma nova entrada será adicionada no launchSettings.json do projeto
3. Algumas novas linhas serão adicionadas no .csproj do projeto
4. Um novo arquivo .dockerignore será criado na raiz da solution

Vamos por partes, primeiro olhando o mais importante de todos: o **Dockerfile**.

> O Dockerfile é o passo-a-passo para que o Docker Daemon (processo que realiza a “compilação” das imagens) saiba de onde começar, o que realizar e para onde jogar os resultados. É uma receita de bolo a ser executada para disponibilizar nosso aplicativo.

<iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fcarbon.now.sh%2Fembed%2F98pUaoTENIdH9NK1Kmen%3F&amp;display_name=Carbon&amp;url=https%3A%2F%2Fcarbon.now.sh%2F98pUaoTENIdH9NK1Kmen&amp;image=https%3A%2F%2Fcarbon.now.sh%2Fapi%2Fimage%2F98pUaoTENIdH9NK1Kmen&amp;key=a19fcc184b9711e1b4764040d3dc5c07&amp;type=text%2Fhtml&amp;scroll=auto&amp;schema=carbon" width="1024" height="480" frameborder="0" scrolling="no"><a href="https://medium.com/media/94df0ea7345472f920daea8ea38d2762/href">https://medium.com/media/94df0ea7345472f920daea8ea38d2762/href</a></iframe>

Basicamente o Dockerfile vai conter **todos os passos que você executaria para publicar sua API** através **da linha de comando**. Para quem está acostumado a executar as coisas sempre pelo Visual Studio os comandos podem parecer estranhos, mas rapidamente nos acostumamos com eles.

> _No começo da minha jornada com Docker passei duas semanas utilizando apenas o Visual Studio Code e o Powershell, para ficar mais familiar ao processo “manual” de compilar e distribuir aplicações .NET._

Algo interessante é que o padrão do Visual Studio já vem com uma otimização muito boa chamada de **Multi-Stage Builds.** Isso permite que o Docker utilize um cache entre cada passo do Dockerfile e dessa maneira tenha de recompilar apenas partes alteradas da Imagem.

> Por exemplo: O step 8 (Restore dos NuGets) só será refeito caso ocorra alguma alteração nas dependências do csproj, que é copiado no step 7!

A segunda alteração, no launchSettings.json , nos permite executar e depurar o nosso container através do Visual Studio.

![](https://cdn-images-1.medium.com/max/639/1*cU6KZYLbArJ9d7u5DWTDPw.png)<figcaption>LaunchSettings.json com Docker</figcaption>

A adição das propriedades httpPort e sslPort nos permitem **garantir que o container executado** para Debug **responda sempre nestas mesmas portas**! Pessoalmente não sei por qual motivo estas configurações não são adicionadas automaticamente assim como são para um projeto executado da maneira convencional.

A alteração no .csproj **permite a integração entre o Visual Studio e o Docker Desktop** , vamos dar uma olhada:

![](https://cdn-images-1.medium.com/max/716/1*F9aou_DSx9T_CkrAinXsTQ.png)<figcaption>.csproj com as Tags adicionais. A DockerfileTag grifada foi adicionada manualmente e será explicada quando formos publicar a imagem!</figcaption>

Finalmente o .dockerignore adicionado:

![](https://cdn-images-1.medium.com/max/338/1*3rDyElBbD0fXgkd7oWCqww.png)<figcaption>Algumas linhas foram adicionadas</figcaption>

O .dockerignore é para o Docker o que o .gitignore é para o Git. Um .dockerignore bem escrito **permite que apenas os arquivos estritamente necessários sejam copiados para a nossa imagem**.

Finalmente podemos ver a integração sendo executada através do **Containers View.** Caso ele não esteja visível por padrão em seu Visual Studio o menu para habilita-lo é View > Other Windows > Containers. O **Container View** nos **permite visualizar** rapidamente quais **containers estão sendo executados** , seus **logs** , **portas expostas, arquivos** dentro do container e as **variáveis de ambiente definidas**!

![](https://cdn-images-1.medium.com/max/1024/1*9o27eRosXyZ4YFs2B2utyQ.png)<figcaption>Logs do Container expostos no Containers View</figcaption>

A adição do Docker à API é simples assim utilizando o Visual Studio. Porém leia cuidadosamente o Dockerfile para entender o que está sendo feito.

Procure no GitHub exemplos de outros Dockerfiles de outras aplicações, dê uma boa olhada nos comandos do Docker. Existem vários conceitos (como Volumes e Networks) que precisam bem mais do que um post para serem explicados e são melhor vistos na prática do que na teoria! Eventualmente farei um post mais focado nos detalhes do Docker e, um dia, sobre Kubernetes. Porém o foco desta série é o **Cell CMS** _ponta-a-ponta._

**Criando o Orquestrador (Docker Compose) para a API**

Antes de falarmos sobre a integração com o Visual Studio, vamos ver o que é um “Docker Compose”:

> Um **docker compose é um documento** em formatoyaml que **descreve quais imagens deve ser executadas** , suas **dependências** , suas **configurações** e **como devem se comunicar**.

Apesar da ideia do Cell CMS ser uma aplicação “lobo solitário”, vamos adicionar suporte á Orquestração de Containers ao nosso Projeto! Será mais para demonstrar a utilidade do **Docker Compose,** e sua integração com o Visual Studio, **durante o desenvolvimento**. De maneira resumida a **vantagem** de **adicionar um docker compose à sua Solution é garantir que desenvolvedores** com Docker Desktop **facilmente tenham um ambiente pronto** para desenvolver (e debugar) assim que abrirem seu projeto.

Ou seja: **junto com o seu código você armazenará um documento que descreve as dependências necessárias do ambiente de sua aplicação**. Apesar de não serem muito recomendados para produção os composes facilitam, e muito, os ambientes de desenvolvimento e testes.

Vamos lá! Com o Solution Explorer aberto clique em sua Api com botão direito e escolha _Add > Container Orchestrator Support…_ e escolha “Docker Compose” no primeiro dialog.

![](https://cdn-images-1.medium.com/max/478/1*oRpYTGE5UYluaYXkrp2fBg.png)

![](https://cdn-images-1.medium.com/max/406/1*yaMfUZT7iigOtFaCz_97cw.png)<figcaption>Para este exemplo escolha Docker Compose, em outro post falaremos sobre Kubernetes!</figcaption>

![](https://cdn-images-1.medium.com/max/409/1*56tyc492ib17Gjgo70z-MA.png)<figcaption>Caso você tenha editado o Dockerfile o seguinte Dialog será apresentado, caso queira substituir o arquivo clique em Yes.</figcaption>

O Visual Studio irá gerar as seguintes alterações no projeto (e solution):

1. Adicionar um novo projeto .dcproj à solution
2. Gerar um arquivo .docker-compose.yml, este será nosso compose para o ambiente normal
3. Gerar um arquivo .docker-compose.override.yml, este será nosso completamento (para ambientes de debug) ao compose principal
4. Adicionar, ao projeto da API, uma referência ao .dcproj

Vamos focar nos arquivos .yml, a sintaxe é bem simples e fácil de decorar e achar referências, o que é bom pois o suporte do Visual Studio aos arquivos deixa a desejar 😅

Vamos dar uma olhada nos arquivos gerados:

![](https://cdn-images-1.medium.com/max/619/1*V5NX6xTHS6E72zZv5dCOiA.png)<figcaption>docker-compose.yml</figcaption>

![](https://cdn-images-1.medium.com/max/897/1*hoayb5avW5pqdRueNd7ryA.png)<figcaption>docker-compose.override.yml</figcaption>

Note a semelhança dos parâmetros das chaves do nosso docker-compose com os parâmetros do docker run. Isso não é mera coincidência! No fundo podemos encarar que o **docker-compose é uma maneira de organizar e unificar diversos docker run.**

Finalmente note, através do Containers View ou do Dashboard do Docker Desktop, que o Visual Studio automaticamente botou todos serviços que estão no docker-compose.yml para rodar:

![](https://cdn-images-1.medium.com/max/1024/1*UEaKychlWk1xWnpAVOrSdg.png)

Para arrumar as portas, e mantendo a integração que nos permite Debug através do Visual Studio, seguimos a mesma ideia da alteração do launchSettings.json. Como queremos isso apenas para ambientes de desenvolvimento vamos abrir o arquivo docker-compose.override.yml e adicionar um mapeamento explícito para portas do nosso container:

![](https://cdn-images-1.medium.com/max/266/1*YHcqhUAckTnaGcAdqV5gPA.png)<figcaption>Achando o override no Solution Explorer</figcaption>

![](https://cdn-images-1.medium.com/max/617/1*VNoRX8vh1W-JlLlTjz-4Pg.png)<figcaption>Alteração para necessária</figcaption>

Relembrando a sintaxe lá do docker run: **À esquerda temos a porta do host** (sua máquina) e **à direita temos a porta do container** (nossa api). No caso a configuração está **dizendo que sempre que acessarmos** [**http://localhost**](http://localhost) **:5000 e** [**https://localhost**](https://localhost) **:5001 na máquina local a chamada será redirecionada para a posta 80/443** dentro **do container**!

Agora vamos confirmar que o Debug funciona? Para demonstrar vou adicionar um Breakpoint em um dos meus métodos do Startup.cs e mandar o Visual Studio iniciar o Debug:

![](https://cdn-images-1.medium.com/max/1024/1*U7_5QSdimPqhQg3syTEhEw.gif)<figcaption>Breakpoint (no host) sendo ativado pelo container.</figcaption>

Note que meu IConfiguration está devidamente populado com as configurações do appsettings.json, appsettings.Development.json, _variáveis de ambiente_ e meu secrets.json. O mapeamento dos appsettingsocorrem durante a criação da nossa imagem, porém o mapeamento do secrets.json é um “bonus” do Scaffold que o Visual Studio fez do docker-compose.override.yml. Em específico a linha de volume que mapeia o diretório ${APPDATA}/Microsoft/UserSecrets da minha máquina (host) para o diretório /root/.microsoft/usersecrets do container, com o modo ro (_readonly)._

Para demonstrar como podemos **alterar** as **configurações** presentes no IConfiguration **através de variáveis de ambiente** nos containers vou renomear nossa database sqlite para “outraStorage.db” através da ConnectionString no docker-compose.override.yml:

![](https://cdn-images-1.medium.com/max/566/1*wulCu1rqBctw3I7KtOtIGA.png)<figcaption>Alterando, por ambiente, a ConnectionString da Database</figcaption>

Colocando um breakpoint e analisando o IConfiguration podemos ver os diversos providers e ver que o valor definido pelo docker-compose está lá:

![](https://cdn-images-1.medium.com/max/829/1*exeGTqsfmzbL_eRL1V-wYA.png)<figcaption>Identificando o Provider de Variáveis de Ambiente</figcaption>

![](https://cdn-images-1.medium.com/max/793/1*lv0P3ncss85Lw0x2FLqTag.png)<figcaption>Encontrando nossas alterações dentro do Provider!</figcaption>

Este é o básicão sobre o Docker Compose. Como sempre recomendo a leitura da documentação para ver melhor os detalhes e até dicas mais avançadas! A documentação está disponível [aqui](https://docs.docker.com/compose/compose-file/).

**Considerações Finais**

Hoje vimos, _bem superficialmente_, o que são Containers, quais suas utilidades e como podemos utilizar o Docker Desktop junto do Visual Studio para facilitar nossa vida durante o desenvolvimento de aplicações. Este foi, acredito, o maior post da série até agora e ainda assim ficou extremamente superficial. Espero que o post tenha sido o suficiente para atiçar a curiosidade daqueles que ainda estavam indecisos sobre as vantagens de containers!

Prometo fazer um post algum dia indo nos pormenores do Docker e, inclusive, trazendo exemplos mais complexos de Orquestração (como, por exemplo, subir uma api, um worker, Um frontend e um banco ao mesmo tempo) mas no momento estou dedicado em avançar com o Cell CMS!

Para o próximo post vamos dar uma olhada em **Testes Unitários** de maneira **produtiva** e prática! Utilizando as bibliotecas **xUnit, AutoFixture, Moq e FluentAssertions.**

Obrigado por lerem mais este post, até a próxima e abraços!