Creating a multi-container application can be daunting, but Docker Compose and C# can make it much more straightforward. This article will walk us through the steps to create a multi-container application with Docker Compose and C#.
So, to begin with, C#:
C# has been around for quite some period, and it continues to develop, obtaining more enhanced features.medium.com
Before we begin, make sure that Docker and Docker Compose are installed on your machine. Docker is a platform for developing, shipping, and running applications using containerization, while Docker Compose is a tool for defining and running multi-container Docker applications.
You can download Docker and Docker Compose from their respective websites.
Once Docker and Docker Compose are installed, please create a new directory for your project and navigate to it using your terminal. For this example, we will call our project "my-csharp-app".
mkdir my-csharp-app
cd my-csharp-app
Next, we will create a Dockerfile for our frontend container. This container will serve as a basic HTML page that calls our backend API.
Create a new file called "Dockerfile.frontend" in your project directory and add the following code:
FROM nginx:alpine
COPY ./nginx.conf /etc/nginx/conf.d/default.conf
COPY ./wwwroot /usr/share/nginx/html
This Dockerfile uses the official nginx: alpine image as its base and copies our custom nginx configuration file and HTML files to the appropriate directories.
Next, we will create a Dockerfile for our backend container. This container will serve a simple C# API that returns a list of users in JSON format.
Create a new file called "Dockerfile.backend" in your project directory and add the following code:
FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build
WORKDIR /app
COPY . .
RUN dotnet restore
RUN dotnet publish -c Release -o out
FROM mcr.microsoft.com/dotnet/aspnet:6.0
WORKDIR /app
COPY --from=build /app/out .
ENTRYPOINT ["dotnet", "MyCSharpApp.dll"]
This Dockerfile uses the official dotnet SDK as its base and copies our application code to the appropriate directory. It then restores dependencies, builds the application, and publishes it to an output directory.
Finally, it copies the output to the appropriate directory in the final container and sets the entry point to start the application.
Now that we have our Dockerfiles, we can create a Docker Compose file to define our multi-container application.
Create a new file called "docker-compose.yml" in your project directory and add the following code:
version: "3"
services:
frontend:
build:
context: .
dockerfile: Dockerfile.frontend
ports:
- "8080:80"
depends_on:
- backend
backend:
build:
context: .
dockerfile: Dockerfile.backend
ports:
- "5000:5000"
This Docker Compose file defines two services: "frontend" and "backend". The "frontend" service uses the Dockerfile.frontend file we created earlier to build its image. It exposes port 8080 on the host machine, which is mapped to port 80 in the container.
It also depends on the "backend" service, meaning it will not start until the "backend" service is running.
The "backend" service uses the Dockerfile.backend file we created earlier to build its image. It exposes port 5000 on both the host and container and has no dependencies on other services.
We need to add some configuration files that our containers will use to communicate.
Create a new directory called "config" in your project directory, and create two files inside it: "appsettings.json" and "nginx.conf".
In the "appsettings.json" file, add the following code:
{
"BackendBaseUrl": "http://backend:5000"
}
This configuration file defines a variable called "BackendBaseUrl" used by our frontend container to make API calls to our backend container.
In the "nginx.conf" file, add the following code:
server {
listen 80;
server_name localhost;
location / {
proxy_pass http://frontend:80;
proxy_redirect off;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Host $server_name;
}
}
This configuration file defines the NGINX reverse proxy settings, allowing our frontend container to communicate with our backend container.
Now that we have our containers and configuration files, we can write the application code that will run inside them.
Create a new directory called "src" in your project directory, and create two files inside it: "Program.cs" and "UserController.cs".
In the "Program.cs" file, add the following code:
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Hosting;
namespace MyCSharpApp
{
public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
}
}
This code sets up the entry point for our backend container and configures it to use a "Startup" class that we will define in the next step.
In the "UserController.cs" file, add the following code:
using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc;
namespace MyCSharpApp.Controllers
{
[ApiController]
[Route("[controller]")]
public class UserController : ControllerBase
{
private static readonly List<string> Users = new List<string> { "Alice", "Bob", "Charlie" };
[HttpGet]
public ActionResult<IEnumerable<string>> Get()
{
return Ok(Users);
}
}
}
This code defines a simple controller that returns a list of users when an HTTP GET request is made to the "/user" route.
Create a new file called "Startup.cs" in your project directory, and add the following code:
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
namespace MyCSharpApp
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
}
}
This code sets up the middleware for our backend container and adds support for controllers.
Now that we have written our application code, we can build and run our containers using Docker Compose.
In your terminal, navigate to your project directory and run the following command:
docker-compose up
This command will build and start the containers defined in our "docker-compose.yml" file. Once the containers are running, you should be able to access the application by navigating to "http://localhost" in your web browser.
You should see a page that displays a list of users, which is being served by our backend container and demonstrated by our frontend container.
After testing your application, you can stop all the running containers by pressing "Ctrl-C" in your terminal.
This will stop and remove all the containers but will not delete any data or configuration files you created.
This article demonstrates how to create a multi-container application using Docker Compose and C#.
By breaking our application into smaller, more modular containers, we can improve the scalability and maintainability of our application and make it easier to develop and deploy.
Docker Compose provides a powerful toolset for managing complex container-based applications, and C# offers a robust and flexible platform for building web applications.
By combining these two technologies, we can create modern, cloud-native applications that are efficient, reliable, and easy to manage.
C# Publication, LinkedIn, Instagram, Twitter, Dev.to, BuyMeACoffee
Also published here