Compartilhar via


Tutorial: Colocar em contêiner um aplicativo .NET

Neste tutorial, você aprenderá a colocar em contêiner um aplicativo .NET com o Docker. Os contêineres têm muitos recursos e benefícios, como ser uma infraestrutura imutável, fornecer uma arquitetura portátil e habilitar 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 .NET
  • Criar uma imagem do Docker
  • Criar e executar um contêiner do Docker

Você explora 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.

Dica

Se você estiver interessado em publicar seu aplicativo .NET como um contêiner sem a necessidade de Docker ou Podman, veja Conteinerizar um aplicativo .NET com dotnet publish.

Nota

Este tutorial não é para aplicativos do ASP.NET Core. Se você estiver usando o ASP.NET Core, consulte o tutorial Saiba como colocar em contêiner um aplicativo do ASP.NET Core.

Pré-requisitos

Instale os seguintes pré-requisitos:

  • SDK do .NET 8+.
    Se você tiver o .NET instalado, use o comando dotnet --info para determinar qual SDK você está usando.
  • Docker Community Edition.
  • Uma pasta de trabalho temporária para o Dockerfile e aplicativo .NET de exemplo. Neste tutorial, o nome docker-working é usado como a pasta de trabalho.

Criar um aplicativo .NET

Você precisa de um aplicativo .NET executado pelo contêiner do Docker. Abra o terminal, crie uma pasta de trabalho se ainda não tiver feito isso e insira-a. Na pasta de trabalho, execute o seguinte comando para criar um novo projeto em um subdiretório chamado App:

dotnet new console -o App -n DotNet.Docker

A árvore de pastas é semelhante à seguinte estrutura de diretório:

📁 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 nova pasta chamada App e gera um aplicativo de console "Olá, Mundo". Agora, você altera os diretórios e navega para a pasta App 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, termina imediatamente. Para este tutorial, você usará um aplicativo que faz loops indefinidamente. Abra o arquivo Program.cs em um editor de texto.

Dica

Se você estiver usando o Visual Studio Code, na sessão do terminal anterior digite o seguinte comando:

code .

Esse comando abre a pasta App que contém o projeto no Visual Studio Code.

O Program.cs deve ser semelhante ao seguinte código C#:

Console.WriteLine("Hello World!");

Substitua o arquivo pelo código a seguir 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 este aplicativo é executado indefinidamente. Use o comando cancelar Ctrl+C para pará-lo. Considere a seguinte saída de exemplo:

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 limitará a contagem a esse valor e sairá. Experimente com dotnet run -- 5 para contar até cinco.

Importante

Todos os parâmetros após -- não são passados para o comando dotnet run e, em vez disso, são passados para seu aplicativo.

Publicar aplicativo .NET

Para que o aplicativo seja adequado para uma criação de imagem, ele precisa ser compilado. O comando dotnet publish é mais adequado para isso, pois ele cria e publica o aplicativo. Para obter uma referência detalhada, consulte a documentação dos comandos dotnet build e dotnet publish.

dotnet publish -c Release

Dica

Se você estiver interessado em publicar seu aplicativo .NET como um contêiner sem a necessidade do Docker, consulte Containerizar um aplicativo .NET com dotnet publish.

O comando dotnet publish compila seu aplicativo para a pasta publish. O caminho para a pasta publish da pasta de trabalho deve ser ./App/bin/Release/<TFM>/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\net9.0\publish\

    Directory: C:\Users\default\docker-working\App\bin\Release\net9.0\publish

Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
-a----          1/6/2025  10:11 AM            431 DotNet.Docker.deps.json
-a----          1/6/2025  10:11 AM           6144 DotNet.Docker.dll
-a----          1/6/2025  10:11 AM         145408 DotNet.Docker.exe
-a----          1/6/2025  10:11 AM          11716 DotNet.Docker.pdb
-a----          1/6/2025  10:11 AM            340 DotNet.Docker.runtimeconfig.json
dir .\bin\Release\net8.0\publish\

    Directory: C:\Users\default\docker-working\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

Criar o Dockerfile

O arquivo do Dockerfile é usado pelo comando para criar uma imagem de contêiner. Esse arquivo é um arquivo de texto chamado Dockerfile que não tem uma extensão.

Crie um arquivo chamado dockerfile no diretório que contém o .csproj e abra-o em um editor de texto. Este tutorial usa a imagem de runtime do ASP.NET Core (que contém a imagem de runtime do .NET) e corresponde ao aplicativo de console do .NET.

FROM mcr.microsoft.com/dotnet/sdk:9.0@sha256:3fcf6f1e809c0553f9feb222369f58749af314af6f063f389cbd2f913b4ad556 AS build
WORKDIR /App

