Tutorial: Inclusión de una aplicación de .NET en un contenedor
En este tutorial obtendrá información sobre cómo incluir una aplicación de .NET en un contenedor con Docker. Los contenedores tienen muchas características y ventajas, como ser una infraestructura inmutable, proporcionar una arquitectura portátil y permitir la escalabilidad. La imagen puede usarse para crear contenedores para un entorno de desarrollo local, una nube privada o una nube pública.
En este tutorial ha:
- Crear y publicar una aplicación .NET sencilla
- Crear y configurar un archivo Dockerfile para .NET
- Creación de una imagen de Docker
- Crear y ejecutar un contenedor de Docker
Aprenderá sobre las tareas de compilación e implementación de un contenedor de Docker para una aplicación .NET. La plataforma Docker usa el motor de Docker para compilar y empaquetar rápidamente aplicaciones como imágenes de Docker. Estas imágenes se escriben en el formato Dockerfile para implementarse y ejecutarse en un contenedor superpuesto.
Nota
Este tutorial no es para aplicaciones ASP.NET Core. Si usa ASP.NET Core, lea el tutorial sobre cómo incluir una aplicación de ASP.NET Core en un contenedor.
Requisitos previos
Instale estos requisitos previos:
- SDK de .NET 8 y versiones posteriores
Si tiene instalado .NET, use el comandodotnet --info
para determinar el SDK que está usando. - Docker Community Edition
- Una carpeta de trabajo temporal para Dockerfile y una aplicación .NET de ejemplo. En este tutorial, el nombre docker-working se usa como la carpeta de trabajo.
- SDK de .NET 7 (y posteriores)
Si tiene instalado .NET, use el comandodotnet --info
para determinar el SDK que está usando. - Docker Community Edition
- Una carpeta de trabajo temporal para Dockerfile y una aplicación .NET de ejemplo. En este tutorial, el nombre docker-working se usa como la carpeta de trabajo.
Creación de aplicaciones .NET
Necesita una aplicación .NET que el contenedor Docker ejecuta. Abra el terminal, cree una carpeta de trabajo si todavía no lo ha hecho y entre en ella. En la carpeta de trabajo, ejecute el comando siguiente para crear un proyecto en un subdirectorio llamado App:
dotnet new console -o App -n DotNet.Docker
El árbol de carpetas es similar al siguiente:
📁 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
Con el comando dotnet new
se crea una carpeta denominada App y se genera una aplicación de consola "Hola mundo". Cambie los directorios y vaya a la carpeta App, desde la sesión de Terminal. Use el comando dotnet run
para iniciar la aplicación. La aplicación se ejecuta e imprime Hello World!
debajo del comando:
cd App
dotnet run
Hello World!
La plantilla predeterminada crea una aplicación que imprime en el terminal y termina de inmediato. En este tutorial, se usa una aplicación que se repite en bucle de manera indefinida. Abra el archivo Program.cs en un editor de texto.
Sugerencia
Si está utilizando Visual Studio Code, en la sesión de Terminal anterior, escriba el siguiente comando:
code .
Se abrirá la carpeta App que contiene el proyecto en Visual Studio Code.
El archivo Program.cs debería ser similar al código de C# siguiente:
Console.WriteLine("Hello World!");
Reemplace el archivo por el código siguiente que cuenta números 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));
}
Guarde el archivo y vuelva a probar el programa con dotnet run
. Recuerde que esta aplicación se ejecuta de manera indefinida. Use el comando Cancelar Ctrl+C para detenerla. A continuación se muestra un resultado de ejemplo:
dotnet run
Counter: 1
Counter: 2
Counter: 3
Counter: 4
^C
Si pasa un número en la línea de comandos a la aplicación, solo se contará hasta esa cantidad y se cerrará. Inténtelo con dotnet run -- 5
para contar hasta cinco.
Importante
Cualquier parámetro posterior a --
no se pasa al comando dotnet run
y, en su lugar, se pasa a su aplicación.
Publicación de una aplicación .NET
Antes de agregar la aplicación .NET a la imagen de Docker, primero debe publicarse. Es mejor que el contenedor ejecute la versión publicada de la aplicación. Ejecute el comando siguiente para publicar la aplicación:
dotnet publish -c Release
Con este comando se compila la aplicación en la carpeta publish. La ruta de acceso a la carpeta publish desde la carpeta de trabajo debería ser .\App\bin\Release\net8.0\publish\
.
Con este comando se compila la aplicación en la carpeta publish. La ruta de acceso a la carpeta publish desde la carpeta de trabajo debería ser .\App\bin\Release\net7.0\publish\
.
En la carpeta App, obtenga un listado de los directorios de la carpeta publish para comprobar que se ha creado el archivo DoNet.Docker.dll.
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
Creación del archivo Dockerfile
El archivo Dockerfile lo usa el comando docker build
para crear una imagen de contenedor. Este archivo es un archivo de texto denominado Dockerfile que no tiene ninguna extensión.
Cree un archivo denominado Dockerfile en el directorio que contiene el archivo .csproj y ábralo en un editor de texto. En este tutorial se usa la imagen del runtime de ASP.NET Core (que contiene la imagen de runtime de .NET) y corresponde a la aplicación de consola de .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"]
Nota:
Aquí se usa intencionadamente la imagen de runtime de ASP.NET Core, aunque se podría haber usado la imagen de mcr.microsoft.com/dotnet/runtime:8.0
.
Sugerencia
Este Dockerfile usa compilaciones de varias fases, lo que optimiza el tamaño final de la imagen mediante la disposición en capas de la compilación y dejando solo los artefactos necesarios. Para obtener más información, vea Documentación de Docker: compilaciones de varias fases.
La palabra clave FROM
requiere un nombre completo de imagen de contenedor de Docker. Microsoft Container Registry (MCR, mcr.microsoft.com) es un sindicato de Docker Hub, que hospeda contenedores a los que se puede acceder de forma pública. El segmento dotnet
es el repositorio de contenedores, mientras que los segmentos sdk
o aspnet
son el nombre de la imagen de contenedor. La imagen está etiquetada con 8.0
, que se usa para el control de versiones. Por tanto, mcr.microsoft.com/dotnet/aspnet:8.0
es el runtime de .NET 8.0. Asegúrese de extraer el runtime que coincida con el que el SDK tiene como destino. Por ejemplo, la aplicación creada en la sección anterior usaba el SDK de .NET 8.0, y la imagen base a la que se hace referencia en Dockerfile se etiqueta con 8.0.
Importante
Al usar imágenes de contenedor basadas en Windows, debe especificar la etiqueta de imagen más allá de simplemente 8.0
, por ejemplo, mcr.microsoft.com/dotnet/aspnet:8.0-nanoserver-1809
en lugar de mcr.microsoft.com/dotnet/aspnet:8.0
. Seleccione un nombre de imagen en función de si usa Nano Server o Windows Server Core y qué versión de ese sistema operativo usa. Puede encontrar una lista completa de todas las etiquetas compatibles en la página de Docker Hub de .NET.
Guarde el archivo Dockerfile. La estructura de directorios de la carpeta de trabajo debería tener el siguiente aspecto. Algunos de los archivos y carpetas inferiores se han omitido para ahorrar espacio en este artículo:
📁 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"]
Nota:
Aquí se usa intencionadamente la imagen de runtime de ASP.NET Core, aunque se podría haber usado la imagen de mcr.microsoft.com/dotnet/runtime:7.0
.
Sugerencia
Este Dockerfile usa compilaciones de varias fases, lo que optimiza el tamaño final de la imagen mediante la disposición en capas de la compilación y dejando solo los artefactos necesarios. Para obtener más información, vea Documentación de Docker: compilaciones de varias fases.
La palabra clave FROM
requiere un nombre completo de imagen de contenedor de Docker. Microsoft Container Registry (MCR, mcr.microsoft.com) es un sindicato de Docker Hub, que hospeda contenedores accesibles públicamente. El segmento dotnet
es el repositorio de contenedores, mientras que los segmentos sdk
o aspnet
son el nombre de la imagen de contenedor. La imagen está etiquetada con 7.0
, que se usa para el control de versiones. Por tanto, mcr.microsoft.com/dotnet/aspnet:7.0
es el entorno de ejecución de .NET 7.0. Asegúrese de extraer el runtime que coincida con el que el SDK tiene como destino. Por ejemplo, la aplicación creada en la sección anterior usaba el SDK de .NET 7.0, y la imagen base a la que se hace referencia en el documento Dockerfile se etiqueta con 7.0.
Guarde el archivo Dockerfile. La estructura de directorios de la carpeta de trabajo debería tener el siguiente aspecto. Algunos de los archivos y carpetas inferiores se han omitido para ahorrar espacio en este artículo:
📁 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
└──...
Desde un terminal, ejecute el comando siguiente:
docker build -t counter-image -f Dockerfile .
Docker procesará cada línea en el archivo Dockerfile. En .
el comando docker build
establece el contexto de compilación de la imagen. El modificador -f
es la ruta de acceso al archivo Dockerfile. Este comando crea la imagen y un repositorio local denominado counter-image que apunta a esa imagen. Una vez que finalice este comando, ejecute docker images
para ver una lista de las imágenes instaladas:
docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
counter-image latest 2f15637dc1f6 10 minutes ago 217MB
El repositorio counter-image
es el nombre de la imagen. La etiqueta latest
es la etiqueta que se usa para identificar la imagen. 2f15637dc1f6
es el id. de la imagen. 10 minutes ago
especifica la hora de creación de la imagen. 217MB
es el tamaño de la imagen. Los pasos finales del archivo Dockerfile son crear un contenedor a partir de la imagen y ejecutar la aplicación, copiar la aplicación publicada en el contenedor y definir el punto 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
El repositorio counter-image
es el nombre de la imagen. La etiqueta latest
es la etiqueta que se usa para identificar la imagen. 2f15637dc1f6
es el id. de la imagen. 10 minutes ago
especifica la hora de creación de la imagen. 208MB
es el tamaño de la imagen. Los pasos finales del archivo Dockerfile son crear un contenedor a partir de la imagen y ejecutar la aplicación, copiar la aplicación publicada en el contenedor y definir el punto de entrada.
FROM mcr.microsoft.com/dotnet/aspnet:7.0
WORKDIR /App
COPY --from=build-env /App/out .
ENTRYPOINT ["dotnet", "DotNet.Docker.dll"]
El comando COPY
indica a Docker que copie la carpeta especificada en el equipo a una carpeta del contenedor. En este ejemplo, la carpeta publish se copia en una carpeta denominada App/out del contenedor.
El comando WORKDIR
cambia el directorio actual dentro del contenedor a App.
El comando siguiente, ENTRYPOINT
, indica a Docker que configure el contenedor para que se ejecute como ejecutable. Cuando el contenedor se inicia, se ejecuta el comando ENTRYPOINT
. Cuando este comando finaliza, el contenedor se detiene automáticamente.
Sugerencia
Antes de .NET 8, los contenedores configurados para ejecutarse como de solo lectura pueden producir un error con Failed to create CoreCLR, HRESULT: 0x8007000E
. Para solucionar este problema, especifique una variable de entorno DOTNET_EnableDiagnostics
como 0
(justo antes del paso ENTRYPOINT
):
ENV DOTNET_EnableDiagnostics=0
Para obtener más información sobre las diversas variables de entorno de .NET, consulte Variables de entorno de .NET.
Nota:
.NET 6 estandariza en el prefijo DOTNET_
en lugar de en COMPlus_
para las variables de entorno que configuran el comportamiento en tiempo de ejecución de .NET. Sin embargo, el prefijo COMPlus_
seguirá funcionando. Si usa una versión anterior del runtime de .NET, debe seguir usando el prefijo COMPlus_
para las variables de entorno.
Crear un contenedor
Ahora que tiene una imagen que contiene la aplicación, puede crear un contenedor. Hay dos formas de crear un contenedor. En primer lugar, cree un contenedor que esté detenido.
docker create --name core-counter counter-image
El comando docker create
crea un contenedor basado en la imagen counter-image. La salida de ese comando muestra el valor CONTAINER ID (el suyo será distinto) del contenedor creado:
d0be06126f7db6dd1cee369d911262a353c9b7fb4829a0c11b4b2eb7b2d429cf
Para ver una lista de todos los contenedores, use el 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
Administración del contenedor
El contenedor se creó con un nombre específico, core-counter
, usado para administrar el contenedor. En el ejemplo siguiente se usa el comando docker start
para iniciar el contenedor y luego se usa el comando docker ps
para mostrar solo los contenedores que están en ejecución:
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
Del mismo modo, el comando docker stop
detiene el contenedor. En el ejemplo siguiente se usa el comando docker stop
para detener el contenedor y luego se usa el comando docker ps
para mostrar que no hay ningún contenedor en ejecución:
docker stop core-counter
core-counter
docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
Conectarse a un contenedor
Una vez que se ejecuta un contenedor, puede conectarse a él para ver la salida. Use los comandos docker start
y docker attach
para iniciar el contenedor y echar un vistazo al flujo de salida. En este ejemplo, la pulsación de teclas Ctrl+C se usa para desasociarse del contenedor en ejecución. A menos que se especifique lo contrario, al pulsar esta tecla finaliza el proceso en el contenedor, con lo que se detendría el contenedor. El parámetro --sig-proxy=false
garantiza que Ctrl+C no va a detener el proceso en el contenedor.
Después de desasociarse del contenedor, reasócielo para comprobar que sigue en ejecución.
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
Eliminación de un contenedor
En este artículo, no quiere que haya contenedores que no hagan nada. Elimine el contenedor que creó anteriormente. Si el contenedor está en ejecución, deténgalo.
docker stop core-counter
En el ejemplo siguiente se muestran todos los contenedores. Luego, se usa el comando docker rm
para eliminar el contenedor y después se vuelve a comprobar si hay algún contenedor en ejecución.
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
Ejecución única
Docker proporciona el comando docker run
para crear y ejecutar el contenedor como comando único. Este comando elimina la necesidad de ejecutar docker create
y luego docker start
. También puede establecer este comando en que elimine automáticamente el contenedor cuando este se detenga. Por ejemplo, use docker run -it --rm
para hacer dos cosas: primero, use automáticamente el terminal actual para conectarse al contenedor y cuando el contenedor finalice, quítelo:
docker run -it --rm counter-image
Counter: 1
Counter: 2
Counter: 3
Counter: 4
Counter: 5
^C
El contenedor también pasa parámetros a la ejecución de la aplicación .NET. Para indicar a la aplicación .NET que cuente solo hasta 3, pase 3.
docker run -it --rm counter-image 3
Counter: 1
Counter: 2
Counter: 3
Con docker run -it
, el comando Ctrl+C detiene el proceso que se ejecuta en el contenedor, lo que, a su vez, detiene el contenedor. Como se proporcionó el parámetro --rm
, el contenedor se elimina automáticamente cuando se detiene el proceso. Compruebe que no existe:
docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
Cambio de ENTRYPOINT
El comando docker run
también permite modificar el comando ENTRYPOINT
desde el archivo Dockerfile y ejecute algún otro elemento, pero solo para ese contenedor. Por ejemplo, use el comando siguiente para ejecutar bash
o cmd.exe
. Edite el comando según sea necesario.
En este ejemplo, ENTRYPOINT
cambia a cmd.exe
. Se presiona Ctrl+C para finalizar el proceso y detener el contenedor.
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 esenciales
Docker tiene muchos comandos diferentes que crean, administran e interactúan con contenedores e imágenes. Estos comandos de Docker son esenciales para la administración de los contenedores:
Limpiar los recursos
Durante este tutorial, creó contenedores e imágenes. Elimine estos recursos si quiere hacerlo. Use los comandos siguientes para
Mostrar todos los contenedores
docker ps -a
Detener los contenedores que están en ejecución por nombre
docker stop core-counter
Eliminar el contenedor
docker rm core-counter
A continuación, elimine las imágenes que ya no quiere tener en la máquina. Elimine la imagen que creó el archivo Dockerfile y luego elimine la imagen de .NET en que se basó el archivo Dockerfile. Puede usar el valor IMAGE ID o la cadena con formato REPOSITORY: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 el comando docker images
para ver una lista de las imágenes instaladas.
Sugerencia
Los archivos de imagen pueden ser grandes. Por lo general, quitaría los contenedores temporales que creó al probar y desarrollar la aplicación. Habitualmente, estas imágenes base se conservan con el runtime instalado si se planea crear otras imágenes basadas en ese runtime.