Compartir a través de


Tutorial: Contenedorización de una aplicación de .NET

En este tutorial, aprenderá a incluir en contenedores una aplicación .NET con Docker. Los contenedores tienen muchas características y ventajas, como ser una infraestructura inmutable, proporcionar una arquitectura portátil y habilitar la escalabilidad. La imagen se puede usar para crear contenedores para el entorno de desarrollo local, la nube privada o la nube pública.

En este tutorial, harás lo siguiente:

  • Creación y publicación de una aplicación .NET sencilla
  • Creación y configuración de un Dockerfile para .NET
  • Creación de una imagen de Docker
  • Creación y ejecución de un contenedor de Docker

Explorará la compilación del contenedor de Docker e implementará tareas 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 que se va a implementar y ejecutar en un contenedor en capas.

Sugerencia

Si está interesado en publicar la aplicación de .NET como contenedor sin necesidad de Docker o Podman, consulte Containerize a .NET app with dotnet publish (Incluir en contenedores una aplicación de .NET con dotnet publish).

Nota:

Este tutorial no es para aplicaciones de ASP.NET Core. Si usa ASP.NET Core, consulte el tutorial Sobre cómo incluir en contenedores una aplicación ASP.NET Core .

Prerrequisitos

Instale los siguientes requisitos previos:

  • SDK de .NET 9+.
    Si tiene instalado .NET, use el dotnet --info comando para determinar qué SDK usa.
  • SDK de .NET 8+.
    Si tiene instalado .NET, use el dotnet --info comando para determinar qué SDK usa.
  • Docker Community Edition.
  • Una carpeta de trabajo temporal para la aplicación de ejemplo Dockerfile y .NET. En este tutorial, el nombre docker-working se usa como carpeta de trabajo.

Creación de una aplicación .NET

Necesita una aplicación de .NET que ejecute el contenedor de Docker. Abra el terminal, cree una carpeta de trabajo si aún no lo ha hecho y escríbala. En la carpeta de trabajo, ejecute el siguiente comando para crear un nuevo proyecto en un subdirectorio denominado App:

dotnet new console -o App -n DotNet.Docker

El árbol de carpetas tiene un aspecto similar a la siguiente estructura de directorios:

📁 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

El dotnet new comando crea una nueva carpeta denominada App y genera una aplicación de consola "Hello World". Ahora, cambia los directorios y navega a la carpeta Aplicación desde la sesión de terminal. Use el dotnet run comando 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 se imprime en el terminal y, a continuación, finaliza inmediatamente. En este tutorial, usarás una aplicación que se ejecuta indefinidamente en un bucle. Abra el archivo Program.cs en un editor de texto.

Sugerencia

Si usa Visual Studio Code, en la sesión de terminal anterior, escriba el siguiente comando:

code .

Este comando abre la carpeta App que contiene el proyecto en Visual Studio Code.

El Program.cs debe tener un aspecto similar al siguiente código de C#:

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 indefinidamente. Use el comando cancelar Ctrl+C para detenerlo. Considere la siguiente salida 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, limita el recuento a esa cantidad y, a continuación, sale. Pruébelo 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 la aplicación .NET

Para que la aplicación sea adecuada para crear una imagen, debe compilarse. El dotnet publish comando es el más adecuado para esto, ya que compila y publica la aplicación. Para obtener una referencia detallada, consulte la documentación de comandos dotnet build y dotnet publish .

dotnet publish -c Release

Sugerencia

Si está interesado en publicar la aplicación .NET como contenedor sin necesidad de Docker, consulte Contenedorización de una aplicación de .NET con dotnet publish.

El dotnet publish comando compila la aplicación en la carpeta publish . La ruta de acceso a la carpeta publish desde la carpeta de trabajo debe ser ./App/bin/Release/TFM>/<publish/:

En la carpeta Aplicación , obtenga una lista de directorios de la carpeta publish para comprobar que se creó el archivo DotNet.Docker.dll .

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

Creación del Dockerfile

El comando usa el docker build archivo Dockerfile para crear una imagen de contenedor. Este archivo es un archivo de texto denominado Dockerfile que no tiene una 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 en tiempo de ejecución de ASP.NET Core (que contiene la imagen en tiempo de ejecución de .NET) y se corresponde con la aplicación de consola de .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:

La imagen en tiempo de ejecución de ASP.NET Core se usa intencionadamente aquí, aunque la mcr.microsoft.com/dotnet/runtime:9.0 imagen podría usarse en su lugar.

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:

