Personalización de contenedores de Docker en Visual Studio

Puede personalizar las imágenes de contenedor editando el Dockerfile que Visual Studio genera al agregar compatibilidad con Docker al proyecto. Tanto si va a compilar un contenedor personalizado desde el IDE de Visual Studio como si va a configurar una compilación de línea de comandos, debe conocer la forma en que Visual Studio usa el archivo Dockerfile para compilar los proyectos. Debe conocer estos detalles porque, por motivos de rendimiento, Visual Studio sigue un proceso especial para compilar y ejecutar aplicaciones en contenedor que no son obvias del Dockerfile.

Supongamos que desea realizar un cambio en el Dockerfile y ver los resultados en la depuración y en los contenedores de producción. En ese caso, puede agregar comandos en el Dockerfile para modificar la primera fase (normalmente, base). Consulte Modificación de la imagen de contenedor para la depuración y producción. Sin embargo, si desea realizar un cambio solo al depurar, pero no en producción, debe crear otra fase y usar la configuración de compilación DockerfileFastModeStage para indicar a Visual Studio que use esa fase para las compilaciones de depuración. Consulte Modificación de la imagen de contenedor solo para la depuración.

En este artículo se explica el proceso de compilación de Visual Studio para aplicaciones en contenedores con cierta detalle y, a continuación, contiene información sobre cómo modificar el Dockerfile para afectar a las compilaciones de depuración y producción, o solo a depuración.

Compilaciones de Dockerfile en Visual Studio

Nota:

En esta sección se describe el proceso de compilación del contenedor que usa Visual Studio al elegir el tipo de compilación de contenedor Dockerfile. Si usa el tipo de compilación del SDK de .NET, las opciones de personalización son diferentes y la información de esta sección no es aplicable. En su lugar, consulte Inclusión de una aplicación .NET en un contenedor mediante dotnet publish y use las propiedades descritas en Personalización del contenedor para configurar el proceso de compilación del contenedor.

Compilación de varias fases

Cuando Visual Studio compila un proyecto que no usa contenedores de Docker, invoca a MSBuild en el equipo local y genera los archivos de salida en una carpeta (normalmente bin) dentro de la carpeta de local de la solución. Sin embargo, en un proyecto en contenedores el proceso de compilación tiene en cuenta las instrucciones del archivo Dockerfile para compilar la aplicación en contenedores. El archivo Dockerfile que usa Visual Studio se divide en varias fases. Este proceso se basa en la característica de compilación de varias fases de Docker.

La característica de compilación de varias fases hace más eficaz el proceso de compilación de contenedores y hace que los contenedores sean más pequeños, lo que les permite contener solo los bits que la aplicación necesita en tiempo de ejecución. La compilación de varias fases se usa para los proyectos de .NET Core, no para los proyectos de .NET Framework.

La compilación de varias fases permite crear imágenes de contenedor en fases que producen imágenes intermedias. A modo de ejemplo, plantéese usar un Dockerfile típico. Se llama a base la primera fase en el Dockerfile que genera Visual Studio, aunque las herramientas no requieren ese nombre.

FROM mcr.microsoft.com/dotnet/aspnet:3.1-buster-slim AS base
WORKDIR /app
EXPOSE 80
EXPOSE 443

Las líneas en el Dockerfile comienzan con la imagen ASP.NET de Microsoft Container Registry (mcr.microsoft.com) y crean una imagen intermedia base que expone los puertos 80 y 443, y establece el directorio de trabajo en /app.

La siguiente fase es build, que se muestra de la siguiente manera:

FROM mcr.microsoft.com/dotnet/sdk:3.1-buster-slim AS build
WORKDIR /src
COPY ["WebApplication43/WebApplication43.csproj", "WebApplication43/"]
RUN dotnet restore "WebApplication43/WebApplication43.csproj"
COPY . .
WORKDIR "/src/WebApplication43"
RUN dotnet build "WebApplication43.csproj" -c Release -o /app/build

