Tutorial: Colocar um aplicativo .NET em contêiner
Neste tutorial, você aprenderá como colocar em contêiner um aplicativo .NET com o Docker. Os contêineres têm muitos recursos e benefícios, como o de serem uma infraestrutura imutável, fornecer uma arquitetura portátil e permitir a escalabilidade. A imagem pode ser usada para criar contêineres para seu ambiente de desenvolvimento local, nuvem privada ou nuvem pública.
Neste tutorial, você:
- Criar e publicar um aplicativo .NET simples
- Criar e configurar um Dockerfile para o .NET
- Compilar uma imagem do docker
- Criar e executar um contêiner do Docker
Você entenderá as tarefas de build e implantação de contêiner do Docker para um aplicativo .NET. A plataforma Docker usa o Mecanismo do Docker para criar e empacotar aplicativos como imagens do Docker com agilidade. Essas imagens são gravadas no formato Dockerfile para serem implantadas e executadas em um contêiner em camadas.
Observação
Este tutorial não é para aplicativos ASP.NET Core. Se você estiver usando ASP.NET Core, consulte o tutorial Saiba como colocar em contêiner um tutorial do aplicativo ASP.NET Core.
Pré-requisitos
Instale os seguintes pré-requisitos:
- SDK do .NET 8+
Se você tiver o .NET instalado, use o comandodotnet --info
para determinar qual SDK está usando. - Docker Community Edition
- Uma pasta de trabalho temporária para o Dockerfile e o aplicativo de exemplo do .NET. Neste tutorial, o nome docker-working é usado como a pasta de trabalho.
- SDK do .NET 7+
Se você tiver o .NET instalado, use o comandodotnet --info
para determinar qual SDK está usando. - Docker Community Edition
- Uma pasta de trabalho temporária para o Dockerfile e o aplicativo de exemplo do .NET. Neste tutorial, o nome docker-working é usado como a pasta de trabalho.
Criar aplicativo .NET
Você precisa de um aplicativo .NET que o contêiner do Docker execute. Abra seu terminal, crie uma pasta de trabalho se você ainda não fez isso e entre nela. Na pasta de trabalho, execute o seguinte comando para criar um projeto em um subdiretório denominado App:
dotnet new console -o App -n DotNet.Docker
Sua árvore de pastas tem a seguinte aparência:
📁 docker-working
└──📂 App
├──DotNet.Docker.csproj
├──Program.cs
└──📂 obj
├── DotNet.Docker.csproj.nuget.dgspec.json
├── DotNet.Docker.csproj.nuget.g.props
├── DotNet.Docker.csproj.nuget.g.targets
├── project.assets.json
└── project.nuget.cache
O comando dotnet new
cria uma pasta chamada App e gera um aplicativo de console "Hello, World". Altere os diretórios e navegue até a pasta App a partir da sessão do terminal. Use o comando dotnet run
para iniciar o aplicativo. O aplicativo é executado e imprime Hello World!
abaixo do comando:
cd App
dotnet run
Hello World!
O modelo padrão cria um aplicativo que imprime no terminal e, em seguida, é encerrado imediatamente. Neste tutorial, você usará um aplicativo que faz um loop indefinidamente. Abra o arquivo Program.cs em um editor de texto.
Dica
Se você estiver usando Visual Studio Code, a partir da sessão do terminal anterior, digite o seguinte comando:
code .
Isso abrirá a pasta App que contém o projeto no Visual Studio Code.
O Program.cs deve ser como o seguinte código C#:
Console.WriteLine("Hello World!");
Substitua o arquivo pelo seguinte código que conta os números a cada segundo:
var counter = 0;
var max = args.Length is not 0 ? Convert.ToInt32(args[0]) : -1;
while (max is -1 || counter < max)
{
Console.WriteLine($"Counter: {++counter}");
await Task.Delay(TimeSpan.FromMilliseconds(1_000));
}
var counter = 0;
var max = args.Length is not 0 ? Convert.ToInt32(args[0]) : -1;
while (max is -1 || counter < max)
{
Console.WriteLine($"Counter: {++counter}");
await Task.Delay(TimeSpan.FromMilliseconds(1_000));
}
Salve o arquivo e teste o programa novamente com dotnet run
. Lembre-se de que esse aplicativo é executado indefinidamente. Use o comando de cancelamento Ctrl+C para interrompê-lo. Confira o exemplo de saída abaixo:
dotnet run
Counter: 1
Counter: 2
Counter: 3
Counter: 4
^C
Se você passar um número na linha de comando para o aplicativo, ele apenas contará até tal valor e, em seguida, sairá. Experimente com dotnet run -- 5
para contar até cinco.
Importante
Quaisquer parâmetros após --
não são passados para o comando dotnet run
e, em vez disso, são passados para o aplicativo.
Publicar aplicativo .NET
Antes de adicionar o aplicativo .NET à imagem do Docker, primeiro ele deve ser publicado. É melhor fazer com que o contêiner execute a versão publicada do aplicativo. Para publicar o aplicativo, execute o seguinte comando:
dotnet publish -c Release
Esse comando compila seu aplicativo para a pasta publish. O caminho para a pasta publish da pasta de trabalho deve ser .\App\bin\Release\net8.0\publish\
.
Esse comando compila seu aplicativo para a pasta publish. O caminho para a pasta publish da pasta de trabalho deve ser .\App\bin\Release\net7.0\publish\
.
Na pasta App, obtenha uma listagem de diretório da pasta de publicação para verificar se o arquivo DotNet.Docker.dll foi criado.
dir .\bin\Release\net8.0\publish\
Directory: C:\Users\default\App\bin\Release\net8.0\publish
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a--- 9/22/2023 9:17 AM 431 DotNet.Docker.deps.json
-a--- 9/22/2023 9:17 AM 6144 DotNet.Docker.dll
-a--- 9/22/2023 9:17 AM 157696 DotNet.Docker.exe
-a--- 9/22/2023 9:17 AM 11688 DotNet.Docker.pdb
-a--- 9/22/2023 9:17 AM 353 DotNet.Docker.runtimeconfig.json
dir .\bin\Release\net7.0\publish\
Directory: C:\Users\default\App\bin\Release\net7.0\publish
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a--- 2/13/2023 1:52 PM 431 DotNet.Docker.deps.json
-a--- 2/13/2023 1:52 PM 6144 DotNet.Docker.dll
-a--- 2/13/2023 1:52 PM 153600 DotNet.Docker.exe
-a--- 2/13/2023 1:52 PM 11052 DotNet.Docker.pdb
-a--- 2/13/2023 1:52 PM 253 DotNet.Docker.runtimeconfig.json
Criar o Dockerfile
O arquivo Dockerfile é usado pelo comando docker build
para criar uma imagem de contêiner. Esse arquivo é um arquivo de texto chamado Dockerfile que não possui uma extensão.
Crie um arquivo chamado Dockerfile no diretório que contém .csproj e abra-o em um editor de texto. Este tutorial usa a imagem do runtime do ASP.NET Core (que contém a imagem do runtime do .NET) e corresponde ao aplicativo de console do .NET.
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build-env
WORKDIR /App
# Copy everything
COPY . ./
# Restore as distinct layers
RUN dotnet restore
# Build and publish a release
RUN dotnet publish -c Release -o out
# Build runtime image
FROM mcr.microsoft.com/dotnet/aspnet:8.0
WORKDIR /App
COPY --from=build-env /App/out .
ENTRYPOINT ["dotnet", "DotNet.Docker.dll"]
Observação
A imagem ASP.NET Core runtime é usada intencionalmente aqui, embora a imagem mcr.microsoft.com/dotnet/runtime:8.0
poderia ter sido usada.
Dica
Esse Dockerfile usa builds de várias fases, o que otimiza o tamanho final da imagem, dispondo o build em camadas e deixando apenas os artefatos necessários. Para obter mais informações, consulte Documentação do Docker: builds de várias fases.
A palavra-chave FROM
requer um nome de imagem de contêiner do Docker totalmente qualificado. O Registro de Contêiner da Microsoft (MCR, mcr.microsoft.com) é um sindicato do Docker Hub, que hospeda contêineres acessíveis ao público. O segmento dotnet
é o repositório de contêineres, enquanto o segmento sdk
ou aspnet
é o nome da imagem do contêiner. A imagem é marcada com 8.0
, que é usado para controle de versão. Portanto, mcr.microsoft.com/dotnet/aspnet:8.0
é o runtime do .NET 8.0. Verifique a execução da versão do runtime que corresponda ao runtime direcionado pelo seu SDK. Por exemplo, o aplicativo criado na seção anterior usou o SDK do .NET 8.0 e a imagem base referenciada no Dockerfile é marcada com 8.0.
Importante
Ao usar imagens de contêiner baseadas no Windows, você precisa especificar a marca de imagem além de simplesmente 8.0
, por exemplo, mcr.microsoft.com/dotnet/aspnet:8.0-nanoserver-1809
em vez de mcr.microsoft.com/dotnet/aspnet:8.0
. Selecione um nome de imagem com base no fato de você estar usando o Nano Server ou o Windows Server Core e a versão desse sistema operacional. Você pode encontrar uma lista completa de todas as marcas com suporte na Página Docker Hub do .NET.
Salve o arquivo Dockerfile. A estrutura de diretório da pasta de trabalho deve ser semelhante à mostrada a seguir. Algumas das pastas e arquivos de nível mais profundo foram omitidas para reduzir o tamanho do artigo:
📁 docker-working
└──📂 App
├── Dockerfile
├── DotNet.Docker.csproj
├── Program.cs
├──📂 bin
│ └──📂 Release
│ └──📂 net8.0
│ └──📂 publish
│ ├── DotNet.Docker.deps.json
│ ├── DotNet.Docker.exe
│ ├── DotNet.Docker.dll
│ ├── DotNet.Docker.pdb
│ └── DotNet.Docker.runtimeconfig.json
└──📁 obj
└──...
FROM mcr.microsoft.com/dotnet/sdk:7.0 AS build-env
WORKDIR /App
# Copy everything
COPY . ./
# Restore as distinct layers
RUN dotnet restore
# Build and publish a release
RUN dotnet publish -c Release -o out
# Build runtime image
FROM mcr.microsoft.com/dotnet/aspnet:7.0
WORKDIR /App
COPY --from=build-env /App/out .
ENTRYPOINT ["dotnet", "DotNet.Docker.dll"]
Observação
A imagem ASP.NET Core runtime é usada intencionalmente aqui, embora a imagem mcr.microsoft.com/dotnet/runtime:7.0
poderia ter sido usada.
Dica
Esse Dockerfile usa builds de várias fases, o que otimiza o tamanho final da imagem, dispondo o build em camadas e deixando apenas os artefatos necessários. Para obter mais informações, consulte Documentação do Docker: builds de várias fases.
A palavra-chave FROM
requer um nome de imagem de contêiner do Docker totalmente qualificado. O Registro de Contêiner da Microsoft (MCR, mcr.microsoft.com) é um sindicato do Docker Hub, que hospeda contêineres acessíveis ao público. O segmento dotnet
é o repositório de contêineres, enquanto o segmento sdk
ou aspnet
é o nome da imagem do contêiner. A imagem é marcada com 7.0
, que é usado para controle de versão. Portanto, mcr.microsoft.com/dotnet/aspnet:7.0
é o runtime do .NET 7.0. Verifique a execução da versão do runtime que corresponda ao runtime direcionado pelo seu SDK. Por exemplo, o aplicativo criado na seção anterior usou o SDK do .NET 7.0 e a imagem base referenciada no Dockerfile é marcada com 7.0.
Salve o arquivo Dockerfile. A estrutura de diretório da pasta de trabalho deve ser semelhante à mostrada a seguir. Algumas das pastas e arquivos de nível mais profundo foram omitidas para reduzir o tamanho do artigo:
📁 docker-working
└──📂 App
├── Dockerfile
├── DotNet.Docker.csproj
├── Program.cs
├──📂 bin
│ └──📂 Release
│ └──📂 net7.0
│ └──📂 publish
│ ├── DotNet.Docker.deps.json
│ ├── DotNet.Docker.exe
│ ├── DotNet.Docker.dll
│ ├── DotNet.Docker.pdb
│ └── DotNet.Docker.runtimeconfig.json
└──📁 obj
└──...
Do terminal, execute o seguinte comando:
docker build -t counter-image -f Dockerfile .
O Docker processará cada linha no Dockerfile. O .
no comando docker build
define o contexto de build da imagem. A opção -f
é o caminho para o Dockerfile. Esse comando constrói a imagem e cria um repositório local chamado counter-image que aponta para essa imagem. Após a conclusão desse comando, execute docker images
para ver uma lista de imagens instaladas:
docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
counter-image latest 2f15637dc1f6 10 minutes ago 217MB
O repositório counter-image
é o nome da imagem. A marca latest
é a marca usada para identificar a imagem. O 2f15637dc1f6
é a ID da imagem. O 10 minutes ago
especifica a hora em que a imagem foi criada. O 217MB
é tamanho da imagem. As etapas finais do Dockerfile são criar um contêiner da imagem e executar o aplicativo, copiar o aplicativo publicado para o contêiner e definir o ponto de entrada.
FROM mcr.microsoft.com/dotnet/aspnet:8.0
WORKDIR /App
COPY --from=build-env /App/out .
ENTRYPOINT ["dotnet", "DotNet.Docker.dll"]
docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
counter-image latest 2f15637dc1f6 10 minutes ago 208MB
O repositório counter-image
é o nome da imagem. A marca latest
é a marca usada para identificar a imagem. O 2f15637dc1f6
é a ID da imagem. O 10 minutes ago
especifica a hora em que a imagem foi criada. O 208MB
é tamanho da imagem. As etapas finais do Dockerfile são criar um contêiner da imagem e executar o aplicativo, copiar o aplicativo publicado para o contêiner e definir o ponto de entrada.
FROM mcr.microsoft.com/dotnet/aspnet:7.0
WORKDIR /App
COPY --from=build-env /App/out .
ENTRYPOINT ["dotnet", "DotNet.Docker.dll"]
O comando COPY
informa ao Docker para copiar a pasta especificada em seu computador para uma pasta no contêiner. Nesse exemplo, a pasta publish é copiada para uma pasta chamada App/out no contêiner.
O comando WORKDIR
altera o diretório atual dentro do contêiner para App.
O comando seguinte, ENTRYPOINT
, informa ao Docker para configurar o contêiner para ser executado como um executável. Quando o contêiner é iniciado, o comando ENTRYPOINT
é executado. Quando esse comando terminar, o contêiner será interrompido automaticamente.
Dica
Antes do .NET 8, os contêineres configurados para serem executados como somente leitura podiam falhar com Failed to create CoreCLR, HRESULT: 0x8007000E
. Para resolver esse problema, especifique uma variável de ambiente DOTNET_EnableDiagnostics
como 0
(pouco antes da etapa ENTRYPOINT
):
ENV DOTNET_EnableDiagnostics=0
Para obter mais informações sobre diversas variáveis de ambiente, confira Variáveis de ambiente do .NET.
Observação
O .NET 6 usa o prefixo DOTNET_
como padrão em vez de COMPlus_
para variáveis de ambiente que configuram o comportamento de tempo de execução do .NET. No entanto, o prefixo COMPlus_
continuará funcionando. Se você estiver usando uma versão anterior do runtime do .NET, continue usando o prefixo COMPlus_
para variáveis de ambiente.
Criar um contêiner
Agora que você tem uma imagem que contém o seu aplicativo, você pode criar um contêiner. Você pode criar um contêiner de duas maneiras. Primeiro, criar um novo contêiner que foi interrompido.
docker create --name core-counter counter-image
Esse comando docker create
cria um contêiner com base na imagem counter-image. A saída desse comando mostra a ID DO CONTÊINER (a sua será diferente) do contêiner criado:
d0be06126f7db6dd1cee369d911262a353c9b7fb4829a0c11b4b2eb7b2d429cf
Para ver uma lista de todos os contêineres, use o comando docker ps -a
:
docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
d0be06126f7d counter-image "dotnet DotNet.Docke…" 12 seconds ago Created core-counter
Gerenciar o contêiner
O contêiner foi criado com um nome core-counter
específico, que é usado para gerenciar o contêiner. O exemplo a seguir usa o comando docker start
para iniciar o contêiner e, em seguida, usa o comando docker ps
para mostrar apenas os contêineres em execução:
docker start core-counter
core-counter
docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
cf01364df453 counter-image "dotnet DotNet.Docke…" 53 seconds ago Up 10 seconds core-counter
Da mesma forma, o comando docker stop
interrompe o contêiner. O exemplo a seguir usa o comando docker stop
para interromper o contêiner e, em seguida, usa o comando docker ps
para mostrar que nenhum contêiner está em execução:
docker stop core-counter
core-counter
docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
Conectar-se a um contêiner
Depois que um contêiner estiver em execução, você poderá se conectar a ele para ver a saída. Use os comandos docker start
e docker attach
para iniciar o contêiner e inspecionar o fluxo de saída. Neste exemplo, pressionar Ctrl+C desanexa do contêiner em execução. Ao pressionar a tecla, o processo no contêiner é encerrado, a menos que especificado de outra forma, o que interromperia o contêiner. O parâmetro --sig-proxy=false
garante que Ctrl+C não interrompa o processo no contêiner.
Depois de desanexar do contêiner, reanexe para verificar se ele ainda está em execução e contando.
docker start core-counter
core-counter
docker attach --sig-proxy=false core-counter
Counter: 7
Counter: 8
Counter: 9
^C
docker attach --sig-proxy=false core-counter
Counter: 17
Counter: 18
Counter: 19
^C
Excluir um contêiner
Para este artigo, você não quer contêineres sem utilidade. Exclua o contêiner que você criou anteriormente. Se o contêiner estiver em execução, interrompa-o.
docker stop core-counter
O exemplo a seguir lista todos os contêineres. Em seguida, ele usa o comando docker rm
para excluir o contêiner e, em depois, verifica uma segunda vez para verificar qualquer contêiner em execução.
docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
2f6424a7ddce counter-image "dotnet DotNet.Dock…" 7 minutes ago Exited (143) 20 seconds ago core-counter
docker rm core-counter
core-counter
docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
Execução única
O Docker fornece o comando docker run
para criar e executar o contêiner como um único comando. Este comando elimina a necessidade de executar docker create
e, em seguida, docker start
. Você também pode definir esse comando para excluir automaticamente o contêiner quando o contêiner for interrompido. Por exemplo, use docker run -it --rm
para fazer duas coisas: primeiro, use automaticamente o terminal atual para se conectar ao contêiner e, quando o contêiner terminar, remova-o:
docker run -it --rm counter-image
Counter: 1
Counter: 2
Counter: 3
Counter: 4
Counter: 5
^C
O contêiner também passa parâmetros para a execução do aplicativo .NET. Para instruir o aplicativo .NET a contar apenas até três, passe em 3.
docker run -it --rm counter-image 3
Counter: 1
Counter: 2
Counter: 3
Com docker run -it
, o comando Ctrl+C interrompe o processo em execução no contêiner, que, por sua vez, interrompe o contêiner. Como o parâmetro --rm
foi fornecido, o contêiner é automaticamente excluído quando o processo é interrompido. Verifique se ele não existe:
docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
Alterar o ENTRYPOINT
O comando docker run
também permite modificar o comando ENTRYPOINT
do Dockerfile e executar outra coisa, mas apenas para esse contêiner. Por exemplo, use o seguinte comando para executar bash
ou cmd.exe
. Edite o comando conforme necessário.
Neste exemplo, ENTRYPOINT
é alterado para cmd.exe
. Ctrl+C é pressionado para finalizar o processo e interromper o contêiner.
docker run -it --rm --entrypoint "cmd.exe" counter-image
Microsoft Windows [Version 10.0.17763.379]
(c) 2018 Microsoft Corporation. All rights reserved.
C:\>dir
Volume in drive C has no label.
Volume Serial Number is 3005-1E84
Directory of C:\
04/09/2019 08:46 AM <DIR> app
03/07/2019 10:25 AM 5,510 License.txt
04/02/2019 01:35 PM <DIR> Program Files
04/09/2019 01:06 PM <DIR> Users
04/02/2019 01:35 PM <DIR> Windows
1 File(s) 5,510 bytes
4 Dir(s) 21,246,517,248 bytes free
C:\>^C
Comandos essenciais
O Docker tem muitos comandos diferentes que criam, gerenciam e interagem com contêineres e imagens. Esses comandos do Docker são essenciais para gerenciar seus contêineres:
Limpar os recursos
Durante este tutorial, você criou contêineres e imagens. Se quiser, exclua esses recursos. Use os seguintes comandos para:
Listar todos os contêineres
docker ps -a
Interrompa os contêineres que estão em execução pelo nome.
docker stop core-counter
Excluir o contêiner
docker rm core-counter
Em seguida, exclua todas as imagens que você não deseja mais em seu computador. Exclua a imagem criada pelo seu Dockerfile e exclua a imagem do .NET na qual o Dockerfile teve base. Você pode usar a ID DA IMAGEM ou a cadeia de caracteres formatada REPOSITÓRIO:TAG.
docker rmi counter-image:latest
docker rmi mcr.microsoft.com/dotnet/aspnet:8.0
docker rmi counter-image:latest
docker rmi mcr.microsoft.com/dotnet/aspnet:7.0
Use o comando docker images
para ver uma lista de imagens instaladas.
Dica
Arquivos de imagem podem ser grandes. Normalmente, você removeria contêineres temporários criados durante o teste e o desenvolvimento de seu aplicativo. Em geral, mantenha as imagens de base com o runtime instalado se você planeja construir outras imagens com base nesse runtime.