Docker Compose is a lightweight orchestration tool for deploying multi-container applications on a single host machine; it easy to use for Docker users, and does not require extensive configuration.
Docker Compose is part of the toolkit in Docker. The previous essay discusses the vision of Compose. In this essay, we bridge the gap between the vision and the architecture.
What is the framework used to explain the architecture of Docker Compose?
The framework leveraged in this essay aims to facilitate three main learning goals for the reader:
We take inspiration from the “4+1” model to provide an intuitive understanding of Compose’s role within Docker and its end-user. The “4+1” model 1 states a unique viewpoint named “Scenarios” which illustrates use-cases that reflect the sequence of interactions between objects and processes. This essay provides a use-case of how the end-user can use Compose to develop/test their application.
Many standard architectural patterns exist within Software Architecture. This section discusses what is the relevant architectural pattern for Compose and the motivation behind it.
For a formal understanding, the architectural viewpoints discussed by Rozanski & Woods 2 will be used. This is followed by a discussion on what are the trade-offs for the function/non-functional properties of the system.
Since Compose is a simple orchestration tool, many viewpoints are not relevant here. The main criteria for determining the relevancy of the viewpoints are applicability and addressal to significant stakeholders. With Rozanski & Woods 2 the chosen viewpoints are:
What aspects are considered from Rozanski & Woods 2?
|Development||Module Structure Models||Describes the architecture that supports the software development process 2||Developers/Testers|
|Functional||Functional Structure Model||Describes the system’s functional elements, their responsibilities, interfaces, and primary interactions. 2||All|
|Deployment||UML Deployment Model||Describes the environment into which the system will be deployed 2||System Administrators, Developers/Testers|
Compose within Docker Architecture
How does Compose interact with Docker components?
Figure 1 3 gives an overview of Compose’s interaction with other components of Docker. Swarm is also an orchestration tool for scheduling and managing across multiple host machines, whereas Compose deals with a single-host machine.
Where is Compose in the lifecycle of a Docker Application?
Compose can play a role in many stages within an application’s life cycle. Developers can use Compose to run their application locally (enables end-to-end testing). The continuous integration process can use Compose to run and build the app (enables automated testing).
During the testing phase, single-server environments can be deployed for user testing (promotes minimality). During production, Compose can be used for defining the format of the application however, due to several limitations of Compose, other orchestration tools such as Swarm are popular to use. Thus Compose is mainly useful within the developing, testing and staging environments of an application.
Figure 2 shows a use-case of the end-user (developer of an application) using Compose to enable the testing/release of a feature that they are developing. 4
The end-user has three main tasks, namely :
- Define user’s application using Dockerfiles
- Define the services that are needed to run the application within the docker-compose.yml
- Run the application using docker compose up command.
Figure 2 also shows how Compose interacts with the Docker registry (i.e. Docker Hub) to facilitate these tasks. It pulls the images created from the DockerFiles from the Docker registry and uses this to run a multi-container application.
With an understanding of how Compose fits into the Docker architecture, the next sections will delve deeper into the architecture of Compose itself. This means that the focus shifts from the end-user of Compose to other stakeholders of Compose, including business owners and developers/testers.
Relevant Architectural Pattern
What is the architectural pattern applicable for Compose?
While Docker implements a client-server architecture, Compose implements a monolithic architectural pattern.
“A Monolithic application has a single code base with multiple modules. Modules are divided as either for business features or technical features. It has a single build system that builds the entire application and/or dependency. It also has a single executable or deployable binary” 5
A monolithic architecture is self-contained, with components of the software being interdependent and interconnected rather than loosely coupled. 6
What are the advantages and disadvantages of a Monolithic architectural pattern?
The main advantage of a monolithic architecture is simplicity. There is simplicity in deployment due to all actions being able to be performed using a few commands. The pace of development is naturally increased as developers do not have to take into consideration the other tools working with Docker.
The disadvantage of a monolithic architecture is that as new features get introduced, the codebase and dependencies increase. This complicates the workflow, resulting in either building a new toolbox all together (for the new features) or decomposing the toolbox into a new architectural pattern (e.g. micro-services architecture).
Another disadvantage is that monolithic applications are not reusable meaning, one feature implemented in Compose cannot be leveraged into another toolbox of Docker.
The entire Compose toolbox needs to be integrated within the other toolbox for the feature to be used. Lastly, any issue within the monolithic toolbox affects the entire toolbox. This results in harder maintainability and more expensive to test.
What is the motivation behind Docker Compose employing a monolithic architectural pattern?
From the discussion above, it seems that the disadvantages of a monolithic architecture outweigh the advantages. However, these disadvantages become prominent when dealing with complex systems. Compose has limited components and functionalities. Therefore, perhaps the disadvantages of maintainability and feature re-use are not as relevant anymore. Docker has many toolboxes such as Swarm that are achieving Compose’s intentions with more complex functionalities.
This eradicates the need for introducing complex features within Compose itself. It is important to keep in mind the roadmap of Compose (as discussed in Essay 1). It indicates that Compose will be an easy-to-use solution, with commonly-used features. This is the reason why Compose implements a monolithic architecture, and “can get away with it”.
Now that we have an idea of the architectural pattern and its motivation for Compose, let us delve into a formal representation of the architecture of Compose.
As discussed above, the Compose system is organized in a way that it can run multiple Docker containers as services using only a single command, instead of running each container separately (Figure 3).
Figure 4 shows the development view of Compose. The source code of Compose is conceptually organized into 3 main modules:
- Core, source code for core functionalities of Compose
- Script, responsible to build and run Compose core source code
- Tests, define and run functionality, integration, and unit tests for the core source code
The Core module is divided further into submodules (layers) that represent Compose’s main functionalities.
CLI layer is in charge of creating a command-line interface for end-users to run Compose’s feature command. It contains components that define all the CLI commands, configure Docker client and some other CLI utilities. CLI layer calls Config layer to get configurations information and Compose layer to pass on command for them to execute and get information about services and containers.
Config layer’s job is to load all configurations from Docker Compose YAML file and environment variables. Its components read and parse
docker-compose.yml file, detect and parse environment variables or file, and do validation and interpolation for these configuration values. It interacts with Compose layer and uses some utilities from Compose’s Utils component.
Compose layer constructs all the main objects and processes of Docker Compose. The components here are responsible for building projects, services, containers in a service, volumes, and networks by communicating with Docker. It uses Config layers to get configuration information and CLI layers to stream on information of Compose processes.
The runtime view of Docker Compose (see Figure 5) is done with the UML notation of the functional viewpoint provided by Rozanski & Woods 2. The runtime view is meant to display the component’s function, dependencies, and interactions. The methods of interaction from the end-user to the system are mainly through the docker-compose client, the
docker-compose.yml configuration file, and the
.env configuration file. Once Docker Compose deploys the containers in the Docker Daemon, the end-user can interact with the containers through a docker client.
Docker Compose is an orchestrator; it runs as a binary file at OS level in MAC, Windows, and Linux machines. At run time, it manages the startup of Docker images, networks, and volumes, inside a single host machine.
Figure 6 shows a deployment view of a containerized application managed and configured by Docker Compose.
Docker Compose Functional / Non-Functional properties tradeoffs
In software architecture, it is common to divide properties into functional and non-functional; those that are functional are about what the system should do, meanwhile non-functional are about how the system should behave.
For Docker Compose, there are no significant trade-offs between non-functional and functional properties. For example, support for BuildKit, coined as a feature, is meant to improve performance, storage management, security, besides overall functionality. The main reason behind this is that end-users are concerned with how time-consuming, secure, and memory friendly Docker Compose is. For this reason, performance, efficiency, and security become core features. As a result, non-functional properties like integrity, availability, and fault-tolerance could be considered out of scope; that means, it works or does not. Meanwhile, performance, efficiency, and security are considered key features.
Written by Manisha Sethia, Kanya Paramita, Andrea Nardi, Lucio Guerchi
- 4+1 Architectural View Model - CodeOpinion. CodeOpinion. Retrieved from https://codeopinion.com/41-architectural-view-model/
MariaDB Corporation. 2017. Getting Started with MariaDB with Docker. Retrieved March 15, 2020 from https://www.slideshare.net/MariaDB/getting-started-with-mariadb-with-docker-84370473 ↩
Brodie Mitchel. 2019. Dev Environment Setup Docs For Mac. limibuyer. Retrieved March 15, 2020 from http://limibuyer.dx.am/dev-environment-setup-docs-for-mac.html ↩
Monolithic vs Microservice Architecture - DZone Integration. dzone.com. Retrieved March 19, 2020 from https://dzone.com/articles/monolithic-vs-microservice-architecture ↩
Coding the Architecture. 2014. What is a Monolith? - Coding the Architecture. Codingthearchitecture.com. Retrieved from http://www.codingthearchitecture.com/2014/11/19/what_is_a_monolith.html ↩