Puede ver que la fase build se inicia a partir de una imagen original diferente del registro (sdk, en vez de aspnet), en lugar de continuar desde la base. La imagen sdk tiene todas las herramientas de compilación y, por esa razón, es mucho más grande que la imagen ASPNET, que solo contiene componentes en tiempo de ejecución. El motivo por el que se usa una imagen independiente queda claro cuando se examina el resto del archivo Dockerfile:

FROM build AS publish
RUN dotnet publish "WebApplication43.csproj" -c Release -o /app/publish

FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "WebApplication43.dll"]

La fase final se inicia de nuevo desde base e incluye COPY --from=publish para copiar la salida publicada en la imagen final. Este proceso permite que la imagen final sea mucho más pequeña, ya que no tiene que incluir todas las herramientas de compilación que se encontraban en la imagen sdk.

MSBuild

Nota:

En esta sección se describe cómo personalizar los contenedores Docker al elegir el tipo de compilación de contenedor Dockerfile. Si usa el tipo de compilación del SDK de .NET, las opciones de personalización son diferentes y la información de este artículo no es aplicable. En su lugar, consulte Inclusión de una aplicación .NET en un contenedor mediante dotnet publish.

Los archivos Dockerfile creados por Visual Studio para proyectos de .NET Framework (y para proyectos de .NET Core creados con versiones de Visual Studio anteriores a Visual Studio 2017 Update 4) no son archivos Dockerfile de varias fases. Los pasos de estos archivos Dockerfile no compilan el código. En su lugar, cuando Visual Studio compila un archivo Dockerfile de .NET Framework, primero compila el proyecto con MSBuild. Cuando esto se realiza correctamente, Visual Studio compila el archivo Dockerfile, que simplemente copia la salida de la compilación de MSBuild en la imagen de Docker resultante. Dado que los pasos para compilar el código no se incluyen en el archivo Dockerfile, no se puede compilar archivos Dockerfile de .NET Framework con docker build desde la línea de comandos. Debe utilizar MSBuild para compilar estos proyectos.

Para compilar una imagen para un solo proyecto de contenedor de Docker, puede usar MSBuild con la opción de comando /t:ContainerBuild. Esto indica a MSBuild que compile el ContainerBuild de destino en lugar del Build de destino predeterminado. Por ejemplo:

MSBuild MyProject.csproj /t:ContainerBuild /p:Configuration=Release

Ve una salida similar a la que ve en la ventana Output cuando compila la solución desde el IDE de Visual Studio. Use siempre /p:Configuration=Release, ya que en los casos en los que Visual Studio usa la optimización de la compilación de varias fases, los resultados que se generan al compilar la configuración Debug podrían no ser los que se esperan. Vea Depuración.

Si usa un proyecto de Docker Compose, utilice el comando para compilar imágenes:

msbuild /p:SolutionPath=<solution-name>.sln /p:Configuration=Release docker-compose.dcproj

Depuración

Nota:

En esta sección se describe cómo personalizar los contenedores Docker al elegir el tipo de compilación de contenedor Dockerfile. Si usa el tipo de compilación del SDK de .NET, las opciones de personalización son diferentes y la mayoría de la información de esta sección no es aplicable. En su lugar, consulte Inclusión de una aplicación .NET en un contenedor mediante dotnet publish.

Cuando compila en la configuración Depuración, varias de las optimizaciones que realiza Visual Studio ayudan con el rendimiento del proceso de compilación para proyectos en contenedores. El proceso de compilación de aplicaciones en contenedores no es tan sencillo como seguir los pasos descritos en el Dockerfile. La compilación en un contenedor es mucho más lenta que la compilación en el equipo local. Por lo tanto, al compilar en la configuración Debug, Visual Studio compila los proyectos en el equipo local y, a continuación, comparte la carpeta de salida con el contenedor mediante el montaje del volumen. Una compilación con esta optimización habilitada se denomina compilación en modo rápido.