La imagen en tiempo de ejecución de ASP.NET Core se usa intencionadamente aquí, aunque la mcr.microsoft.com/dotnet/runtime:8.0 imagen podría usarse en su lugar.

Importante

Incluir un algoritmo hash seguro (SHA) después de la etiqueta de imagen en un Dockerfile es un procedimiento recomendado. Esto garantiza que la imagen no se manipule y que la imagen sea la misma que espera. SHA es un identificador único para la imagen. Para obtener más información, consulte Documentos de Docker: Extracción de una imagen por resumen.

Sugerencia

Este Dockerfile utiliza compilaciones de varias fases, que optimizan el tamaño final de la imagen al aplicar capas al proceso de compilación y dejando solo los artefactos necesarios. Para más información, consulte Docker Docs: compilaciones en 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 dotnet segmento es el repositorio de contenedores, mientras que el sdk o aspnet es el nombre de la imagen del contenedor. La imagen se etiqueta con 9.0, que se usa para el control de versiones. Por lo tanto, mcr.microsoft.com/dotnet/aspnet:9.0 es el entorno de ejecución de .NET 9.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 usó el SDK de .NET 9.0 y la imagen base a la que se hace referencia en dockerfile se etiqueta con la versión 9.0.

Importante

Al usar imágenes de contenedor basadas en Windows, debe especificar la etiqueta de imagen más allá de simplemente 9.0, por ejemplo, mcr.microsoft.com/dotnet/aspnet:9.0-nanoserver-1809 en lugar de mcr.microsoft.com/dotnet/aspnet:9.0. Seleccione un nombre de imagen en función de si usa Nano Server o Windows Server Core y la versión de ese sistema operativo. 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 debe ser similar a la siguiente. Algunos de los archivos y carpetas de nivel más profundo se omiten para ahorrar espacio en el artículo:

📁 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
            └──...

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 dotnet segmento es el repositorio de contenedores, mientras que el sdk o aspnet es el nombre de la imagen del contenedor. La imagen se etiqueta con 8.0, que se usa para el control de versiones. Por lo tanto, mcr.microsoft.com/dotnet/aspnet:8.0 es el entorno de ejecución 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 usó el SDK de .NET 8.0 y la imagen base a la que se hace referencia en dockerfile se etiqueta con la versión 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 la versión de ese sistema operativo. Puede encontrar una lista completa de todas las etiquetas admitidas en la página de Docker Hub de .NET.

Guarde el archivo Dockerfile . La estructura de directorios de la carpeta de trabajo debe ser similar a la siguiente. Algunos de los archivos y carpetas de nivel más profundo se omiten para ahorrar espacio en el 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
            └──...

La ENTRYPOINT instrucción establece dotnet como host para .DotNet.Docker.dll Sin embargo, es posible definir el ENTRYPOINT como el ejecutable de la propia aplicación, confiando en el sistema operativo como host de la aplicación.

ENTRYPOINT ["./DotNet.Docker"]

Esto hace que la aplicación se ejecute directamente, sin dotnet, y en su lugar se basa en el host de la aplicación y en el sistema operativo subyacente. Para obtener más información sobre la implementación de archivos binarios multiplataforma, vea Generar un binario multiplataforma.

Para compilar el contenedor, desde el terminal, ejecute el siguiente comando:

docker build -t counter-image -f Dockerfile .

Docker procesa cada línea del 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 compila la imagen y crea un repositorio local denominado counter-image que apunta a esa imagen. Una vez finalizado este comando, ejecute docker images para ver una lista de imágenes 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

El counter-image repositorio es el nombre de la imagen. Además, la etiqueta de imagen, el identificador de imagen, el tamaño y cuándo se creó están incluidos en la salida. Los pasos finales del 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: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"]

El FROM comando especifica la imagen base y la etiqueta que se van a usar. El WORKDIR comando cambia el directorio actual dentro del contenedor a App.

El COPY comando indica a Docker que copie el directorio de origen especificado en una carpeta de destino. En este ejemplo, el contenido de publicación de la build capa se genera en la carpeta denominada App/out, por lo que es el origen desde el que se va a copiar. Todos los contenidos publicados en el directorio App/out se copian en el directorio de trabajo actual (App).

El siguiente comando, ENTRYPOINT, indica a Docker que configure el contenedor para que se ejecute como un archivo ejecutable. Cuando se inicia el contenedor, se ejecuta el ENTRYPOINT comando . Cuando finaliza este comando, el contenedor se detiene automáticamente.

