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&lt;List&lt;Session&gt;&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:5000
is 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:5000
will 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 🙂
great post on container orchestration
Thanks! I am glad you found it useful 😊