Let’s Create a Dotnet Core Docker Image
By: Matt At Bitleaf.Io
Tags: Docker, Docker Image, Dotnet Core, Dotnet

Let’s Create a Dotnet Core Docker Image

In a previous post we created our Dockerfile. In this next step we are going to use our Dockerfile script to create a Docker image. Then once we have the image we can run it in a container. So let the games begin.

For our sample aspnet app, I’m just using the code from the asp.net samples repository…

https://github.com/bitleaf-io/aspdotnetcore-docker.

First, I’ll clone that repository to my local machine.

Projects/bitleaf/dotnet-docker3

❯ git clone git@github.com:bitleaf-io/aspdotnetcore-docker.git

Cloning into 'aspdotnetcore-docker'...

remote: Enumerating objects: 77, done.

remote: Counting objects: 100% (77/77), done.

remote: Compressing objects: 100% (60/60), done.

remote: Total 77 (delta 12), reused 73 (delta 12), pack-reused 0

Receiving objects: 100% (77/77), 683.33 KiB | 381.00 KiB/s, done.

Resolving deltas: 100% (12/12), done.

Projects/bitleaf/dotnet-docker3 took 10s

❯ cd aspdotnetcore-docker/

aspdotnetcore-docker on master

❯ ls

aspnetapp Dockerfile README.md

aspdotnetcore-docker on master

Great. We now have the sample repository to work with. It contains our Dockerfile we created in the previous post and some sample asp.net code from Microsoft.

Let’s take a look at our Dockerfile again.

# <https://hub.docker.com/_/microsoft-dotnet-core>

FROM mcr.microsoft.com/dotnet/core/sdk:3.1 AS build

WORKDIR /source



# copy csproj and restore as distinct layers

COPY *.sln .