Sugerencia

Antes de .NET 8, los contenedores configurados para ejecutarse como de solo lectura podrían producir un error con Failed to create CoreCLR, HRESULT: 0x8007000E. Para solucionar este problema, especifique una DOTNET_EnableDiagnostics variable de entorno como 0 (justo antes del ENTRYPOINT paso):

ENV DOTNET_EnableDiagnostics=0

Para obtener más información sobre varias variables de entorno de .NET, consulte Variables de entorno de .NET.

Nota:

.NET 6 normaliza el prefijo DOTNET_ en lugar de 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 entorno de ejecución de .NET, debe seguir usando el prefijo COMPlus_ para las variables de entorno.

Creación de un contenedor

Ahora que tiene una imagen que contiene la aplicación, puede crear un contenedor. Puede crear un contenedor de dos maneras. 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 del docker create comando muestra el ID del contenedor (el identificador será diferente):

d0be06126f7db6dd1cee369d911262a353c9b7fb4829a0c11b4b2eb7b2d429cf

Para ver una lista de todos los contenedores, use el docker ps -a comando :

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 core-counterespecífico. Este nombre se usa para administrar el contenedor. En el ejemplo siguiente se usa el docker start comando para iniciar el contenedor y, a continuación, se usa el docker ps comando para mostrar solo los contenedores que se ejecutan:

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 docker stop comando detiene el contenedor. En el ejemplo siguiente se usa el docker stop comando para detener el contenedor y, a continuación, se usa el docker ps comando para mostrar que no hay contenedores en ejecución:

docker stop core-counter
core-counter

docker ps
CONTAINER ID    IMAGE    COMMAND    CREATED    STATUS    PORTS    NAMES

Conexión a un contenedor

Después de ejecutar un contenedor, puede conectarse a él para ver la salida. Use los docker start comandos y docker attach para iniciar el contenedor y examinar el flujo de salida. En este ejemplo, la pulsación de tecla Ctrl+C se usa para desasociar del contenedor en ejecución. Esta pulsación de tecla finaliza el proceso en el contenedor a menos que se especifique lo contrario, lo que detendría el contenedor. El --sig-proxy=false parámetro garantiza que Ctrl+C no detenga el proceso en el contenedor.

Después de desconectarse del contenedor, reconéctelo para verificar que todavía esté funcionando y 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

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 enumeran todos los contenedores. A continuación, usa el docker rm comando para eliminar el contenedor y, a continuación, comprueba una segunda vez si hay contenedores 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 docker run comando para crear y ejecutar el contenedor como un solo comando. Este comando elimina la necesidad de ejecutar docker create y, a continuación, docker start. También puede establecer este comando para eliminar automáticamente el contenedor cuando se detenga el contenedor. Por ejemplo, use docker run -it --rm para hacer dos cosas, en primer lugar, use automáticamente el terminal actual para conectarse al contenedor y, a continuación, cuando finalice el contenedor, 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 hasta tres, pase el número 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, que a su vez detiene el contenedor. Dado que se proporcionó el --rm parámetro , 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 docker run comando también le permite modificar el ENTRYPOINT comando desde dockerfile y ejecutar otra cosa, pero solo para ese contenedor. Por ejemplo, use el siguiente comando para ejecutar bash o cmd.exe. Edite el comando según sea necesario.

En este ejemplo, ENTRYPOINT se cambia a cmd.exe. Ctrl+C se presiona 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

Nota:

Este ejemplo solo funciona en contenedores de Windows. Los contenedores de Linux no tienen cmd.exe.

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 administrar los contenedores:

Limpieza de recursos

Durante este tutorial, ha creado contenedores e imágenes. Si lo desea, elimine estos recursos. Use los comandos siguientes para

  1. Enumerar todos los contenedores

    docker ps -a
    
  2. Detener los contenedores que están en ejecución por nombre

    docker stop core-counter
    
  3. Eliminación del contenedor

    docker rm core-counter
    

A continuación, elimine las imágenes que ya no desee 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 identificador de imagen o la cadena con formato REPOSITORY: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 el docker images comando para ver una lista de imágenes instaladas.

Sugerencia

Los archivos de imagen pueden ser grandes. Normalmente, quitaría los contenedores temporales que creó al probar y desarrollar la aplicación. Normalmente, las imágenes base se mantienen instaladas con el tiempo de ejecución si planea crear otras imágenes basadas en ese tiempo de ejecución.

Pasos siguientes