In most cases, applications do not consist of only one monolithic block, but rather of several application services that work together. When using Docker containers, each application service runs in its own container. When we want to run such a multi-service application, we can of course start all the participating containers with the well-known docker container run command. But this is inefficient at best. With the Docker Compose tool, we are given a way to define the application in a declarative way in a file that uses the YAML format.
Let's have a look at the content of a simple docker-compose.yml file:
version: "3.5"
services:
web:
image: fundamentalsofdocker/ch08-web:1.0
ports:
- 3000:3000
db:
image: fundamentalsofdocker/ch08-db:1.0
volumes:
- pets-data:/var/lib/postgresql/data
volumes:
pets-data:
The lines in the file are explained as follows:
- version: In this line, we specify the version of the Docker Compose format we want to use. At the time of writing, this is version 3.5.
- services: In this section, we specify the services that make up our application in the services block. In our sample, we have two application services and we call them web and db:
- web: The web service is using the image fundamentalsofdocker/ch08-web:1.0 from the Docker Hub and is publishing container port 3000 to the host port, also 3000.
- db: The db service, on the other hand, is using the image fundamentalsofdocker/ch08-db:1.0, which is a customized PostgreSQL database. We are mounting a volume called pets-data into the container of the db service.
- volumes: The volumes used by any of the services have to be declared in this section. In our sample, this is the last section of the file. The first time the application is run, a volume called pets-data will be created by Docker and then, in subsequent runs, if the volume is still there, it will be reused. This could be important when the application, for some reason, crashes and has to be restarted. Then, the previous data is still around and ready to be used by the restarted database service.
Navigate to the subfolder ch08 of the labs folder and start the application using Docker Compose:
$ docker-compose up
If we enter the preceding command, then the tool will assume that there must be a file in the current directory called docker-compose.yml and it will use that one to run. In our case, this is indeed the case and the application will start. We should see the output as follows:
The preceding output is explained as follows:
- In the first part of the output, we can see how Docker Compose pulls the two images that constitute our application. This is followed by the creation of a network ch08_default and a volume ch08_pets-data, followed by the two containers ch08_web_1 and ch08_db_1, one for each service, web and db. All the names are automatically prefixed by Docker Compose with the name of the parent directory, which in this case is called ch08.
- After that, we see the logs produced by the two containers. Each line of the output is conveniently prefixed with the name of the service, and each service's output is in a different color. Here, the lion's share is produced by the database and only one line is from the web service.
We can now open a browser tab and navigate to localhost:3000/pet. We should be greeted by a nice cat image and some additional information about the container it came from, as shown in the following screenshot:
Refresh the browser a few times to see other cat images. The application selects the current image randomly from a set of 12 images whose URLs are stored in the database.
As the application is running in interactive mode and thus the Terminal where we ran Docker Compose is blocked, we can cancel the application by pressing Ctrl+C. If we do so, we will see the following:
^CGracefully stopping... (press Ctrl+C again to force)
Stopping ch08_web_1 ... done
Stopping ch08_db_1 ... done
We will notice that the database service stops immediately while the web service takes about 10 seconds to do so. The reason for this being that the database service listens to and reacts to the SIGTERM signal sent by Docker while the web service doesn't, and thus Docker kills it after 10 seconds.
If we run the application again, the output will be much shorter:
This time, we didn't have to download the images and the database didn't have to initialize from scratch, but it was just reusing the data that was already present in the volume pets-data from the previous run.
We can also run the application in the background. All containers will run as daemons. For this, we just need to use the -d parameter, as shown in the following code:
$ docker-compose up -d
Docker Compose offers us many more commands than just up. We can use it to list all services that are part of the application:
This command is similar to docker container ls, with the only difference being that it only lists containers that are part of the application.
To stop and clean up the application, we use the docker-compose down command:
$ docker-compose down
Stopping ch08_web_1 ... done
Stopping ch08_db_1 ... done
Removing ch08_web_1 ... done
Removing ch08_db_1 ... done
Removing network ch08_default
If we also want to remove the volume for the database, then we can use the following command:
$ docker volume rm ch08_pets-data
Why is there a ch08 prefix in the name of the volume? In the docker-compose.yml file, we have called the volume to use pets-data. But as we have already mentioned, Docker Compose prefixes all names with the name of the parent folder of the docker-compose.yml file plus an underscore. In this case, the parent folder is called ch08.