In my earlier post, I had written about building docker image for an asp.net application. In this post, I will show how we can use docker-compose api to orchestrate the creation of docker images for an application  that depends on multiple services.  Docker Compose is a set of apis that docker exposes, to automate and orchestrate docker tasks – for e.g., building the docker image, instancing dependent images before starting a particular image etc., For this sample I will be creating an asp.net core mvc application that uses asp.net core web api to load data from it.

Step 1 : Create an asp.net core mvc app

This can be done by using commands that dotnet cli exposes. Create a new app, restore the packages and run it once to make sure everything is working fine.

dotnet new -t web
dotnet restore
dotnet run

Step 2 : Create an asp.net core web api

Though this could very well be done using dotnet cli commands, I will be using yeoman generator to scaffold a web api project. This way we will create a project which contains the necessary modules and code files related to web api alone. Run the below set of commands to install yeoman and the aspnet generator. You can skip this part, if yeoman and asp.net generators are already installed in your machine.

npm install -g yo
npm install -g generator-aspnet

To scaffold a new asp.net project using yeoman, all we need is to run the below command and answer the sequence of questions it throws. You can take a look at my post on running your first web api on mac which details the steps.

yo aspnet

Once the app is created, we can use the dotnet cli commands dotnet restore and dotnet run to check the app is working fine.

Step 3 : Modify the web api project to return the data we desire

For this post, I will be creating an API that returns the agenda for an event. First, I will add a model that gets returned by the api. Navigate to Models folder and create a Session Model

public class Session
{
public string Title { get; set; }
public string Speaker { get; set; }
public string Time { get; set; }
}

Create a new controller that returns a set of sessions. You can use yeoman’s sub generators to accomplish this. Navigate to the Controllers folder and run the below command.

yo aspnet:webapicontroller SessionsController
You called the aspnet subgenerator with the arg SessionsController
SessionsController.cs created.
create SessionsController.cs

Modify the get method in the controller with the below code:


[HttpGet]
public IEnumerable<Session> Get()
{
List<Session> Sessions = new List<Session>();
Sessions.Add(new Session { Title="Intro to Docker", Speaker = "Neependra", Time="09:30 - 11:00"});
Sessions.Add(new Session { Title="Break/Networking", Speaker = "-", Time="11:00 - 11:15"});
Sessions.Add(new Session { Title="Docker for .Net apps", Speaker = "Swami", Time="11:15 - 12:45"});
return Sessions;
}

Now, the api method is ready to be consumed. To verify the output, build and run the application using dotnet run. When you hit the url http://localhost:5000/api/sessions you should be seeing the below json output.

[{"title":"Intro to Docker","speaker":"Neependra","time":"09:30 - 11:00"},{"title":"Break/Networking","speaker":"-","time":"11:00 - 11:15"},{"title":"Docker for .Net apps","speaker":"Swami","time":"11:15 - 12:45"}]

Step 4 : Consume the web api in the mvc app

To consume the sessions data in the mvc app, add a new controller method within HomeController.cs file. Add below set of code


 public async Task<IActionResult> Session()
 {
var response = await client.GetStringAsync("http://localhost:8000/api/sessions");
 var sessions = JsonConvert.DeserializeObject&amp;lt;List&amp;lt;Session&amp;gt;&amp;gt;(response);
 ViewData["Message"] = "Sessions";
 return View(sessions);
 }

Now that the controller method is ready, its time to create a view to render this data. Navigate to the Views\Home folder and create a new file Sessions.cshtml and update the file with below lines of code


@model IEnumerable<bdotnet_mvc_demo.Models.Session>
<h2>@ViewData["Message"]</h2>
<table class="table">
<thead>
<tr>
<th>
					@Html.DisplayNameFor(model=>model.Time)</th>
<th>
					@Html.DisplayNameFor(model=>model.Title)</th>
<th>
					@Html.DisplayNameFor(model=>model.Speaker)</th>
</tr>
</thead>
@foreach (var item in Model) {
<tr>
<td>@Html.DisplayFor(modelItem => item.Time)</td>
<td>@Html.DisplayFor(modelItem => item.Title)</td>
<td>@Html.DisplayFor(modelItem => item.Speaker)</td>
</tr>
}</table>

Make sure to update the first line with appropriate namespace of your model

Step 5 : Run both the apps and verify they are working fine

Run the webapi app under the port 8000 by using the following command

dotnet run --server.urls=http://localhost:8000/

Run the mvc app by running the dotnet run command. If you navigate to the url http://localhost:5000/home/sessions you should be seeing the agenda appearing as a table view

Step 6 : Build docker images for both the apps

