Building up some Microservices, Breaking up the Monolith #2 – Dockerising the Monolith

By | 2 March, 2019

Docker as a technology seems to divide the community, some say its a good solution to the problem of packaging up your software that collects all of your dependencies and then stamps them into a repeatable deployable package….others say its been done before with other technologies or that its a security problem waiting to happen since all of the dependencies you’ve placed in the container are locked at a particular version and not updatable.

If you’ve got a pipeline around it thet can regenerate your deployable package in a repeatable and testable way (which is what we’re aiming to do with this series), then my opinion tends to be with the former. We can use these repeatable blocks to create something complex yet easy to support. *BUT* we do have to make sure its automated and repeatable…..

Lets make our first immutable building block.

Here are the basics of Docker (and yes, there are books and books more on this topic alone, however I’m purely going for the minimum here…)

Docker Images are like Templates. We can use any templates stored in a Docker Registry and use that as a basis to create a new Image. (If you want to have a look at what images you can build on top of, search google for docker hub). To create a new Image, stored locally, we’ll use the “docker build” command in combination with a “DockerFile”

Docker Containers are the running version of an Image. They are an “instance” of that image that can run. To execute a container we’ll use “docker container run”

We’ll get to registries, and docker pulls in another article.

For now the aim is to build a docker image using a base image, and add our code to it. Lets go through my docker file one line at a time. Each of these lines, becomes a layer in our docker “onion”, this leads to the ability to reuse layers and speed up successive builds.

 FROM microsoft/dotnet:2.1-sdk AS builder

This instructs docker to use the Microsoft/dotnet:2.1-sdk image (pull it from docker hub in necessary, if its already in the local library just use that).

 WORKDIR /source

In the container, set the working directory to “source”

 COPY *.csproj .
 RUN dotnet restore

…this copies our project file, and pulls down all of our Nuget dependencies.

 COPY . .
 RUN dotnet publish --output /app/ --configuration Release

copies the rest of our source into place, and creates a binary in the app folder.

FROM microsoft/dotnet:2.1-aspnetcore-runtime
COPY --from=builder /app .
ENTRYPOINT ["dotnet", "monolithsvc.dll"]

and now the magic. Create another new image from the “microsoft/dotnet:2.1-aspnetcore-runtime” image, set the working directory to “app” and copy the contents of the app folder on the builder image to this one. “Entrypoint” defines the command that will run on invocation.

Here’s the whole file:

# Stage 1
FROM microsoft/dotnet:2.1-sdk AS builder
WORKDIR /source

# caches restore result by copying csproj file separately
COPY *.csproj .
RUN dotnet restore

# copies the rest of your code
COPY . .
RUN dotnet publish --output /app/ --configuration Release

# Stage 2
FROM microsoft/dotnet:2.1-aspnetcore-runtime
COPY --from=builder /app .
ENTRYPOINT ["dotnet", "monolithsvc.dll"]

So how do we now build the image? Do this by:

docker build -t monolithsvc .