En el modo rápido, Visual Studio llama a docker build con un argumento que le indica a Docker que compile solo la fase base. Puede cambiarlo estableciendo la propiedad MSBuild, DockerfileFastModeStage, que se describe en Propiedades de MSBuild de herramientas de contenedor. Visual Studio controla el resto del proceso sin tener en cuenta el contenido de Dockerfile. Por lo tanto, cuando modifique el archivo Dockerfile, como para personalizar el entorno del contenedor o instalar dependencias adicionales, debe colocar las modificaciones en la primera fase. No se ejecutará ningún paso personalizado colocado en las fases build, publish o final del archivo Dockerfile.

Esta optimización del rendimiento solo se produce cuando compila en la configuración Debug. En la configuración Release, la compilación se produce en el contenedor tal y como se especifica en el archivo Dockerfile.

Si desea deshabilitar la optimización del rendimiento y compilar como se especifica en el archivo Dockerfile, establezca la propiedad ContainerDevelopmentMode en Regular en el archivo del proyecto de la siguiente manera:

<PropertyGroup>
   <ContainerDevelopmentMode>Regular</ContainerDevelopmentMode>
</PropertyGroup>

Para restaurar la optimización del rendimiento, quite la propiedad del archivo de proyecto.

Cuando se inicia la depuración (F5), se vuelve a usar un contenedor iniciado previamente, si es posible. Si no desea volver a usar el contenedor anterior, puede usar los comandos Rebuild o Clean en Visual Studio para obligar a Visual Studio a usar un nuevo contenedor.

El proceso de ejecución del depurador depende del tipo de proyecto y del sistema operativo del contenedor:

Escenario Proceso del depurador
Aplicaciones .NET Core (contenedores de Linux) Visual Studio descarga vsdbg y lo asigna al contenedor, después se llama con el programa y los argumentos (es decir, dotnet webapp.dll) y luego el depurador se asocia al proceso.
Aplicaciones .NET Core (contenedores de Windows) Visual Studio usa onecoremsvsmon y lo asigna al contenedor, lo ejecuta como punto de entrada y luego Visual Studio se conecta a él y se asocia al programa. Es similar a como normalmente se configuraría la depuración remota en otro equipo o máquina virtual.
Aplicaciones .NET Framework Visual Studio usa msvsmon y lo asigna al contenedor, lo ejecuta como parte del punto de entrada, donde Visual Studio se puede conectar a él, y se asocia al programa.

Para obtener información sobre vsdbg.exe, vea Depuración fuera de ruta de .NET Core en Linux y OS X desde Visual Studio.

Modificación de la imagen de contenedor para la depuración y producción

Para modificar la imagen de contenedor para la depuración y la producción, modifique la fase base. Agregue las personalizaciones al Dockerfile en la sección de fase base, normalmente la primera sección del Dockerfile. Consulte la referencia de Dockerfile en la documentación de Docker para obtener información sobre los comandos de Dockerfile.

FROM mcr.microsoft.com/dotnet/aspnet:6.0 AS base
WORKDIR /app
EXPOSE 80
EXPOSE 443
# <add your commands here>

FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build
WORKDIR /src
COPY ["WebApplication3/WebApplication3.csproj", "WebApplication3/"]
RUN dotnet restore "WebApplication3/WebApplication3.csproj"
COPY . .
WORKDIR "/src/WebApplication3"
RUN dotnet build "WebApplication3.csproj" -c Release -o /app/build

FROM build AS publish
RUN dotnet publish "WebApplication3.csproj" -c Release -o /app/publish

FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "WebApplication3.dll"]

Modificación de la imagen de contenedor solo para la depuración

Este escenario se aplica cuando desea hacer algo con los contenedores para ayudarle en el proceso de depuración, como la instalación de algo con fines de diagnóstico, pero no quiere que se instale en compilaciones de producción.

Para modificar el contenedor solo para la depuración, cree una fase y, a continuación, use la propiedad DockerfileFastModeStage de MSBuild para indicar a Visual Studio que use la fase personalizada al depurar. Consulte la referencia de Dockerfile en la documentación de Docker para obtener información sobre los comandos de Dockerfile.