COPY aspnetapp/*.csproj ./aspnetapp/

RUN dotnet restore -r linux-musl-x64



# copy everything else and build app

COPY aspnetapp/. ./aspnetapp/

WORKDIR /source/aspnetapp

RUN dotnet publish -c release -o /app -r linux-musl-x64 --self-contained false --no-restore



# final stage/image

FROM mcr.microsoft.com/dotnet/core/aspnet:3.1-alpine

WORKDIR /app

COPY --from=build /app ./



ENTRYPOINT ["./aspnetapp"]

This is the same file we created before and it’s going to allow us to build and run our sample asp.net core application inside of Docker. Remember the three stages of Docker…

Create a Dockerfile Build a Docker image based on that Dockerfile Run the Docker container based on the Docker image

So, we have our Dockerfile. Now we need to build our Docker image.

❯ docker build --pull -t aspnetapp .

Sending build context to Docker daemon 5.16MB

Step 1/12 : FROM mcr.microsoft.com/dotnet/core/sdk:3.1 AS build

3.1: Pulling from dotnet/core/sdk

376057ac6fa1: Pull complete

5a63a0a859d8: Pull complete

496548a8c952: Pull complete

2adae3950d4d: Pull complete

2e16b9e1a161: Pull complete

00f5f595bb47: Pull complete

218e0b1856d2: Pull complete

Digest: sha256:134f0793a9a65a237430b5d98050cb63e7c8718b0d2e73f4f974384a98023d56

Status: Downloaded newer image for mcr.microsoft.com/dotnet/core/sdk:3.1

---> 8c4dd5ac064a

Step 2/12 : WORKDIR /source

---> Using cache

---> 0c604866f2f8

Step 3/12 : COPY *.sln .

---> 6eabf9f173e9

Step 4/12 : COPY aspnetapp/*.csproj ./aspnetapp/

---> 0b6d0ebff6fa

Step 5/12 : RUN dotnet restore -r linux-musl-x64

---> Running in d6593d853c89

Determining projects to restore...

Restored /source/aspnetapp/aspnetapp.csproj (in 3.7 sec).

Removing intermediate container d6593d853c89

---> 9ad377760bb8

Step 6/12 : COPY aspnetapp/. ./aspnetapp/

---> 997f14845ebc

Step 7/12 : WORKDIR /source/aspnetapp

---> Running in 1493794ee1a3

Removing intermediate container 1493794ee1a3

---> bff3d9c480ac

Step 8/12 : RUN dotnet publish -c release -o /app -r linux-musl-x64 --self-contained false --no-restore

---> Running in 11cb4f8d8956

Microsoft (R) Build Engine version 16.6.0+5ff7b0c9e for .NET Core

Copyright (C) Microsoft Corporation. All rights reserved.



aspnetapp -> /source/aspnetapp/bin/release/netcoreapp3.1/linux-musl-x64/aspnetapp.dll

aspnetapp -> /source/aspnetapp/bin/release/netcoreapp3.1/linux-musl-x64/aspnetapp.Views.dll

aspnetapp -> /app/

Removing intermediate container 11cb4f8d8956

---> e3f76099643a

Step 9/12 : FROM mcr.microsoft.com/dotnet/core/aspnet:3.1-alpine

3.1-alpine: Pulling from dotnet/core/aspnet

cbdbe7a5bc2a: Already exists

80caa14a70db: Pull complete

08a0d3029c8d: Pull complete

76c18e0100a6: Pull complete

Digest: sha256:d9275e02fa9f52a31917a5ef7c0612811c64d2a6a401eb9654939595dab7e5de

Status: Downloaded newer image for mcr.microsoft.com/dotnet/core/aspnet:3.1-alpine

---> 57c690133803

Step 10/12 : WORKDIR /app

---> Running in e07191c61223

Removing intermediate container e07191c61223

---> 0edcf08059b6

Step 11/12 : COPY --from=build /app ./

---> 12e89067c073

Step 12/12 : ENTRYPOINT ["./aspnetapp"]

---> Running in 421522f32772

Removing intermediate container 421522f32772

---> bb6e34978e96

Successfully built bb6e34978e96

Successfully tagged aspnetapp:latest

aspdotnetcore-docker on master via •NET v3.1.300 took 18s

❯

So, all we did was run docker build --pull -t aspnetapp . in the root of our repository directory. So what did it do? First off let's look at the docker command...

--pull in the docker command line means to pull the the latest specified images in our Dockerfile. In our case we wanted the aspnet core SDK and Runtime 3.1 images. So this switch just copied those images down from Docker hub to our local machine for us to use in our own Docker image.

-t aspnetapp . in the docker command line means to name our Docker image aspnetapp . We'll use this to reference our image when we are ready to run it. Also not the trailing . at the end. That just means to look at the current directory for the Dockerfile.

After we run that we see the Dockerfile in action. Everything is occurring inside of Docker. It’s bringing down the SDK and Runtime images and copying source files from our machine, but the build and execution is all inside of Docker.

Aside: By default Docker goes against Docker Hub to locate and bring down images. You can point to different docker repositories and even create your own private repository to host your images.

Let’s take a look at what images we now have on our local machine…

❯ docker images

REPOSITORY TAG IMAGE ID CREATED SIZE

aspnetapp latest bb6e34978e96 About an hour ago 110MB

mcr.microsoft.com/dotnet/core/sdk 3.1 8c4dd5ac064a About an hour ago 705MB

mcr.microsoft.com/dotnet/core/aspnet 3.1-alpine 57c690133803 About an hour ago 105MB

By running the docker images command we can see what images we have downloaded to our local machine. You can see our newly created Docker image along with the Microsoft Dotnet SDK and runtime.

Running our Dotnet Core Docker Image

Now the time has come to actually run our aspnet core code inside of Docker.

❯ docker run --rm -p 8000:80 aspnetapp

warn: Microsoft.AspNetCore.DataProtection.Repositories.FileSystemXmlRepository[60]

Storing keys in a directory '/root/.aspnet/DataProtection-Keys' that may not be persisted outside of the container. Protected data will be unavailable when container is destroyed.

warn: Microsoft.AspNetCore.DataProtection.KeyManagement.XmlKeyManager[35]

No XML encryptor configured. Key {220ed00c-80b6-46a5-a59c-62caa78ace19} may be persisted to storage in unencrypted form.

info: Microsoft.Hosting.Lifetime[0]

Now listening on: http://[::]:80

info: Microsoft.Hosting.Lifetime[0]

Application started. Press Ctrl+C to shut down.

info: Microsoft.Hosting.Lifetime[0]

Hosting environment: Production

info: Microsoft.Hosting.Lifetime[0]

Content root path: /app

We tell docker to run our image by running docker run --rm -p 8000:80 aspnetapp . Let's break down these commands.

--rm tells Docker to remove the container when it's done running. This is just so we don't leave a container on our system since this is just for testing.

-p 8000:80 tells Docker to map a network port from 80 on the Docker container (which is the port our aspnet core app is running on inside the container) to our host machine port 8000 . This will let us open our browser on our machine and access the code that is running inside of the Docker container.

aspnetapp tell Docker what image to run. This is the name we gave it earlier. By default it always gets the latest version of the image (just like if we added the tag :latest after aspnetapp like aspnetapp:latest . If we want to specify a particular version we would add that tag like aspnetapp:1.0.0 . Tags can be anything you like, they don't have to be in a particular format.

If we open our machine’s browser and go to…

http://localhost:8000/

…you should see the beautiful ‘Welcome to .NET Core’ page. Again that page you are accessing is running inside of that Docker container.

Now why not take a look at what is inside of the container that is based on our Docker image. Opne up another terminal and let’s see what Docker containers we currently have on the system (including our current running one)…

❯ docker container ls

CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES

610cddb6f411 aspnetapp "./aspnetapp" 47 seconds ago Up 45 seconds 0.0.0.0:8000->80/tcp zen_mendel

Cool. I see our container running. The container is Linux based so lets shell into it. We take note of the Container ID above and run the docker exec command.

❯ docker exec -it 610cddb6f411 sh

/app # ls

appsettings.Development.json aspnetapp aspnetapp.Views.pdb aspnetapp.dll aspnetapp.runtimeconfig.json wwwroot

appsettings.json aspnetapp.Views.dll aspnetapp.deps.json aspnetapp.pdb web.config

So there’s our compiled application inside of the Docker container which is based on our image. What does that docker command actually do.

exec Docker exec executes a command inside of a running container. In our case it is executing sh which is the shell.

-it tells Docker we want to have an interactive terminal to work with, so we can type in commands against the container.

Now enter exit to disconnect from the container's shell. Let's go back to our original terminal with the Docker container running and hit ctrl+c to kill it.

Delete Docker Images

If we run docker images again.

❯ docker images

REPOSITORY TAG IMAGE ID CREATED SIZE

aspnetapp latest bb6e34978e96 About an hour ago 110MB

mcr.microsoft.com/dotnet/core/sdk 3.1 8c4dd5ac064a About an hour ago 705MB

mcr.microsoft.com/dotnet/core/aspnet 3.1-alpine 57c690133803 About an hour ago 105MB

We see our three images. We can remove images from our local machine with the docker image rm command.

❯ docker image rm aspnetapp

Untagged: aspnetapp:latest

Deleted: sha256:bb6e34978e96ed4eea3fc64adf943a09b9e798e0926b4c2308e341e3e306e76f

Deleted: sha256:12e89067c0734b17e2a60685ad200c4f11ea63db9854ba8cf473ea4a732b7cc3

Deleted: sha256:2d6b8df9e2adf8c4e144d1dafc47dbc25a73f856f0d761d924d8f84ea810c07b

Deleted: sha256:0edcf08059b6534b1bf18c330152f413c13ba7b7923fac9b4327abf82c8cec4b

Deleted: sha256:670db31ab972c44245b0fb18262d6e4a1367f02ad30a9207e2b458fd4d7bbe8f

This removed our custom aspnetapp Docker image. We can always build it again if we need it. You could have also used the Image ID instead of the name.

Now if I list our images again.

❯ docker images

REPOSITORY TAG IMAGE ID CREATED SIZE

mcr.microsoft.com/dotnet/core/sdk 3.1 8c4dd5ac064a About an hour ago 705MB

mcr.microsoft.com/dotnet/core/aspnet 3.1-alpine 57c690133803 About an hour ago 105MB

You can see our custom Docker image is now gone from our local machine.

Hopefully you can see the power of this. You don’t have to wait and make sure your hosting server has all right versions of the software over deployment weekend. You just let it build and deploy your Docker image. It will work anywhere Docker is running. SO much less stress. Obviously there’s a little more too it with managing your company’s services through the use of container orchestration like Kubernetes, but you can see from a developer standpoint how this makes this helps remove those wonderful deployment surprises.