Step 1/10 : FROM microsoft/dotnet:2.1-sdk AS builder
---> 95a1b55739ea
Step 2/10 : WORKDIR /source
---> Using cache
---> ce33cfb74d30
Step 3/10 : COPY *.csproj .
---> 0d2903d51d1a
Step 4/10 : RUN dotnet restore
---> Running in e7fe9643d5fc
Restoring packages for /source/monolithsvc.csproj...
Installing System.Diagnostics.Contracts 4.0.1.
Installing System.Net.WebSockets 4.0.0.
Installing System.Security.Principal 4.0.1.
Installing System.Security.Claims 4.0.1.
Installing System.Threading.Thread 4.0.0.
Installing System.Threading.Overlapped 4.0.1.
Installing runtime.win7-x64.runtime.native.System.Data.SqlClient.sni 4.3.0.
Installing runtime.win7-x86.runtime.native.System.Data.SqlClient.sni 4.3.0.
Installing Microsoft.Net.Http.Headers 1.0.2.
Installing Microsoft.Extensions.ObjectPool 1.0.1.
Installing Microsoft.AspNetCore.WebUtilities 1.0.2.
Installing Microsoft.AspNetCore.Http.Abstractions 1.0.2.
Installing Microsoft.AspNetCore.Http.Features 1.0.2.
Installing System.IO.FileSystem.Watcher 4.0.0.
Installing Microsoft.Extensions.FileSystemGlobbing 1.0.1.
Installing Microsoft.Extensions.FileProviders.Abstractions 1.0.1.
Installing System.Collections.Immutable 1.2.0.
Installing System.Data.Common 4.3.0.
Installing runtime.native.System.Data.SqlClient.sni 4.3.0.
Installing System.IO.Pipes 4.3.0.
Installing System.Security.AccessControl 4.3.0.
Installing Microsoft.ApplicationInsights.AspNetCore 2.2.0.
Installing System.Threading.Overlapped 4.3.0.
Installing System.Linq.Expressions 4.1.1.
Installing Microsoft.Extensions.Primitives 1.0.1.
Installing Microsoft.AspNetCore.Http.Extensions 1.0.2.
Installing Microsoft.AspNetCore.Http 1.0.2.
Installing Microsoft.Extensions.Logging.Abstractions 1.0.2.
Installing Microsoft.ApplicationInsights.PerfCounterCollector 2.5.0.
Installing Microsoft.ApplicationInsights 2.5.0.
Installing Microsoft.Extensions.Configuration 1.0.2.
Installing Microsoft.AspNetCore.Hosting.Abstractions 1.0.2.
Installing Microsoft.Extensions.DiagnosticAdapter 1.0.2.
Installing Microsoft.ApplicationInsights.WindowsServer.TelemetryChannel 2.5.0.
Installing Microsoft.Extensions.Configuration.Json 1.0.2.
Installing Microsoft.AspNetCore.Hosting 1.0.2.
Installing Microsoft.ApplicationInsights.DependencyCollector 2.5.0.
Installing System.Runtime.Serialization.Json 4.3.0.
Installing System.Net.Requests 4.3.0.
Installing Microsoft.Extensions.Configuration.Abstractions 1.0.2.
Installing System.ComponentModel 4.0.1.
Installing Microsoft.AspNetCore.Hosting.Server.Abstractions 1.0.2.
Installing Microsoft.Extensions.DependencyInjection.Abstractions 1.0.2.
Installing System.Net.WebHeaderCollection 4.3.0.
Installing System.Net.NetworkInformation 4.3.0.
Installing Newtonsoft.Json 10.0.3.
Installing System.IO.FileSystem.AccessControl 4.3.0.
Installing System.Data.SqlClient 4.3.1.
Installing Microsoft.Extensions.FileProviders.Physical 1.0.1.
Installing System.Reflection.Metadata 1.3.0.
Installing Microsoft.Extensions.Configuration.FileExtensions 1.0.2.
Installing Microsoft.Extensions.Configuration.EnvironmentVariables 1.0.2.
Installing Microsoft.Extensions.Logging 1.0.2.
Installing Microsoft.Extensions.DependencyInjection 1.0.2.
Installing System.Diagnostics.StackTrace 4.0.1.
Installing Microsoft.Extensions.PlatformAbstractions 1.0.0.
Installing Microsoft.Extensions.Options 1.0.2.
Generating MSBuild file /source/obj/monolithsvc.csproj.nuget.g.props.
Generating MSBuild file /source/obj/monolithsvc.csproj.nuget.g.targets.
Restore completed in 8.38 sec for /source/monolithsvc.csproj.
Removing intermediate container e7fe9643d5fc
---> 109abb4b890d
Step 5/10 : COPY . .
---> 11ba37721534
Step 6/10 : RUN dotnet publish --output /app/ --configuration Release
---> Running in e37b451285a7
Microsoft (R) Build Engine version 15.9.20+g88f5fadfbe for .NET Core
Copyright (C) Microsoft Corporation. All rights reserved.

Restoring packages for /source/monolithsvc.csproj...
Generating MSBuild file /source/obj/monolithsvc.csproj.nuget.g.props.
Generating MSBuild file /source/obj/monolithsvc.csproj.nuget.g.targets.
Restore completed in 783.64 ms for /source/monolithsvc.csproj.
monolithsvc -> /source/bin/Release/netcoreapp2.1/monolithsvc.dll
monolithsvc -> /source/bin/Release/netcoreapp2.1/monolithsvc.Views.dll
monolithsvc -> /app/
Removing intermediate container e37b451285a7
---> d2d860de0f60
Step 7/10 : FROM microsoft/dotnet:2.1-aspnetcore-runtime
---> ecbbac7adc4c
Step 8/10 : WORKDIR /app
---> Using cache
---> a6889088bb6a
Step 9/10 : COPY --from=builder /app .
---> 228079216f73
Step 10/10 : ENTRYPOINT ["dotnet", "monolithsvc.dll"]
---> Running in b7e038162050
Removing intermediate container b7e038162050
---> ecc207b7e994
Successfully built ecc207b7e994
Successfully tagged monolithsvc:latest

We should have a successful image in the local docker repo now, do a

docker image ls

REPOSITORY                                  TAG                      IMAGE ID            CREATED             SIZE
monolithsvc                                 latest                   ecc207b7e994        5 minutes ago       256MB

as you can see the image id matches with the hash at the final step of the docker build. Cool.

Last but not least we should try and make it run:

docker container run --name testdoodad -p 8080:80 monolithsvc

warn: Microsoft.AspNetCore.DataProtection.KeyManagement.XmlKeyManager[35]
      No XML encryptor configured. Key {20f00e1c-b3c9-4b3a-b1e8-b36e7bbc73eb} may be persisted to storage in unencrypted form.
Hosting environment: Production
Content root path: /app
Now listening on: http://[::]:80
Application started. Press Ctrl+C to shut down.

…and test using a browser http://localhost:8080

Hopefully should all be nice and happy!

Source code at this point is here:
Links to other articles in the series:
1. First a Monolith