Ha pasado bastante tiempo desde nuestra receta navideña con Docker. Y han habido muchos e importantes cambios en ASP.NET Core RT:

Vamos a hacer una actualización de este paso a paso para crear y ejecutar una aplicación ASP.NET Core 1.0 RTM en Docker.

¿Te apuntas?

Ingredientes

Para esta receta vamos a necesitar

  • .NET Core SDK Installer (Preview 2)
  • HomeBrew (si eres usuario de OS X)
  • NPM (brew install npm , para OS X. Los usuarios de Windows pueden instalar NPM como parte de Node.JS)
  • Bower (npm install -g bower)
  • Yeoman (npm install -g yo)
  • ASP.NET Generators para Yeoman (npm install generator-aspnet)

Paso 0: Configurando Docker

Primero que nada, vas a necesitar configurar Docker para MacWindows or Linux

Una vez instalado, ¿tengo alguna VM de Docker instalada? Comprobemoslo:

$ docker-machine ls NAME ACTIVE DRIVER STATE URL SWARM DOCKER ERRORS innotech * parallels Running tcp://10.211.55.27:2376 v1.11.1

innotech es el nombre de mi máquina Docker. En tu caso, Docker Toolbox seguramente haya creado una que se llama “Default”:

¿No funciona docker-machine? Arranquemos  la VM y evaluemos las variables de entorno para configurar la conexión:

$ docker-machine start innotech Starting "innotech"... (innotech) Waiting for VM to start... Machine "innotech" was started. Waiting for SSH to be available... Detecting the provisioner... Started machines may have new IP addresses. You may need to re-run the docker-machine env command. $ eval $(docker-machine env innotech)

Y comprobemos que Docker está operativo, examinando la información:

$ docker info Containers: 0 Running: 0 Paused: 0 Stopped: 0 Images: 317 Server Version: 1.11.1 Storage Driver: aufs Root Dir: /mnt/sda1/var/lib/docker/aufs Backing Filesystem: extfs Dirs: 133 Dirperm1 Supported: true Logging Driver: json-file Plugins: Volume: local Network: null host bridge Kernel Version: 4.4.8-boot2docker Operating System: Boot2Docker 1.11.1 (TCL 7.0); HEAD : 7954f54 - Wed Apr 27 16:36:45 UTC 2016 OSType: linux Architecture: x86_64 CPUs: 1 Total Memory: 994.9 MiB Name: innotech ID: Debug mode (server): true File Descriptors: 25 Goroutines: 45 System Time: 2016-05-31T21:31:34.598910446Z EventsListeners: 0 Init SHA1: Init Path: Docker Root Dir: /mnt/sda1/var/lib/docker Labels: provider=parallels

Paso 1: Crear una aplicación ASP.NET Core 1.0 con Yeoman

Vamos a crear una aplicación ASP.NET Core con Yeoman. Nuevamente, desde nuestro terminal/consola:

$ yo aspnet ----- | | .--------------------------. |--(o)--| | Welcome to the | ---------´ | marvellous ASP.NET Core | ( _´U_ ) | 1.0 generator! | /A\ '--------------------------' | ~ | '..'_ ´ |° ´ Y ? What type of application do you want to create? Web Application Basic [without Membership and Authorization] ? Which UI framework would you like to use? Bootstrap (3.3.6) ? What's the name of your ASP.NET application? WebApplicationBasic create WebApplicationBasic/gulpfile.js create WebApplicationBasic/Dockerfile create WebApplicationBasic/.bowerrc create WebApplicationBasic/.gitignore create WebApplicationBasic/bower.json create WebApplicationBasic/appsettings.json create WebApplicationBasic/package.json create WebApplicationBasic/project.json create WebApplicationBasic/Program.cs create WebApplicationBasic/Properties/launchSettings.json create WebApplicationBasic/README.md create WebApplicationBasic/Startup.cs create WebApplicationBasic/web.config create WebApplicationBasic/Controllers/HomeController.cs create WebApplicationBasic/Views/_ViewImports.cshtml create WebApplicationBasic/Views/_ViewStart.cshtml create WebApplicationBasic/Views/Home/About.cshtml create WebApplicationBasic/Views/Home/Contact.cshtml create WebApplicationBasic/Views/Home/Index.cshtml create WebApplicationBasic/Views/Shared/_Layout.cshtml create WebApplicationBasic/Views/Shared/Error.cshtml create WebApplicationBasic/wwwroot/css/site.css create WebApplicationBasic/wwwroot/css/site.min.css create WebApplicationBasic/wwwroot/favicon.ico create WebApplicationBasic/wwwroot/images/banner1.svg create WebApplicationBasic/wwwroot/images/banner2.svg create WebApplicationBasic/wwwroot/images/banner3.svg create WebApplicationBasic/wwwroot/images/banner4.svg create WebApplicationBasic/wwwroot/js/site.js create WebApplicationBasic/wwwroot/js/site.min.js I'm all done. Running npm install & bower install for you to install the required dependencies. If this fails, try running the command yourself. Your project is now created, you can use the following commands to get going cd "WebApplicationBasic" dotnet restore dotnet build (optional, build will also happen when it's run) dotnet run

Restauramos y compilamos el proyecto:

$ cd WebApplicationBasic/ $ dotnet restore log : Restoring packages for /Users/Sergio/Repositories/dotnet/WebApplicationBasic/project.json... info : GET https://api.nuget.org/v3-flatcontainer/microsoft.netcore.dotnethostresolver/index.json info : OK https://api.nuget.org/v3-flatcontainer/microsoft.netcore.dotnethostresolver/index.json 554ms info : GET https://api.nuget.org/v3-flatcontainer/microsoft.netcore.dotnethost/index.json info : OK https://api.nuget.org/v3-flatcontainer/microsoft.netcore.dotnethost/index.json 582ms log : Restoring packages for tool 'Microsoft.AspNetCore.Razor.Tools' in /Users/Sergio/Repositories/dotnet/WebApplicationBasic/project.json... log : Restoring packages for tool 'Microsoft.AspNetCore.Server.IISIntegration.Tools' in /Users/Sergio/Repositories/dotnet/WebApplicationBasic/project.json... info : Committing restore... log : Writing lock file to disk. Path: /Users/Sergio/Repositories/dotnet/WebApplicationBasic/project.lock.json log : /Users/Sergio/Repositories/dotnet/WebApplicationBasic/project.json log : Restore completed in 5086ms. NuGet Config files used: /Users/Sergio/.nuget/NuGet/NuGet.Config Feeds used: https://api.nuget.org/v3/index.json $ dot net build Project WebApplicationBasic (.NETCoreApp,Version=v1.0) will be compiled because expected outputs are missing Compiling WebApplicationBasic for .NETCoreApp,Version=v1.0 Compilation succeeded. 0 Warning(s) 0 Error(s) Time elapsed 00:00:04.9675153

Arreglando algunos problemas con yo aspnet

ACTUALIZACIÓN: Este pull request solucionó el problema. Si tienes la última versión de los generadores ASP.NET para Yeoman (yo install generator-aspnet -g) puedes obviar este paso.

La versión actual (ASP.NET Core 1.0 RC-2) de los generadores contienen una versión desactualizada del Dockerfile, que solo funciona con versiones anteriores. Vamos a actualizar el fichero:

Primero de nada, vamos a crear el archivo Dockerile. Con Visual Studio Code (o tu editor preferido) abre el archivo “Dockerfile” que encontrarás en la raíz del proyecto que acabamos de crear con Yeoman, e incluye el siguiente contenido:

FROM microsoft/dotnet:latest COPY . /app WORKDIR /app RUN ["dotnet", "restore"] RUN ["dotnet", "build"] EXPOSE 5000/tcp ENTRYPOINT ["dotnet", "run", "--server.urls", "http://*:5000"]

Con este Dockerfile, estamos indicando a Docker que debe utilizar la última imagen del container de DotNet, copiar el contenido de nuestro proyecto dentro, restaurar los paquetes y compilar la el proyecto dentro del container, en tiempo de creación de la imagen. Nuestro servidor Kestrel escuchará, por defecto, en el puerto TCP 5000. Como ocurría en la versión RC 1 de ASP.NET, necesitamos formar el puerto en y las   IPs desde la que se va a aceptar tráfico entrante, con el argument –server.urls

Incluir el argument –server.urls es vital para permitir tráfico entrante desde fuera del container. De lo contrario, Kestrel rechaza cualquier conexión que no provenga del propio Container, lo cual es algo poco útil…

Ahora, abramos el archivo project.json y añadamos el paquete Nuget “Microsoft.Extensions.Configuration.CommandLine”:

Project.json: { "dependencies": { "Microsoft.NETCore.App": { "version": "1.0.0", "type": "platform" }, "Microsoft.AspNetCore.Diagnostics": "1.0.0", "Microsoft.AspNetCore.Mvc": "1.0.0-rc2-final", "Microsoft.AspNetCore.Razor.Tools": { "version": "1.0.0-preview2-final", "type": "build" }, "Microsoft.AspNetCore.Server.IISIntegration": "1.0.0", "Microsoft.AspNetCore.Server.Kestrel": "1.0.0", "Microsoft.AspNetCore.StaticFiles": "1.0.0", "Microsoft.Extensions.Configuration.EnvironmentVariables": "1.0.0", "Microsoft.Extensions.Configuration.Json": "1.0.0", "Microsoft.Extensions.Configuration.CommandLine": "1.0.0", "Microsoft.Extensions.Logging": "1.0.0", "Microsoft.Extensions.Logging.Console": "1.0.0", "Microsoft.Extensions.Logging.Debug": "1.0.0", "Microsoft.VisualStudio.Web.BrowserLink.Loader": "14.0.0" },

Y, desde el Terminal/Consola ejecuta:

$ dotnet restore log : Restoring packages for /Users/Sergio/Repositories/blog/app/dotnet/project.json... info : GET https://api.nuget.org/v3-flatcontainer/microsoft.netcore.dotnethostresolver/index.json info : OK https://api.nuget.org/v3-flatcontainer/microsoft.netcore.dotnethostresolver/index.json 519ms info : GET https://api.nuget.org/v3-flatcontainer/microsoft.netcore.dotnethost/index.json info : OK https://api.nuget.org/v3-flatcontainer/microsoft.netcore.dotnethost/index.json 538ms log : Restoring packages for tool 'Microsoft.AspNetCore.Razor.Tools' in /Users/Sergio/Repositories/blog/app/dotnet/project.json... log : Restoring packages for tool 'Microsoft.AspNetCore.Server.IISIntegration.Tools' in /Users/Sergio/Repositories/blog/app/dotnet/project.json... info : Committing restore... log : Writing lock file to disk. Path: /Users/Sergio/Repositories/blog/app/dotnet/project.lock.json log : /Users/Sergio/Repositories/blog/app/dotnet/project.json log : Restore completed in 4305ms. NuGet Config files used: /Users/Sergio/.nuget/NuGet/NuGet.Config Feeds used: https://api.nuget.org/v3/index.json

Y modificamos el archivo Program.cs para leer la configuración desde los argumentos de entrada del programa:

using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; namespace WebApplicationBasic { public class Program { public static void Main(string[] args) { var config = new ConfigurationBuilder() .AddCommandLine(args) .AddEnvironmentVariables(prefix: "ASPNETCORE_") .Build(); var host = new WebHostBuilder() .UseConfiguration(config) .UseKestrel() .UseContentRoot(Directory.GetCurrentDirectory()) .UseIISIntegration() .UseStartup() .Build(); host.Run(); } } }

¡Ya estamos casi! Es hora de compilar y ejecutar nuestro container

Compilar y ejecutar

Y ahora, tiempo para Docker. Volvamos a nuestro Terminal y ejecutemos el comando para crear el Container:

$ docker build -t sesispla/aspnet-app . Sending build context to Docker daemon 12.11 MB Step 1 : FROM microsoft/dotnet latest: Pulling from microsoft/dotnet 51f5c6a04d83: Pull complete a3ed95caeb02: Pull complete 7004cfc6e122: Pull complete 5f37c8a7cfbd: Pull complete f04c717aadf7: Pull complete 9e4f1a99b45d: Pull complete Digest: sha256:294140fd6a4222f7c51b555c4b3eca2f4194f6e51995d32e770c878f6034a1fd Status: Downloaded newer image for microsoft/dotnet:latest ---> f315e6bbb53f Step 2 : RUN printf "deb http://ftp.us.debian.org/debian jessie main\n" >> /etc/apt/sources.list ---> Running in bf82c67b1fe0 ---> 655f0a9ef1c3 Removing intermediate container bf82c67b1fe0 Step 3 : COPY . /app ---> c4b9601cd652 Removing intermediate container 292641b90f7a Step 4 : WORKDIR /app ---> Running in ba4108b8f087 ---> 9396567ac9fb Removing intermediate container ba4108b8f087 Step 5 : RUN dotnet restore ---> Running in 6b273c80aa7a log : Restoring packages for /app/project.json... info : GET https://api.nuget.org/v3-flatcontainer/microsoft.aspnetcore.diagnostics/index.json info : GET https://api.nuget.org/v3-flatcontainer/microsoft.netcore.app/index.json ... info : Committing restore... log : Lock file has not changed. Skipping lock file write. Path: /app/project.lock.json log : /app/project.json log : Restore completed in 140503ms. NuGet Config files used: /root/.nuget/NuGet/NuGet.Config Feeds used: https://api.nuget.org/v3/index.json Installed: 209 package(s) to /app/project.json ---> 4063190181dc Removing intermediate container 6b273c80aa7a Step 6 : RUN dotnet build ---> Running in 73d7cd64dfda Project app (.NETCoreApp,Version=v1.0) will be compiled because the version or bitness of the CLI changed since the last build Compiling app for .NETCoreApp,Version=v1.0 Compilation succeeded. 0 Warning(s) 0 Error(s) Time elapsed 00:00:07.0382804 ---> fac72bc031a8 Removing intermediate container 73d7cd64dfda Step 7 : EXPOSE 5000/tcp ---> Running in c0fe05045b11 ---> 046911677efe Removing intermediate container c0fe05045b11 Step 8 : ENTRYPOINT dotnet run --server.urls=http://*:5000 ---> Running in e4326e985746 ---> 0d56db4df620 Removing intermediate container e4326e985746 Successfully built 0d56db4df620

Ahora, nuestra imagen de Container está lista para ser desplegada. Comprobemos que está en el listado de imágenes locales:

$ docker images REPOSITORY TAG IMAGE ID CREATED SIZE sesispla/aspnet-app latest 0d56db4df620 About a minute ago 1.177 GB microsoft/dotnet latest f315e6bbb53f 4 hours ago 641.1 MB

Para ejecutar una instancia de la imagen:

docker run -d -p 90:5000 sesispla/aspnet-app e8cbaa2fee3ffe25001903c955dab0513e9cec5c45b6722613f53ac0de1cd475

Para comprobar que el Container se está ejecutando:

Sergios-MacBook-Pro:WebApplicationBasic Sergio$ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES e8cbaa2fee3f sesispla/aspnet-app "dotnet run --server." 8 seconds ago Up 7 seconds 0.0.0.0:90->5000/tcp evil_mestorf

Y… finalmente, hora del navegador

http://default:90 (o el nombre de tu docker machine, seguido del puerto :90)

container-1

¡Y aquí está! Tu aplicación ASP.NET Core 1.0 ejecutándose en Docker

Cambios respecto a RC1

Uno de los cambios más significativos desde la versión RC1 de .NET Core, es que ahora todas las aplicaciones, incluye las aplicaciones ASP.NET Core, son aplicaciones de Consola. Si damos un vistazo al proyecto generador yo aspnet, verás que ahora hay un program.cs:

using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Hosting; namespace WebApplicationBasic { public class Program { public static void Main(string[] args) { var host = new WebHostBuilder() .UseKestrel() .UseContentRoot(Directory.GetCurrentDirectory()) .UseIISIntegration() .UseStartup() .Build(); host.Run(); } } }

Este fichero tiene un punto de entrada, Main, donde se realiza la inicialización del WebHostBuilder (con Kestrel) dentro.

Así que… ahora puedes Docker cualquier aplicación .NET Core siguiendo este tutorial.

Docker bonus tracks

Bonus 1: ¿Quieres ejecutar más Containers con esta misma imagen? ¡Fácil!

docker run -d -p 91:5000 --name="app2" sesispla/aspnet-app 6ea122317745493fc184956c4cd6da6745e44672f38e2c428260261b12458e74 docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 6ea122317745 sesispla/aspnet-app "dotnet run --server." About an hour ago Up 2 seconds 0.0.0.0:91->5000/tcp app1 096d63f03b94 sesispla/aspnet-app "dotnet run --server." About an hour ago Up 16 minutes 0.0.0.0:90->5000/tcp app2

Abre el puerto 91 en tu navegador y listos (Por ejemplo, http://default:91):

container-2

Bonus 2: ¿Arrancar y parar containers existentes? Aquí tienes los comandos (ojo, que estoy utilizando el container name, que puedes sacar del Bonus 3):

docker stop app1 app1 docker stop app2 app2 docker start app1 app1

Bonus 3: Listar todos los Containers (incluidos aquellos que están parados)

docker ps -a CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 6ea122317745 sesispla/dnxapp "dnx -p project.json " 2 hours ago Exited (137) 31 minutes ago dnxapp2 096d63f03b94 sesispla/dnxapp "dnx -p project.json " 2 hours ago Up 31 minutes 0.0.0.0:90->5000/tcp dnxapp1 Sergios-MacBook-Pro:DockerizedDotNet Sergio$

Preguntas frecuentes

A: ¿Tengo que crear una imagen del container cada vez que cambio el código fuente?
Q: Sí.

A: ¿Puedo utilizar volúmenes de Docker para referencer el código fuente en mi máquina física, en lugar de generar imagenes cada vez?
Q: Sí. Pero, ¿porqué no utilizar tu propia máquina para depurar y realia una primera prueba en local, y realizar las pruebas finales con Docker? But why not to use your own machine for debugging and testing, and leave Docker images for the final tests?


Bienvenido al fascinando mundo de los Containers