En el ejemplo siguiente, instalamos el paquete procps-ng, pero solo en modo de depuración. Este paquete proporciona el comando pidof, que Visual Studio requiere, pero no está en la imagen de Mariner que se usa aquí. La fase que usamos para la depuración en modo rápido es debug, una fase personalizada definida aquí. La fase de modo rápido no necesita heredar de la fase build o publish, puede heredar directamente de la fase base, ya que Visual Studio monta un volumen que contiene todo lo necesario para ejecutar la aplicación, como se ha descrito anteriormente en este artículo.

#See https://aka.ms/containerfastmode to understand how Visual Studio uses this Dockerfile to build your images for faster debugging.

FROM mcr.microsoft.com/dotnet/aspnet:6.0-cbl-mariner2.0 AS base
WORKDIR /app
EXPOSE 80
EXPOSE 443

FROM base AS debug
RUN tdnf install procps-ng -y

FROM mcr.microsoft.com/dotnet/sdk:6.0-cbl-mariner2.0 AS build
WORKDIR /src
COPY ["WebApplication1/WebApplication1.csproj", "WebApplication1/"]
RUN dotnet restore "WebApplication1/WebApplication1.csproj"
COPY . .
WORKDIR "/src/WebApplication1"
RUN dotnet build "WebApplication1.csproj" -c Release -o /app/build

FROM build AS publish
RUN dotnet publish "WebApplication1.csproj" -c Release -o /app/publish

FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "WebApplication1.dll"]

En el archivo del proyecto, agregue esta configuración para indicar a Visual Studio que use la fase personalizada debug al depurar.

  <PropertyGroup>
     <!-- other property settings -->
     <DockerfileFastModeStage>debug</DockerfileFastModeStage>
  </PropertyGroup>

Las secciones siguientes contienen información que puede ser útil en determinados casos, como cuando desea especificar un punto de entrada diferente, o si la aplicación está habilitada para SSL y cambia algo que podría afectar a cómo se controlan los certificados SSL.

Compilar desde la línea de comandos

Si quiere compilar un proyecto de contenedor con un Dockerfile fuera de Visual Studio, puede usar docker build, MSBuild, dotnet build o dotnet publish para hacerlo desde la línea de comandos.

Si usa el tipo de compilación del SDK de .NET, no tiene un Dockerfile, por lo que no puede usar docker build; en su lugar, use MSBuild, dotnet build o dotnet publish para compilar en la línea de comandos.

Uso de una compilación de Docker

Para compilar una solución en contenedores desde la línea de comandos, normalmente puede usar el comando docker build <context> para cada proyecto de la solución. Proporcione el argumento build context. El contexto de compilación para un archivo Dockerfile es la carpeta del equipo local que se usa como carpeta de trabajo para generar la imagen. Por ejemplo, es la carpeta desde la que se copian los archivos cuando copia en el contenedor. En los proyectos de .NET Core, use la carpeta que contiene el archivo de solución (.sln). Expresado como una ruta de acceso relativa, este argumento suele ser ".." para un archivo Dockerfile en una carpeta del proyecto y el archivo de solución en su carpeta principal. En el caso de los proyectos de .NET Framework, el contexto de compilación es la carpeta del proyecto, no la carpeta de la solución.

docker build -f Dockerfile ..

Preparación del proyecto

La preparación del proyecto se refiere a una serie de pasos que se producen cuando se selecciona el perfil de Docker de un proyecto (es decir, cuando se carga un proyecto o se agrega compatibilidad con Docker) con el fin de mejorar el rendimiento de las ejecuciones posteriores (F5 o Ctrl+F5). Este comportamiento se puede configurar en Herramientas>Opciones>Herramientas de contenedor. Estas son las tareas que se ejecutan en segundo plano:

  • Comprobar que Docker Desktop está instalado y en ejecución.
  • Asegurarse de que Docker Desktop está establecido en el mismo sistema operativo que el proyecto.
  • Extraer las imágenes de la primera fase del Dockerfile (la fase base en la mayoría de Dockerfiles).
  • Compilar el Dockerfile e iniciar el contenedor.

