ASP.NET Core 2.0 in Docker

Authored by

TL;DR In this tutorial, you will learn how to create a Docker image with an ASP.NET Core 2.0 application and how to run your application on any Docker host.

Inspiration

I was inspired by the following David Fowler's tweet :

Disclaimer

I was looking for a blog post how to create a Docker image with an ASP.NET Core 2.0 application. It was hard to find a blog post that covers the baby steps how to do it. Fortunately, I find a video of Mark Rendle doing a lab of ASP.NET Core 2.0 in Docker where he explains exactly what I was looking for! This blog post is based on Mark Rendle's video but the demo source is my own. This is absolutely my personal experience about what I learned from the lab video. I learned a lot.

Prerequisites

Before jumping in this tutorial, the following tools should be installed first :

Docker Hub

Docker Hub is a cloud-based registry service which allows you to link to code repositories, build your images and test them, stores manually pushed images, and links to Docker Cloud so you can deploy images to your hosts.

It provides a centralized resource for container image discovery, distribution and change management, user and team collaboration, and workflow automation throughout the development pipeline.

.NET Core 2.0 in Docker

First, we need to go to Docker Hub in order to grab Docker images. Let's start pulling a dotnet 2.0 SDK image from Docker Hub using the following Docker CLI :

docker pull microsoft/dotnet:2.0.0-sdk
C:\Labs\DockerDemo
λ docker pull microsoft/dotnet:2.0.0-sdk
2.0.0-sdk: Pulling from microsoft/dotnet
3e17c6eae66c: Pull complete
74d44b20f851: Pull complete
a156217f3fa4: Pull complete
4a1ed13b6faa: Pull complete
18842ff6b0bf: Pull complete
e857bd06f538: Pull complete
b800e4c6f9e9: Pull complete
Digest: sha256:f4ea9cdf980bb9512523a3fb88e30f2b83cce4b0cddd2972bc36685461081e2f
Status: Downloaded newer image for microsoft/dotnet:2.0.0-sdk
docker run -ti microsoft/dotnet:2.0.0-sdk
C:\Labs\DockerDemo
λ docker run -ti microsoft/dotnet:2.0.0-sdk
root@ee5263bb86ca:/# ls -altr
total 72
drwxr-xr-x   2 root root 4096 Jul 13 13:04 home
drwxr-xr-x   2 root root 4096 Jul 13 13:04 boot
drwxr-xr-x   2 root root 4096 Oct  9 00:00 srv
drwxr-xr-x   4 root root 4096 Oct  9 00:00 run
drwxr-xr-x   2 root root 4096 Oct  9 00:00 opt
drwxr-xr-x   2 root root 4096 Oct  9 00:00 mnt
drwxr-xr-x   2 root root 4096 Oct  9 00:00 media
drwxr-xr-x   2 root root 4096 Oct  9 00:00 lib64
drwxr-xr-x   1 root root 4096 Oct  9 00:00 lib
drwxr-xr-x   1 root root 4096 Oct  9 22:37 sbin
drwxr-xr-x   1 root root 4096 Oct  9 22:37 bin
drwxr-xr-x   1 root root 4096 Oct 10 09:07 var
drwxr-xr-x   1 root root 4096 Oct 10 09:08 usr
drwxrwxrwt   1 root root 4096 Oct 10 09:08 tmp
drwx------   1 root root 4096 Oct 10 09:08 root
drwxr-xr-x   1 root root 4096 Oct 23 09:00 etc
-rwxr-xr-x   1 root root    0 Oct 23 09:00 .dockerenv
drwxr-xr-x   1 root root 4096 Oct 23 09:00 ..
drwxr-xr-x   1 root root 4096 Oct 23 09:00 .
dr-xr-xr-x 119 root root    0 Oct 23 09:00 proc
dr-xr-xr-x  13 root root    0 Oct 23 09:00 sys
drwxr-xr-x   5 root root  360 Oct 23 09:00 dev
root@ee5263bb86ca:/#

Yes, it is absolutely a real Unix system! By the way, the Unix operating system featured in Jurassic Park movie is real as well. It was a Silicon Graphics workstation (using IRIX, the SGI System V based Unix) running a three dimensional file system browser.

Now let's do a Clear and change directory to /tmp. Make sure the folder is empty using ls -l.
Then, create a new console app. Let's see the files created and do a dotnet run to run the app.

