Categorias:

Docker – Criando um container para API .Net (6.0)

Direto ao ponto

Nosso objetivo é criar uma API com .Net 6.0, criar uma imagem desta aplicação, subir esta imagem no Docker Hub e criar um container a partir desta imagem.

Pré requisitos

Antes vamos ver se este tutorial irá realmente te atender, mostrando as ferramentas que utilizarei

  • Visual Studio Code
  • Dotnet SDK
fernando@fernando-System-Product-Name:~$ dotnet --version
6.0.202
  • Docker
fernando@fernando-System-Product-Name:~$ docker --version
Docker version 20.10.15, build fd82621

Criar uma nova api

Nossa API tem um controlador chamado HelloWorld, uma ação GET e o propósito simples de retornar a mensagem HelloWorld em sua saída.

Não é o objetivo deste artigo mostrar a construção desta API. Entretanto, o código que utilizarei estará disponível no GitHub.

Testando nossa API

Para testar a api, usamos o terminal do pŕoprio Visual Studio Code e digitamos o comando dotnet run.

fernando@fernando-System-Product-Name:~/repo/HelloWorld$ dotnet run

Certifique-se se estar no diretório raiz da aplicação antes de executar o comando

Ainda no terminal do Visual Studio Code, serão apresentados os endereços de execução da aplicação. Pressione a tecla Ctrl do seu teclado e clique sobre um dos endereços para visualizar o resultado da API no seu navegador.

terminal do Visual Studio Code
Resultado na tela do navegador (FireFox)

Editando o arquivo Dockerfile

Na raiz da aplicação crie um novo arquivo como nome Dockerfile (este arquivo não tem extensão) e copie o conteúdo abaixo:

# https://hub.docker.com/_/microsoft-dotnet
FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build
WORKDIR /app

# copy csproj and restore as distinct layers
COPY *.csproj ./
RUN dotnet restore

# copy everything else and build app
COPY . ./
RUN dotnet publish -c release -o out

# final stage/image
FROM mcr.microsoft.com/dotnet/aspnet:6.0
WORKDIR /app
EXPOSE 80
COPY --from=build /app/out .
ENTRYPOINT ["dotnet", "HelloWorld.dll"]

