10. Build multi-container environment with Docker Compose
Introduction
The aim of this learning module is to create a multi-container environment with Docker Compose for an ASP.NET webapp that uses a MongoDB. By the end of this guide, you’ll have a fully functional ASP.NET web app, connected to MongoDB, all running in Docker containers.
Prerequisites
In order to follow along, ensure you have the following tools installed on your machine:
- Docker (Docker Desktop) installed on your local machine. Download Docker.
- Visual Studio Code (VSCode) as your IDE. Download VSCode. docs.microsoft.com/en-us/dotnet/core/tools/).
- .NET SDK 8.0 installed to build .NET applications. Install .NET.
Method
We’ll take a step-by-step approach to containerize your environment:
- Individual Containers: We’ll begin by setting up individual containers for the ASP.NET app and MongoDB using Docker.
- Docker Compose: Transition from manual container management to using Docker Compose for orchestrating our containers in a more manageable, declarative manner (IaC).
Overview of the environment
graph TD app(TodoApp) --> db express(MongoExpress) --> db db(MongoDB) --> vo1(Volume)
Before we begin
Clone the example app from here:
git clone https://github.com/Campus-Molndal/ToDoApp.git
cd ToDoApp
Run the application and verify that it works.
dotnet run
If you get errors when running you might need to run the following command:
dotnet nuget add source https://api.nuget.org/v3/index.json -n nuget.org
Step 1: Setup a MongoDB as a Docker container
MongoDB has its own official Docker image at Docker Hub called mongo
. This container will act as the database for our ASP.NET application. In order to make it available locally for the application we run it like this:
Start MongoDB with Docker:
docker run -d -p 27017:27017 --name db mongo docker ps
Switch the ASP.NET app to use MongoDB by setting an environment variable:
export TODO_SERVICE_IMPLEMENTATION=MongoDb dotnet run
You should now be able to interact with the app and see that it uses MongoDB for data storage.
Bonus: Use Mongo Express for database inspection. It’s a web-based MongoDB admin interface. Start it in Docker and connect it to your MongoDB container.
The docker conainers are launched in a separate docker network with its own IP range. Let’s find out the IP address of the MongoDB container in the default network.
docker inspect db | grep IPAddress
You should get an output like this. The IP address might not be the same.
Output
"SecondaryIPAddresses": null, "IPAddress": "172.17.0.2", "IPAddress": "172.17.0.2",
Use the IP adress you have in your environment and run the Mongo Express container
docker run -d -p 8081:8081 --name mongo-express2 -e ME_CONFIG_MONGODB_SERVER=172.17.0.2 mongo-express
Browse to Mongo Express on
localhost:8081
and find the database and the collection. Verify that it works.
Persist the database
Currently the data persistence of the MongoDB is inside the container. This means that if we remove the container and spin up a new one all data is lost. Let’s create a named volume and attach it to the mongodb container.
docker rm -f db
docker volume create mongodb-data
docker run -d -p 27017:27017 -v mongodb-data:/data/db --name db mongo
Step 2: Containerize the ASP.NET Application
Here we’ll encapsulate our ASP.NET app in a Docker container, following best practices. Microsoft advocates a two-stage approach when containerizing an ASP.NET web application. In the first stage we build and publish the app and in the second stage we build the runtime image based on the artifacts from stage one.
Dockerize the Application: Utilize a multi-stage Dockerfile to build and run your ASP.NET application. This method ensures your Docker image is as lean as possible.
An easy way to create a Dockerfile for the application is to use a Docker wizard:
docker init
Follow the instructions in the wizard. Normally it auto detects the project and you only have to confirm its suggestions by pressing
<Enter>
Now, build the docker image
docker build -t todoapp .
Run Your Containerized App: With your Docker image built, start your application as a Docker container, exposing it on a specific port to access it via a web browser.
Start the todoapp container:
docker run -p 8080:8080 todoapp
Verify it works by browsing to
localhost:8080
. You should see that the app uses the JSON database.
Connect the todoapp to the mongodb container
It’s time to connect the two containers in a similar fashion as with the Mongo Express container.
docker run -p 8080:8080 -e MongoDbSettings__ConnectionString="mongodb://172.17.0.2:27017" -e TODO_SERVICE_IMPLEMENTATION=MongoDb -e ASPNETCORE_ENVIRONMENT=Development todoapp
There are three environment variable that we need to add:
- The MongoDB connection string (in the docker network)
- The Service implementation (MongoDb)
- Run in development mode (since the settings are in
appsettings.Development.json
)
Verify that it works.
Step 3: Use a Docker Network
This step involves configuring our containers to communicate over a private, Docker-managed network.
One of the benefits of using docker is that you don’t have to expose all services to the outer world. In this case we never interacts directly with the MongoDB. Either we do it through the todo app or through Mongo Express.
We can use the Docker Network to keep the database unexposed. Docker also has a local DNS and service discovery function built-in. This means that we can use the docker name to reference it within the Docker Network (however, not in the default network).
First let’s remove all containers. We can do that by levereging command expansion.
Warning: Only do this if you have nothing important running. All containers are removed.
docker rm -f $(docker ps -aq)
First we need to create a Docker Network
docker network create todonet
Next we need to start all the other containers and attach them to the network. In order to use service discovery the containers need to be attached to the same network.
docker run -d --name db --network todonet -v mongodb-data:/data/db mongo
docker run -d -p 8081:8081 --name mongo-express --network todonet -e ME_CONFIG_MONGODB_SERVER=db mongo-express
docker run -d -p 8080:8080 --name todoapp --network todonet -e MongoDbSettings__ConnectionString="mongodb://db:27017" -e TODO_SERVICE_IMPLEMENTATION=MongoDb -e ASPNETCORE_ENVIRONMENT=Development todoapp
Examine the commands thoroughly. Note how the network, names and environment variables are linked together.
Verify that it works.
Step 4: Use Docker Compose
In order to facilitate the orchestration of multiple docker containers we can use Docker Compose. It’s an IaC tool where we can describe an entire docker environment using YAML.
You’ll define a compose.yaml
file, describing your application’s structure, including the ASP.NET app, MongoDB, and any additional services like Mongo Express. This approach enables you to start, stop, and manage your entire environment with simple Docker Compose commands.
First we remove all containers again.
Warning: Only do this if you have nothing important running. All containers are removed.
docker rm -f $(docker ps -aq)
Let’s create a file called compose.yaml
and add this content
compose.yaml
services:
# TodoApp service
todoapp:
image: todoapp
restart: always
ports:
- "8080:8080"
Bring up the todo app by running:
docker compose up -d
Verify that it works
Remove the entire environment by running:
docker compose down
Now, we have our first compose file working. Let’s add the other components - The MongoDB, the volume, the network and Mongo Express.
compose.yaml
services:
# TodoApp service
app:
image: todoapp
restart: always
ports:
- "8080:8080"
environment:
- MongoDbSettings__ConnectionString=mongodb://db:27017
- TODO_SERVICE_IMPLEMENTATION=MongoDb
- ASPNETCORE_ENVIRONMENT=Development
# MongoDB service
db:
image: mongo
restart: always
volumes:
- mongodb-data:/data/db
# Mongo Express service
mongo-express:
image: mongo-express
restart: always
ports:
- "8081:8081"
environment:
- ME_CONFIG_MONGODB_SERVER=db
volumes:
mongodb-data:
Note how the network is not explicitly mentioned. It will be created though. You can see it in the output when running
docker compose
Clean up
With Docker Compose it’s easy to clean up and reset an environment. Run:
docker compose down -v
Note: if you appen -v after the command it will remove the volumes as well
Conclusion
By now, you’ve experienced first-hand how Docker and Docker Compose can dramatically simplify the management of complex applications that span multiple containers.