root@ee5263bb86ca:/# cd /tmp                                   
root@ee5263bb86ca:/tmp# ls -l                                  
total 0                                                        
root@ee5263bb86ca:/tmp# dotnet new console                     
The template "Console Application" was created successfully.   

Processing post-creation actions...                            
Running 'dotnet restore' on /tmp/tmp.csproj...                 
  Restoring packages for /tmp/tmp.csproj...                    
  Generating MSBuild file /tmp/obj/tmp.csproj.nuget.g.props.   
  Generating MSBuild file /tmp/obj/tmp.csproj.nuget.g.targets. 
  Restore completed in 179.4 ms for /tmp/tmp.csproj.           


Restore succeeded.                                             

root@ee5263bb86ca:/tmp# ls -l                                  
total 16                                                       
drwxrwxrwx 3 root root 4096 Oct 23 09:05 NuGetScratch          
-rw-r--r-- 1 root root  185 Oct 23 09:05 Program.cs            
drwxr-xr-x 2 root root 4096 Oct 23 09:05 obj                   
-rw-r--r-- 1 root root  178 Oct 23 09:05 tmp.csproj            
root@ee5263bb86ca:/tmp# dotnet run                             
Hello World!                                                   
root@ee5263bb86ca:/tmp#

The main goal of this tutorial is to focus on AS.NET Core 2.0 in Docker and not .NET Core 2.0 in Docker. But it's interesting to understand how things work for .NET Core 2.0 since ASP.NET Core 2.0 is built on top of .NET Core 2.0 :

ASP.NET Core 2.0 in Docker

Two Docker images are required to create a Docker image for an ASP.NET Core 2.0 application : microsoft/aspnetcore-build and microsoft/aspnetcore .

docker pull microsoft/aspnetcore:2.0.0
C:\Labs\DockerDemo
λ docker pull microsoft/aspnetcore:2.0.0
2.0.0: Pulling from microsoft/aspnetcore
3e17c6eae66c: Already exists
75c1df3f7fe9: Pull complete
8daaf8f1aeef: Pull complete
f864005f62f4: Pull complete
b0699e66d0f1: Pull complete
Digest: sha256:d65c94c63919998d7f4a4543bb1b333d7e60b9ba1cff31ba08bc5b04960b38d8
Status: Downloaded newer image for microsoft/aspnetcore:2.0.0
docker pull microsoft/aspnetcore-build:2.0.0
C:\Labs\DockerDemo
λ docker pull microsoft/aspnetcore-build:2.0.0
2.0.0: Pulling from microsoft/aspnetcore-build
219d2e45b4af: Pull complete
ef9ce992ffe4: Pull complete
d0df8518230c: Pull complete
38ae21afde7b: Pull complete
d8597196fae8: Pull complete
01a7bdcfe48a: Pull complete
a9e767050d45: Pull complete
ea42cd37a9e6: Pull complete
7dee185f8b08: Pull complete
7b92c484cf6e: Pull complete
119f30831f89: Pull complete
80709d8221bf: Pull complete
Digest: sha256:ddd1a73e1c069b6de351ef1ba5a3ce197fceef10e880ac93c8b5332e1d8cacfa
Status: Downloaded newer image for microsoft/aspnetcore-build:2.0.0

docker images shows all the images on your disk.

C:\Labs\DockerDemo
λ docker images
REPOSITORY                   TAG                 IMAGE ID            CREATED             SIZE
microsoft/aspnetcore         2.0.0               3a33cbde7608        3 days ago          280MB
microsoft/dotnet             2.0.0-sdk           fde8197d13f4        13 days ago         1.64GB
microsoft/aspnetcore-build   2.0.0               c5549d4c5716        2 weeks ago         1.86GB
dotnet new web -o src/DockerDemo
C:\Labs\DockerDemo
λ dotnet new web -o src/DockerDemo
Le modèle 'ASP.NET Core Empty' a été créé.
Ce modèle contient des technologies d'éditeurs autres que Microsoft. 
Pour plus de détails, consultez https://aka.ms/template-3pn.

Traitement des actions postcréation...
Exécution de 'dotnet restore' sur src/DockerDemo\DockerDemo.csproj...
  Restoring packages for C:\Labs\DockerDemo\src\DockerDemo\DockerDemo.csproj...
  Generating MSBuild file C:\Labs\DockerDemo\src\DockerDemo\obj\DockerDemo.csproj.nuget.g.props.
  Generating MSBuild file C:\Labs\DockerDemo\src\DockerDemo\obj\DockerDemo.csproj.nuget.g.targets.
  Restore completed in 4,93 sec for C:\Labs\DockerDemo\src\DockerDemo\DockerDemo.csproj.