Now that the application is completely ready, I will add Dockerfile to both the projects and create a docker image for both the applications. Before building the image, we just need to make one change to the mvc app code to point to the right api url.

Instead of http://localhost:8000/api/sessions update the url with http://bdotnetwebapi:8000/api/sessions. bdotnetwebapi is the domain name which I will be using to host the web api.

The docker file for the web api project should look like this:


FROM microsoft/aspnetcore:latest
WORKDIR /app
COPY /app /app
ENV ASPNETCORE_URLS http://*:8000
EXPOSE 8000
ENTRYPOINT ["dotnet", "bdotnet-webapi-demo.dll"]

You might have to update the entry point line with the appropriate dll name of yours. To understand about this dockerfile, refer my previous post on building first docker image for aspnet core apps

The docker file for the mvc app should looks like this:


FROM microsoft/aspnetcore:latest
WORKDIR /app
COPY /app /app
ENV ASPNETCORE_URLS http://*:5000
EXPOSE 5000
ENTRYPOINT ["dotnet", "bdotnet-mvc-demo.dll"]

Use below docker commands to build and push both the images. I will be creating an optimized image that can run faster, so I will have to first publish the application using dotnet publish -c release -o app to the app folder and then, to build the mvcapp image, Run the below command


docker build -t mvcdemo .

To push a docker image to the docker registry, you need to create an account (its absolutely FREE) at http://hub.docker.com.  Login to the registry from the docker client in your machine and push the images


docker login

docker push <yournameuserid>/<imagename>:<tag>

I have pushed the images to my docker registry and they can be found in the registry using the below urls

mvc app – https://hub.docker.com/r/wannabeegeek/mvcapp-demo/

web api app – https://hub.docker.com/r/wannabeegeek/webapi-demo/

Step 7 : Use Docker-Compose to orchestrate the services

Docker compose api looks for the file with the name of Docker-Compose.yml.If we give a different name, we should explicitly mention the location of the compose yaml file while running the docker-compose command. I will be using image reference technique to reference multiple images. The set of commands we require to run the demo app are given below, update the yaml with this set of commands.


version : '2'

services:
  bdotnetmvcapp:
    image: wannabeegeek/mvcapp-demo:v1
  ports:
    - 5000:5000
  depends_on:
    - bdotnetwebapi
  bdotnetwebapi:
    image: wannabeegeek/webapi-demo:v1
    ports:
      - 8000

image: wannabeegeek/mvcapp-demo:v1 – is required to mention using which image the bdotnetmvcapp is being built

ports: -5000:5000is required to specify under which port the mvc app should be running. In this case, container port 5000 is mapped to the host port 5000.

depends_on – means the mvc app is dependent on the bdotnetwebapi service, the details of which are given below in the yaml file.Notice this is the same name, I had used in the url of the web api within my mvc app.

ports: -8000 – specifies that the api service needs to be run under the port 8000, but its not mapped to any of the host port. So, we wouldn’t really be able to access the api from host. The api will only be available for the mvc container.

To orchestrate both the services, run the below command from the directory where Docker-Compose.yml file is present


docker-compose up

Starting bdotnetmvcdemo_bdotnetwebapi_1

Starting bdotnetmvcdemo_bdotnetmvcapp_1

Attaching to bdotnetmvcdemo_bdotnetwebapi_1, bdotnetmvcdemo_bdotnetmvcapp_1

bdotnetwebapi_1  | Hosting environment: Production

bdotnetwebapi_1  | Content root path: /app

bdotnetwebapi_1  | Now listening on: http://*:8000

bdotnetwebapi_1  | Application started. Press Ctrl+C to shut down.

bdotnetmvcapp_1  | info: Microsoft.Extensions.DependencyInjection.DataProtectionServices[0]

bdotnetmvcapp_1  |       User profile is available. Using '/root/.aspnet/DataProtection-Keys' as key repository; keys will not be encrypted at rest.

bdotnetmvcapp_1  | Hosting environment: Production

bdotnetmvcapp_1  | Content root path: /app

bdotnetmvcapp_1  | Now listening on: http://*:5000

bdotnetmvcapp_1  | Application started. Press Ctrl+C to shut down.

As you can see from the log, docker has first started a container with the web api image and then it has started a container with mvc app image and attached both of them. Browsing the url http://localhost:5000will bring up the same application we saw working in step 5.

Congratulations! You have successfully created your first orchestration using docker-compose.

The completed code base of the application is available in github at https://github.com/svswaminathan/docker-multiservice-demo

You can clone the repository, navigate to the bdotnet-mvc-demo folder and run docker-compose up to see the app running in minutes 🙂

Advertisements