# Copy everything
COPY . ./
# Restore as distinct layers
RUN dotnet restore
# Build and publish a release
RUN dotnet publish -o out

# Build runtime image
FROM mcr.microsoft.com/dotnet/aspnet:9.0@sha256:b4bea3a52a0a77317fa93c5bbdb076623f81e3e2f201078d89914da71318b5d8
WORKDIR /App
COPY --from=build /App/out .
ENTRYPOINT ["dotnet", "DotNet.Docker.dll"]

Nota

A imagem ASP.NET Core runtime é usada intencionalmente aqui, mas a imagem mcr.microsoft.com/dotnet/runtime:9.0 poderia ter sido usada.

FROM mcr.microsoft.com/dotnet/sdk:8.0@sha256:35792ea4ad1db051981f62b313f1be3b46b1f45cadbaa3c288cd0d3056eefb83 AS build
WORKDIR /App

# Copy everything
COPY . ./
# Restore as distinct layers
RUN dotnet restore
# Build and publish a release
RUN dotnet publish -o out

# Build runtime image
FROM mcr.microsoft.com/dotnet/aspnet:8.0@sha256:6c4df091e4e531bb93bdbfe7e7f0998e7ced344f54426b7e874116a3dc3233ff
WORKDIR /App
COPY --from=build /App/out .
ENTRYPOINT ["dotnet", "DotNet.Docker.dll"]

Nota

A imagem ASP.NET Core runtime é usada intencionalmente aqui, mas a imagem mcr.microsoft.com/dotnet/runtime:8.0 poderia ter sido usada.

Importante

Incluir um SHA (algoritmo de hash seguro) após a marca de imagem em um Dockerfile é uma prática recomendada. Isso garante que a imagem não seja adulterada e que a imagem seja a mesma esperada. O SHA é um identificador exclusivo para a imagem. Para obter mais informações, consulte Docker Docs: enviar por pull uma imagem por digest.

Dica

Esse Dockerfile utiliza compilações em múltiplos estágios, que otimizam o tamanho final da imagem estruturando a construção em camadas e deixando apenas 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 MCR (Registro de Contêiner da Microsoft, mcr.microsoft.com) é um sindicato do Hub do Docker, que hospeda contêineres publicamente acessíveis. 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 9.0, que é usada para controle de versão. Portanto, mcr.microsoft.com/dotnet/aspnet:9.0 é o runtime do .NET 9.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 9.0 e a imagem base referenciada no do Dockerfile é marcada com 9.0.

Importante

Ao usar imagens de contêiner baseadas no Windows, você precisa especificar a marca de imagem além de simplesmente 9.0, por exemplo, mcr.microsoft.com/dotnet/aspnet:9.0-nanoserver-1809 em vez de mcr.microsoft.com/dotnet/aspnet:9.0. Selecione um nome de imagem com base em se você está usando o Nano Server ou o Windows Server Core e qual versão desse sistema operacional. Você pode encontrar uma lista completa de todas as tags com suporte na página do Hub do Docker de .NET.

Salve o arquivo do Dockerfile . A estrutura do diretório da pasta de trabalho deve ser semelhante à seguinte. Alguns dos arquivos e pastas de nível mais profundo são omitidos para economizar espaço no artigo:

📁 docker-working
    └──📂 App
        ├── Dockerfile
        ├── DotNet.Docker.csproj
        ├── Program.cs
        ├──📂 bin
        │   └───📂 Release
        │        └───📂 net9.0
        │             ├───📂 publish
        │             │    ├─── DotNet.Docker.deps.json
        │             │    ├─── DotNet.Docker.dll
        │             │    ├─── DotNet.Docker.exe
        │             │    ├─── DotNet.Docker.pdb
        │             │    └─── DotNet.Docker.runtimeconfig.json
        │             ├─── DotNet.Docker.deps.json
        │             ├─── DotNet.Docker.dll
        │             ├─── DotNet.Docker.exe
        │             ├─── DotNet.Docker.pdb
        │             └─── DotNet.Docker.runtimeconfig.json
        └──📁 obj
            └──...

A palavra-chave FROM requer um nome de imagem de contêiner do Docker totalmente qualificado. O MCR (Registro de Contêiner da Microsoft, mcr.microsoft.com) é um sindicato do Hub do Docker, que hospeda contêineres publicamente acessíveis. 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 é usada 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 em se você está usando o Nano Server ou o Windows Server Core e qual 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 do Dockerfile . A estrutura do diretório da pasta de trabalho deve ser semelhante à seguinte. Alguns dos arquivos e pastas de nível mais profundo são omitidos para economizar espaço no 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
            └──...

A instrução ENTRYPOINT define dotnet como o host do DotNet.Docker.dll. No entanto, é possível definir o ENTRYPOINT como o próprio executável do aplicativo, contando com o sistema operacional como o host do aplicativo:

ENTRYPOINT ["./DotNet.Docker"]

Isso faz com que o aplicativo seja executado diretamente, sem dotnete, em vez disso, depende do host do aplicativo e do sistema operacional subjacente. Para obter mais informações sobre como implantar binários multiplataforma, consulte Produza um binário multiplataforma.

Para criar o contêiner, no terminal, execute o seguinte comando:

docker build -t counter-image -f Dockerfile .

O Docker processa 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 cria a imagem e cria um repositório local chamado de contra-imagem que aponta para essa imagem. Após a conclusão desse comando, execute docker images para ver uma lista de imagens instaladas:

REPOSITORY       TAG       IMAGE ID       CREATED          SIZE
counter-image    latest    1c1f1433e51d   32 seconds ago   223MB
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. Além disso, a tag de imagem, o identificador de imagem, o tamanho e quando ela foi criada fazem parte da saída. As etapas finais do dockerfile são criar um contêiner a partir 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:9.0
WORKDIR /App
COPY --from=build /App/out .
ENTRYPOINT ["dotnet", "DotNet.Docker.dll"]
FROM mcr.microsoft.com/dotnet/aspnet:8.0
WORKDIR /App
COPY --from=build /App/out .
ENTRYPOINT ["dotnet", "DotNet.Docker.dll"]

O comando FROM especifica a imagem base e a marca a ser usada. O comando WORKDIR altera o diretório atual dentro do contêiner para App.

O comando COPY informa ao Docker para copiar o diretório de origem especificado para uma pasta de destino. Neste exemplo, o conteúdo de publish na camada build são enviados para a pasta chamada App/out, sendo assim a origem de onde copiar. Todo o conteúdo publicado no diretório App/out é copiado para o diretório de trabalho atual (App).

O próximo comando, ENTRYPOINT, instrui o Docker a configurar o contêiner para que ele execute como um programa. Quando o contêiner é iniciado, o comando ENTRYPOINT é executado. Quando esse comando termina, o contêiner é interrompido automaticamente.

Dica

Antes do .NET 8, os contêineres configurados para execução como somente leitura podem 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 várias variáveis de ambiente do .NET, consulte variáveis de ambiente do .NET.

Nota

O .NET 6 padroniza o prefixo DOTNET_ 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, ainda deverá usar o prefixo COMPlus_ para variáveis de ambiente.

Criar um contêiner

Agora que você tem uma imagem que contém seu aplicativo, você pode criar um contêiner. Você pode criar um contêiner de duas maneiras. Primeiro, crie um novo contêiner que esteja parado.

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 docker create mostra a ID DO CONTÊINER (seu identificador será diferente):

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 específico core-counter. Esse nome é 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 que estão 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 espiar o fluxo de saída. Neste exemplo, o atalho de teclado Ctrl+C é usado para desvincular-se do contêiner em execução. Esse pressionamento de tecla encerra o processo no contêiner, 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 por perto que não façam nada. Exclua o contêiner criado 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 verifica uma segunda vez se há contêineres 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. Esse comando elimina a necessidade de executar docker create e, em seguida, docker start. Você também pode configurar esse comando para excluir automaticamente o contêiner quando ele parar. 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 é excluído automaticamente 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 também permite modificar o comando do do Dockerfile e executar outra coisa, mas apenas para esse contêiner. Por exemplo, use o comando a seguir para executar bash ou cmd.exe. Edite o comando conforme necessário.

Neste exemplo, ENTRYPOINT é alterado para cmd.exe. Ctrl+C é pressionado para encerrar o processo e parar 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

Nota

Este exemplo só funciona em contêineres do Windows. Os contêineres do Linux não têm cmd.exe.

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 recursos

Durante este tutorial, você criou contêineres e imagens. Se desejar, exclua esses recursos. Utilize os comandos a seguir para

  1. Listar todos os contêineres

    docker ps -a
    
  2. Interrompa os contêineres que estão em execução pelo nome.

    docker stop core-counter
    
  3. Excluir o contêiner

    docker rm core-counter
    

Em seguida, exclua as imagens que você não deseja mais em seu computador. Exclua a imagem criada pelo do Dockerfile e exclua a imagem do .NET na qual o do Dockerfile se baseava. 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:9.0
docker rmi counter-image:latest
docker rmi mcr.microsoft.com/dotnet/aspnet:8.0

Use o comando docker images para ver uma lista de imagens instaladas.

Dica

Os arquivos de imagem podem ser grandes. Normalmente, você removeria contêineres temporários criados durante o teste e o desenvolvimento do aplicativo. Normalmente, você mantém as imagens base com o runtime instalado se planeja criar outras imagens com base nesse runtime.

Próximas etapas