Restauration réussie.

We use the dotnet CLI to run the DockerDemo :

C:\Labs\DockerDemo
λ cd src

C:\Labs\DockerDemo\src
λ cd DockerDemo\

C:\Labs\DockerDemo\src\DockerDemo
λ dotnet run
Hosting environment: Production
Content root path: C:\Labs\DockerDemo\src\DockerDemo
Now listening on: http://localhost:5000
Application started. Press Ctrl+C to shut down.
info: Microsoft.AspNetCore.Hosting.Internal.WebHost[1]
      Request starting HTTP/1.1 GET http://localhost:5000/
info: Microsoft.AspNetCore.Hosting.Internal.WebHost[2]
      Request finished in 36.7997ms 200
info: Microsoft.AspNetCore.Hosting.Internal.WebHost[1]
      Request starting HTTP/1.1 GET http://localhost:5000/favicon.ico
info: Microsoft.AspNetCore.Hosting.Internal.WebHost[2]
      Request finished in 0.2534ms 200

This is the following result when we open a web browser :
Let's use now one of the new key features of ASP.NET Core 2.0 :

Razor Pages

ASP.NET Core 2.0 version comes with a great new feature : Razor Pages. Razor Pages is less ceremony than MVC and a part of MVC as well. That's why we need to configure MVC middleware in the pipeline. The following line 10 adds the MVC middleware to make it available. Line 15 configures the MVC middleware in the pipeline.

using Microsoft.AspNetCore.Builder;  
using Microsoft.Extensions.DependencyInjection;

namespace DockerDemo  
{
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddMvc();
        }

        public void Configure(IApplicationBuilder app)
        {
            app.UseMvc();           
        }
    }
}

The creation of Pages folder is a convention to respect in order to configure the routing to Razor Pages. I am using Visual Studio Code to create index.cshtml inside Pages folder. The directive @page is required to define a Razor Pages. Note here @DateTime.Now! which ends with "!" on purpose to show how smart is Razor engine! It detects that DateTime.Now is C# and "!" is a string!

Dockerfile

Docker can build images automatically by reading the instructions from a Dockerfile. A Dockerfile is a text document that contains all the commands a user could call on the command line to assemble an image. Using docker build users can create an automated build that executes several command-line instructions in succession.

A Dockerfile is like a recipe for Docker image. -- Mark Rendle

Use multi-stage builds

Multi-stage builds are a new feature requiring Docker 17.05 or higher on the daemon and client. Multistage builds are useful to anyone who has struggled to optimize Dockerfiles while keeping them easy to read and maintain.

FROM microsoft/aspnetcore-build:2.0.0 AS build

WORKDIR /code

COPY . .

RUN dotnet restore

RUN dotnet publish --output /output --configuration Release

FROM microsoft/aspnetcore:2.0.0

COPY --from=build /output /app

WORKDIR /app

ENTRYPOINT [ "dotnet", "DockerDemo.dll" ]
docker build
docker build -t local/demo .
C:\Labs\DockerDemo\src\DockerDemo
λ docker build -t local/demo .
Sending build context to Docker daemon  888.3kB
Step 1/9 : FROM microsoft/aspnetcore-build:2.0.0 AS build
 ---> c5549d4c5716
Step 2/9 : WORKDIR /code
 ---> 90b140aadebd
Removing intermediate container b85d532f77ab
Step 3/9 : COPY . .
 ---> 090a3972189b
Step 4/9 : RUN dotnet restore
 ---> Running in 50828acd447c
  Restoring packages for /code/DockerDemo.csproj...
  Generating MSBuild file /code/obj/DockerDemo.csproj.nuget.g.props.
  Generating MSBuild file /code/obj/DockerDemo.csproj.nuget.g.targets.
  Restore completed in 2.32 sec for /code/DockerDemo.csproj.
 ---> 99f022b6d1d0
Removing intermediate container 50828acd447c
Step 5/9 : RUN dotnet publish --output /output --configuration Release
 ---> Running in 02aa4dd308cc
Microsoft (R) Build Engine version 15.3.409.57025 for .NET Core
Copyright (C) Microsoft Corporation. All rights reserved.

  DockerDemo -> /code/bin/Release/netcoreapp2.0/DockerDemo.dll
  DockerDemo -> /output/
 ---> 682e257fb2c2