La preparación solo se produce en el modo Rápido, por lo que el contenedor en ejecución tiene el volumen de la carpeta app montado. Esto significa que cualquier cambio en la aplicación no debe invalidar el contenedor. Este comportamiento mejora considerablemente el rendimiento de depuración y reduce el tiempo de espera de las tareas de larga duración, como la extracción de imágenes de gran tamaño.

Asignación de volúmenes

Para que la depuración funcione en contenedores, Visual Studio usa la asignación de volúmenes a fin de asignar el depurador y las carpetas de NuGet desde el equipo host. La asignación de volúmenes se describe en la documentación de Docker aquí. Puede ver las asignaciones de volúmenes de un contenedor mediante la ventana Contenedores de Visual Studio.

Estos son los volúmenes que se montan en el contenedor:

Volumen Descripción
Carpeta de la aplicación Contiene la carpeta del proyecto donde se encuentra el Dockerfile.
Carpetas de paquetes NuGet Contienen los paquetes NuGet y las carpetas de reserva que se leen desde el archivo obj{project}.csproj.nuget.g.props del proyecto.
Depurador remoto Contiene los bits necesarios para ejecutar el depurador en el contenedor en función del tipo de proyecto. Consulte la sección Depuración.
Carpeta de origen Contiene el contexto de compilación que se pasa a los comandos de Docker.

Estos son los volúmenes que se montan en el contenedor. Lo que se ve en los contenedores podría diferir en función de la versión secundaria de Visual Studio 2022 que use.

Volumen Descripción
Carpeta de la aplicación Contiene la carpeta del proyecto donde se encuentra el Dockerfile.
HotReloadAgent Contiene los archivos del agente de recarga frecuente.
HotReloadProxy Contiene los archivos necesarios para ejecutar un servicio que permita al agente de recarga del host comunicarse con Visual Studio en el host.
Carpetas de paquetes NuGet Contienen los paquetes NuGet y las carpetas de reserva que se leen desde el archivo obj{project}.csproj.nuget.g.props del proyecto.
Depurador remoto Contiene los bits necesarios para ejecutar el depurador en el contenedor en función del tipo de proyecto. Esto se explica con más detalle en la sección Depuración.
Carpeta de origen Contiene el contexto de compilación que se pasa a los comandos de Docker.
TokenService.Proxy Contiene los archivos necesarios para ejecutar un servicio que permita a VisualStudioCredential comunicarse con Visual Studio en el host.

En .NET 8, también podrían haber puntos de montaje adicionales en la raíz y para el usuario de la aplicación que incluyan secretos de usuario y el certificado HTTPS. En la versión preliminar de Visual Studio 17.10, el volumen Recarga activa y Servicio de token, junto con otro componente, el asistente sin distribución, se combinan en un único punto de montaje, /VSTools.

Nota:

Versión preliminar 17.10 de Visual Studio Si utiliza Docker Engine en Windows Subsystem for Linux (WSL) sin Docker Desktop, configure la variable de entorno VSCT_WslDaemon=1 para que Visual Studio utilice rutas WSL al crear montajes de volumen. El paquete NuGet Microsoft.VisualStudio.Azure.Containers.Tools.Targets 1.20.0-Preview 1 también es necesario.

En el caso de las aplicaciones web ASP.NET Core, puede haber dos carpetas adicionales para el certificado SSL y los secretos de usuario, lo que se explica con más detalle en la sección siguiente.

Habilitación de registros detallados de herramientas de contenedor