Estes arquivo está dividido em quatro partes (camadas), cada qual iniciada por um comentário (#)

  • # https://hub.docker.com/_/microsoft-dotnet

Cria uma nova imagem base onde faremos o build da nossa aplciação.

Na instrução FROM utilizamos uma imagem que já tem as ferramentas do sdk do dotnet. A imagem mcr.microsoft.com/dotnet/sdk:6.0 foi obtida a partir da documentação da Microsoft que coloco ao final deste artigo.

  • # copy csproj and restore as distinct layers

Copia o arquivo de projeto da api para a pasta raiz do container intermediário e executa o comando dotnet restore (que utiliza Nuget) para restaurar as dependências e ferramentas especificadas no arquivo do projeto.

  • # copy everything else and build app

Copia os demais arquivos para o container intermediário e executa o comando dotnet publish para gerar uma versão de release da aplicação na nova pasta out do container intermediário. (a pasta out não é gerada na sua máquina local, é gerada diretamente no container intermediário).

  • # final stage/image

As definições deste bloco constroem a imagem final.

Criamos um nova imagem, desta vez utilizando apenas o RUNTIME do dotnet, definimos o diretório de trabalho /app, definimos a porta de “escuta” do contâiner (porta 80) e copiamos toda o código publicado no diretórtio out do container intermediário para o container atual (representado por “.” ao final do comando COPY).

Finalmente, na última linha do arquivo, na instrução ENTRYPPOINT, executamos o comando dotnet run usando o argumento HelloWorld.dll (que é o resultado da compilação do projeto HelloWorld.csproj).

Criar uma imagem

A partir do diretório raiz da aplicação, digite o comando docker build, utilizando o argumento -t para nomear a imagem.

docker build -t fecassa/helloworld .

A execução do comando irá exibir o passo a passo da execução do arquivo Dockerfile, conforme o exemplo abaixo:

Sending build context to Docker daemon  4.196MB
Step 1/12 : FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build
 ---> 907525206621
Step 2/12 : WORKDIR /app
 ---> Running in 03bd9464ce76
Removing intermediate container 03bd9464ce76
 ---> 7d97f9539848
Step 3/12 : LABEL stage="intermediate"
 ---> Running in 52ead8f1fabb
Removing intermediate container 52ead8f1fabb
 ---> 98a2cd492048
Step 4/12 : COPY *.csproj ./
 ---> e321d5fa6cad
Step 5/12 : RUN dotnet restore
 ---> Running in 626e967b812d
  Determining projects to restore...
  Restored /app/HelloWorld.csproj (in 3.13 sec).
Removing intermediate container 626e967b812d
 ---> f49a86f2d585
Step 6/12 : COPY . ./
 ---> c84ce4d1d6cf
Step 7/12 : RUN dotnet publish -c release -o out
 ---> Running in a2c9d11fe8f9
Microsoft (R) Build Engine version 17.2.0+41abc5629 for .NET
Copyright (C) Microsoft Corporation. All rights reserved.

  Determining projects to restore...
  All projects are up-to-date for restore.
/app/HelloMessage.cs(3,19): warning CS8618: Non-nullable property 'Message' must contain a non-null value when exiting constructor. Consider declaring the property as nullable. [/app/HelloWorld.csproj]
  HelloWorld -> /app/bin/release/net6.0/HelloWorld.dll
  HelloWorld -> /app/out/
Removing intermediate container a2c9d11fe8f9
 ---> 3c4a82ae095d
Step 8/12 : FROM mcr.microsoft.com/dotnet/aspnet:6.0
 ---> dcbb7a2af474
Step 9/12 : WORKDIR /app
 ---> Running in 367829573b63
Removing intermediate container 367829573b63
 ---> 3c2277e6a030
Step 10/12 : EXPOSE 80
 ---> Running in fba3f70e45c0
Removing intermediate container fba3f70e45c0
 ---> 7aa63537f078
Step 11/12 : COPY --from=build /app/out .
 ---> 4dc779980506
Step 12/12 : ENTRYPOINT ["dotnet", "HelloWorld.dll"]
 ---> Running in e943c3367f8c
Removing intermediate container e943c3367f8c
 ---> c4dbc9f102d7
Successfully built c4dbc9f102d7
Successfully tagged fecassa/helloworld:latest

A partir deste momento a imagem foi criada e já aparece na listagem de imagens disponíveis em sua máquina.

fernando@fernando-System-Product-Name:~/repo/HelloWorld$ docker image ls fecassa/helloworld

Já temos uma imagem a partir da qual podemos criar um container.

Mas executar um container a partir de uma imagem local não parece trazer muitas vantagens. Então, vamos “subir” a nossa imagem para um repositório a partir de onde outras pessoas também poderão executar a nossa API.

Para isto utilizaremos o Dockerhub.

Criar repositório no Dockerhub

Na página inicial do Dockerhub, crie um usuário e senha (ou efetue o login)

Após o login, clique no menu Repositories e no botão Create Repository.

Forneça um nome para sua imagem, uma descrição, mantenha a visibilidade como pública e clique no botão Create.

Após clicar no botão Create, nosso repositório estará criado.

Fazendo upload da nossa imagem para o repositório

De volta ao terminal da nossa máquina, iremos executar o comando docker login para nos autenticar no Dockerhub

docker login -u=”seu-usuario” -p=”sua-senha”

O terminal irá exibir algumas mensagens informando que esta forma de autenticação é insegura, e que as credenciais informadas ficarão armazenadas sem criptografia em um diretório da nossa máquina local.

Contanto que você informe as suas credenciais corretamente, isso não impedirá o login.

Veremos como ajustar essa brecha de segurança em outra oportunidade

Execute o comando docker push, passando o nome e versão da imagem que deseja fazer upload

docker push fecassa/helloworld:latest

Saída esperada no terminal:

The push refers to repository [docker.io/fecassa/helloworld]
4dcd72115b74: Pushed 
86aee86a3571: Pushed 
8b099aa428a0: Pushed 
e71686a49974: Pushed 
aaf9b5a99495: Pushed 
5c08ad95b842: Pushed 
9c1b6dd6c1e6: Pushed 
latest: digest: sha256:1fbcebae4cfcbe50b00f1fe17234c13a1527b93f60a057f62cede9476aa3292a size: 1788

Repositório remoto atualizado

O nome da imagem deve coincidir com nome do reposítório remoto 😐

Se estiverem diferentes, basta executar o comando docker tag passando o nome e versão atual da imagem local e o novo nome (podendo manter o número de versão) 😛

docker tag fecassa/helloworld:latest [novo nome]:latest

Executar um container a partir de uma imagem em um repositório remoto

A partir deste momento, nós podemos criar um container a partir da imagem hospedada no Dockerhub usando qualquer outra máquina sem a necessidade de instalação do runtime ou sdk do dotnet.

A única coisa que precisamos nos certificar é que estas novas máquinas tenham o docker instalado.

Se você pretente continuar usando a mesma máquina para seguir o passo a passo deste roteiro, certifique-se de apagar a imagem local que criamos nos passos anteriores usando o comando docker image rm (do contrário não conseguiremos utilizar a imagem do repositório remoto).

docker image rm fecassa/helloworld

Para criar um container a partir da imagem no repositório remoto, insira o comando docker run.

docker run -d -p 8080:80 --name myapp fecassa/helloworld

Usamos os seguintes argumentos no comando:

  • -d para executar o container em modo detached
  • -p para mapear a porta 80 do container (a porta que usamos na instrução EXPOSE do Dockerfile) na porta 8080 da máquina local
  • –name para nomear o container que será criado com o nome myapp
  • e por último o nome da imagem que usaremos para criar o container, “fecassa/helloworld

A primeira mensagem da execução do comando deve ser a mensagem: “Unable to find image ‘fecassa/helloworld:latest’ locally”. O cursor vai ficar um tempo parado nesta mensagem e quer dizer que a imagem não existe na máquina local. Depois de alguns segundos será feito o dowload da imagem do DockerHub (que acabamos de fazer o upload) e o comando seguirá para a construção do container.

Após a finalização do comando, execute a instrução docker ps para exibir o container criado.

Abra o navegador e informe o endereço http://localhost:8080/HelloWorld.

Parabéns, seu container está em execução 🙂 .

Materias de referência

https://docs.microsoft.com/pt-br/aspnet/core/host-and-deploy/docker/building-net-docker-images?view=aspnetcore-6.0