Removing intermediate container 02aa4dd308cc
Step 6/9 : FROM microsoft/aspnetcore:2.0.0
 ---> 3a33cbde7608
Step 7/9 : COPY --from=build /output /app
 ---> ed0207b3abd8
Step 8/9 : WORKDIR /app
 ---> 13f302499f60
Removing intermediate container bf06b9c9a7ba
Step 9/9 : ENTRYPOINT dotnet DockerDemo.dll
 ---> Running in de49cf3db283
 ---> df5dcfc52a75
Removing intermediate container de49cf3db283
Successfully built df5dcfc52a75
Successfully tagged local/demo:latest
SECURITY WARNING: You are building a Docker image from Windows against a non-Windows Docker host. 
All files and directories added to build context will have '-rwxr-xr-x' permissions. 
It is recommended to double check and reset permissions for sensitive files and directories.
C:\Labs\DockerDemo\src\DockerDemo
λ docker images
REPOSITORY                   TAG                 IMAGE ID            CREATED             SIZE
local/demo                   latest              df5dcfc52a75        2 minutes ago       280MB
<none>                       <none>              682e257fb2c2        2 minutes ago       1.86GB
microsoft/aspnetcore         2.0.0               3a33cbde7608        3 days ago          280MB
microsoft/dotnet             2.0.0-sdk           fde8197d13f4        13 days ago         1.64GB
microsoft/aspnetcore-build   2.0.0               c5549d4c5716        2 weeks ago         1.86GB

Let's remove the unused data using the following command :

docker system prune

This command will remove all stopped containes, all networks not used by at least one container, all dangling images and all build cache. Before deleting such containes, networks, images or cache, you need to consent it.

C:\Labs\DockerDemo\src\DockerDemo
λ docker system prune
WARNING! This will remove:
        - all stopped containers
        - all networks not used by at least one container
        - all dangling images
        - all build cache
Are you sure you want to continue? [y/N] y
Deleted Containers:
ee5263bb86ca34c4d5f017692bca5dd45430501dfd16cd910625981b699d96ba
601251891e0bf76147726ff5d326f312fe538c53244dd47a3feb292b597665b4

Deleted Images:
deleted: sha256:682e257fb2c26bebeeaa2afc107b95c1776f979bc54032ada740fa4a391c916f
deleted: sha256:1bdebbe97363a2eb5bfcaa7e516dd9ce90be3de8e77099e1865ab5243ef280b5
deleted: sha256:99f022b6d1d0457861fad319b6e57f46799bced7a53bc93a2632808207ab771c
deleted: sha256:7adeb4c2cb4e8ee5ea0ebc69e08e60aff7f50c878b7773605cfa368228f3690a
deleted: sha256:090a3972189bdb435a164a2fdccd15369a55dbfab9caf9bdc404249eb2fe1fd7
deleted: sha256:6931596d47424e360ea4c189190dc901e69a8502f7b0dcd07c2a26fe4202ad55
deleted: sha256:90b140aadebd7598af6b2fe4a01039308a11ca4a4e795473b5c1580770536a09
deleted: sha256:c17e63e6db688b9f4c2ce8575d8508dfc4058653783b682b6bf9dffb68328dee

Total reclaimed space: 1.618GB
C:\Labs\DockerDemo\src\DockerDemo
λ docker images
REPOSITORY                   TAG                 IMAGE ID            CREATED             SIZE
local/demo                   latest              df5dcfc52a75        6 minutes ago       280MB
microsoft/aspnetcore         2.0.0               3a33cbde7608        3 days ago          280MB
microsoft/dotnet             2.0.0-sdk           fde8197d13f4        13 days ago         1.64GB
microsoft/aspnetcore-build   2.0.0               c5549d4c5716        2 weeks ago         1.86GB

Mark used -ti option which means terminal interactive. He also mapped the hosting machine port 5080 with port 80 in the docker container:

docker run -ti -p 5080:80 local/demo

Let's open a browser, type localhost:5080 and voilà!

Summary

In this blog post, we learnt the following points :

  • docker pull to get Images from hub.docker.com

  • docker run to start a container

  • microsoft/aspnetcore and microsoft/aspnetcore-build

  • Dockerfile to define your own image

  • Multi-stage build – two FROM lines

  • docker build to build your own image

Hydrate yourself and happy coding and learning!

Authored by

Disclaimer: The opinions expressed herein are my own personal opinions and do not represent my employer's view in any way.