Para fines de diagnóstico, puede habilitar determinados registros de herramientas de contenedor. Para habilitar estos registros, puede establecer determinadas variables de entorno. Para proyectos de contenedor únicos, la variable de entorno es MS_VS_CONTAINERS_TOOLS_LOGGING_ENABLED, que, a continuación, registra en %tmp%\Microsoft.VisualStudio.Containers.Tools. En el caso de proyectos de Docker Compose, es MS_VS_DOCKER_TOOLS_LOGGING_ENABLED, que después se registra en %tmp%\Microsoft.VisualStudio.DockerCompose.Tools.

Precaución

Cuando el registro está habilitado y usa un proxy de token para la autenticación de Azure, las credenciales de autenticación se podrían registrar como texto sin formato. Consulte Configurar la autenticación de Azure.

Punto de entrada de contenedor

Visual Studio usa un punto de entrada de contenedor personalizado en función del tipo de proyecto y del sistema operativo del contenedor; estas son las diferentes combinaciones:

Tipo de contenedor Punto de entrada
Contenedores de Linux El punto de entrada es tail -f /dev/null, que es una espera infinita para mantener el contenedor en ejecución. Cuando la aplicación se inicia a través del depurador, este es el responsable de ejecutar la aplicación (es decir, dotnet webapp.dll). Si se inicia sin depurar, las herramientas ejecutan docker exec -i {containerId} dotnet webapp.dll para ejecutar la aplicación.
Contenedores de Windows El punto de entrada es algo como C:\remote_debugger\x64\msvsmon.exe /noauth /anyuser /silent /nostatus, que ejecuta el depurador, así que escucha para detectar conexiones. Este método se aplica cuando el depurador ejecuta la aplicación. Cuando se inicia sin depurar, se usa un comando docker exec. En aplicaciones web .NET Framework, el punto de entrada es ligeramente diferente donde se agrega ServiceMonitor al comando.

El punto de entrada de contenedor solo puede modificarse en proyectos de Docker Compose, no en proyectos de un solo contenedor.

Aplicaciones ASP.NET Core habilitadas para SSL

Las herramientas de contenedor de Visual Studio admiten la depuración de una aplicación ASP.NET Core habilitada para SSL con un certificado de desarrollo, de la misma manera que cabría esperar que funcionase sin contenedores. Para que esto suceda, Visual Studio agrega un par de pasos más para exportar el certificado y ponerlo a disposición del contenedor. Este es el flujo que Visual Studio controla automáticamente durante la depuración en el contenedor:

  1. Garantiza que el certificado de desarrollo local esté presente y sea de confianza en el equipo host mediante la herramienta dev-certs.

  2. Exporta el certificado a %APPDATA%\ASP.NET\Https con una contraseña segura que esté almacenada en el almacén de secretos de usuario de esta aplicación en concreto.

  3. Monta el volumen en los directorios siguientes:

    • *%APPDATA%\Microsoft\UserSecrets
    • *%APPDATA%\ASP.NET\Https

ASP.NET Core busca un certificado que coincida con el nombre de ensamblado de la carpeta Https, que es el motivo por el que se asigna al contenedor en esa ruta de acceso. La ruta de acceso del certificado y la contraseña pueden definirse alternativamente mediante variables de entorno (es decir, ASPNETCORE_Kestrel__Certificates__Default__Path y ASPNETCORE_Kestrel__Certificates__Default__Password) o en el archivo json de secretos de usuario, por ejemplo:

{
  "Kestrel": {
    "Certificates": {
      "Default": {
        "Path": "c:\\app\\mycert.pfx",
        "Password": "strongpassword"
      }
    }
  }
}

Si la configuración admite tanto compilaciones ubicadas en contenedores como compilaciones que no lo están, debe usar las variables del entorno, ya que las rutas de acceso son específicas del entorno de contenedor.

Para obtener más información sobre el uso de SSL con aplicaciones ASP.NET Core en contenedores, vea Hospedaje de imágenes de ASP.NET Core con Docker a través de HTTPS.

Para ver un ejemplo de código que muestra cómo crear certificados personalizados para una aplicación de varios servicios que son de confianza en el host y en los contenedores para la comunicación entre servicios HTTPS, consulte CertExample.