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...
...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.
Top comments (0)