New! Learn Akka.NET, .NET Core, Docker, and K8s at the 2019 Petabridge Akka.NET Training Tour!

The Akka.CQRS sample used in this workshop uses a build system built on top of Petabridge’s standard dotnet new template.

One thing you’ll need to do multiple times during the course of this workshop is build new versions of your Docker images - this document outlines how the Akka.CQRS build system works and how to do this.


The first step to publishing a new Docker image for all of the Akka.CQRS projects is to update the top entry of the file:

#### 0.1.0 May 26 2019 ####
* Fixed `NullReferenceException` when recovering `MatchAggregatorSnapshot` records with no price and volume updates.
* Fixed issue with BSON serialization for `MatchAggregatorSnapshot` records.

To bump the version number on your Docker images, add a new entry using this format to the top of the markdown file along with a description of your changes:

#### 0.1.1 May 27 2019 ####
* Reduced verbosity of the logging system.

#### 0.1.0 May 26 2019 ####
* Fixed `NullReferenceException` when recovering `MatchAggregatorSnapshot` records with no price and volume updates.
* Fixed issue with BSON serialization for `MatchAggregatorSnapshot` records.

The version number at the top of the file dictates the version number added to all of the Docker image tags we’re going to create, so choose it accordingly!

Run build.cmd on Windows or on OS X / Linux

Once is updated, we then need to execute the following command:

./build.cmd Docker [phobos] [remoteRegistry={YOUR_REMOTE_DOCKER_URL}]` 

Advanced Usage: Pass in the phobos flag on the commandline if you want to add Phobos Akka.NET actor monitoring and tracing support to this Docker image - otherwise it’ll be left off by default.

You can also pass in a URL to a remote registry, such as, to have Docker images tagged for that remote Docker image registry via the ./build.cmd Docker parameter.

However, you will need to make sure your build server or your local development machine has completed the necessary docker login steps before you run this command.

Under the covers, this command executes the following workflow:

Akka.CQRS Docker image build process

Call dotnet publish on Every .Service or .Web .csproj

We only want to publish our .NET Core applications as Docker images, not our unit tests and and libraries, thus that’s why we have the following filtering code-in place in our build.fsx file:

Target "PublishCode" (fun _ ->    
    let projects = !! "src/**/*.Service.csproj" // publish services  and web only
                      ++ "src/**/*.Web.csproj"

    let runSingleProject project =
            (fun p -> 
                { p with
                    Project = project
                    Configuration = configuration
                    VersionSuffix = overrideVersionSuffix project
                    AdditionalArgs = ["--no-restore --output bin/Release/netcoreapp2.1/publish"] // would be ideal to change publish dir via MSBuild

    projects |> Seq.iter (runSingleProject)

The dotnet publish command packs all of the binaries and other resources our application needs to run into a single set of folders: bin/Release/netcoreapp2.1/publish typically. This is what is typically used for all kinds of .NET Core binary deployments and Docker is no exception.

Call docker build on every .Service or .Web .csproj

Once all of the content is published into the bin/Release/netcoreapp2.1/publish folder for each project, our Dockerfiles can be written to copy those binaries into the working directory our application will use inside each Docker container:

FROM microsoft/dotnet:2.1-sdk AS base

# should be a comma-delimited list
ENV MONGO_CONNECTION_STR "" #MongoDb connection string for Akka.Persistence

COPY ./bin/Release/netcoreapp2.1/publish/ /app

# 9110 - Petabridge.Cmd
# 6055 - Akka.Cluster
EXPOSE 9110 6055

# Install Petabridge.Cmd client
RUN dotnet tool install --global pbm 

# Needed because
ENV PATH="${PATH}:/root/.dotnet/tools"

# RUN pbm help

CMD ["dotnet", "Akka.CQRS.Pricing.Service.dll"]

This Dockerfile is from the Akka.CQRS.Pricing.Service. As you can see, the COPY instruction is what copies the output from the dotnet publish command we executed a step earlier.

Mapping .csproj Names to Docker Images

Inside build.fsx we use a simple matching function to translate the name of every Akka.CQRS service into an acceptable Docker image name:

let mapDockerImageName (projectName:string) =
    match projectName with
    | "Akka.CQRS.TradeProcessor.Service" -> Some("akka.cqrs.tradeprocessor")
    | "Akka.CQRS.TradePlacers.Service" -> Some("akka.cqrs.traders")
    | "Akka.CQRS.Pricing.Service" -> Some("akka.cqrs.pricing")
    | "Akka.CQRS.Pricing.Web" -> Some("akka.cqrs.pricing.web")
    | _ -> None

The reason why we do this is because Docker has a specific convention it enforces for image names:

An image name is made up of slash-separated name components, optionally prefixed by a registry hostname. The hostname must comply with standard DNS rules, but may not contain underscores. If a hostname is present, it may optionally be followed by a port number in the format :8080. If not present, the command uses Docker’s public registry located at by default. Name components may contain lowercase letters, digits and separators. A separator is defined as a period, one or two underscores, or one or more dashes. A name component may not start or end with a separator.

Since all image names must be lowercase, we have to convert the normal Pascal case used by .NET namespaces into something the Docker runtime will accept, hence this mapping function.

Tagging Docker Images

Now for the final stage, tagging the Docker images. This is done using the version number from the ‘’ file, the mapped Docker image name, and the remoteRegistry value if you provided one.

let buildDockerImage imageName projectPath =
    let args = 
        if(hasBuildParam "remoteRegistry") then
                |> append "build"
                |> append "-t"
                |> append (imageName + ":" + releaseNotes.AssemblyVersion) 
                |> append "-t"
                |> append (imageName + ":latest") 
                |> append "-t"
                |> append (remoteRegistryUrl + "/" + imageName + ":" + releaseNotes.AssemblyVersion) 
                |> append "-t"
                |> append (remoteRegistryUrl + "/" + imageName + ":latest") 
                |> append "."
                |> toText
                |> append "build"
                |> append "-t"
                |> append (imageName + ":" + releaseNotes.AssemblyVersion) 
                |> append "-t"
                |> append (imageName + ":latest") 
                |> append "."
                |> toText

    ExecProcess(fun info -> 
            info.FileName <- "docker"
            info.WorkingDirectory <- Path.GetDirectoryName projectPath
            info.Arguments <- args) (System.TimeSpan.FromMinutes 5.0) (* Reasonably long-running task. *)

All Docker images will automatically be tagged with the version number you provided AND the latest tag.

Upon successfully building your Docker images, you should see output similar to the following (using 0.1.0 in our, in this case):

Akka.CQRS Docker Build output

If you liked this post, you can share it with your followers or follow us on Twitter!
Written on



Upcoming Petabridge Live Akka.NET Webinar Trainings

Get up to speed on the leading edge of large-scale .NET development with the Petabridge team. Each training is done remotely via webinar, lasts four hours, and will save you weeks of trial and error.

Course Dates
Akka.NET Application Architecture and Design Patterns
Building Networked .NET Applications with Akka.Remote
.NET Distributed Systems Architecture and Design with Akka.Cluster

Get to the cutting edge with Akka.NET

Learn production best practices, operations and deployment approaches for using Akka.NET.