
Testing and building the application using Docker Compose
In the previous section, you used Docker commands to perform the following tasks:
- Build a test image
- Run tests
- Build a release image
- Run the application
Each time we ran a Docker command, we had to supply quite a bit of configuration, and trying to remember the various commands that you need to run is already starting to become difficult. In addition to this, we also discovered that to start the release image for the application, we need to have an operational external database. For local testing use cases, running an external database in another container is an excellent approach, but having to orchestrate this by running a series of Docker commands with lots of different input parameters very quickly becomes difficult to manage.
Docker Compose is a tool that allows you to orchestrate multi-container environments using a declarative approach, making it much easier to orchestrate complex workflows that may require multiple containers. By convention, Docker Compose looks for a file called docker-compose.yml in the current directory, so let's create this file at the root of the todobackend repository, alongside our Dockerfile:
version: '2.4'
services:
test:
build:
context: .
dockerfile: Dockerfile
target: test
release:
build:
context: .
dockerfile: Dockerfile
We first specify the version property, which is mandatory and references the version of the Compose file format syntax that we are using. If you are using Docker for local development and build tasks, I recommending using version 2.x of the Compose file format, as it includes some useful features, such as health checks on dependent services, that we will learn how to use shortly. If you are using Docker Swarm to run your containers, then you should use version 3.x of the Compose file format, as this version supports a number of features that relate to managing and orchestrating Docker Swarm.
We next specify the services property, which defines one or more services that run in our Docker Compose environment. In the preceding example, we create two services that correspond to the test and release stages of our workflow, and then add a single build property to each service, which defines how we want to build the Docker image for each service. Note that the build properties are based upon the various flags that we passed to the docker build command—for example, when we build the test stage image, we set the build context to the local folder, used the local Dockerfile as the build specification for the image, and targeted only the test stage for building the image. Rather than imperatively specifying these settings each time we run a Docker command, we are declaratively defining the desired configuration for the build process, which is an important distinction.
Of course we need to run a command to actually build these services, which you can do by running the docker-compose build command at the root of the todobackend repository:
> docker-compose build test
Building test
Step 1/12 : FROM alpine AS test
---> 3fd9065eaf02
Step 2/12 : LABEL application=todobackend
---> Using cache
---> 23e0c2657711
...
...
Step 12/12 : CMD ["python3", "manage.py", "test", "--noinput", "--settings=todobackend.settings_test"]
---> Running in 1ac9bded79bf
Removing intermediate container 1ac9bded79bf
---> f42d0d774c23
Successfully built f42d0d774c23
Successfully tagged todobackend_test:latest
You can see that running the docker-compose build test command achieves the equivalent of the earlier docker build command we ran, however, we don't need to pass any build options or configuration to the docker-compose command, given all of our specific settings are captured in the docker-compose.yml file.
If you now want to run tests from the newly built image, you can execute the docker-compose run command:
> docker-compose run test
Creating network "todobackend_default" with the default driver
nosetests --verbosity=2 --nologcapture --with-coverage --cover-package=todo --with-spec --spec-color --with-xunit --xunit-file=./unittests.xml --cover-xml --cover-xml-file=./coverage.xml
Creating test database for alias 'default'...
Ensure we can create a new todo item
- item has correct title
- item was created
- received 201 created status code
- received location header hyperlink
...
...
...
...
Ran 12 tests in 0.316s
OK
Destroying test database for alias 'default'...
You can also extend the Docker Compose file to add port mapping and command configurations to services, as demonstrated in the following example:
version: '2.4'
services:
test:
build:
context: .
dockerfile: Dockerfile
target: test
release:
build:
context: .
dockerfile: Dockerfile
ports:
- 8000:8000
command:
- uwsgi
- --http=0.0.0.0:8000
- --module=todobackend.wsgi
- --master
Here we specify that when the release service is run, it should create a static port mapping from port 8000 on the host to port 8000 on the container, and pass the uwsgi command we used earlier to the release container. If you now run the release stage using the docker-compose up command, note that Docker Compose will automatically build the image for a service if it does not yet exist, and then start the service:
> docker-compose up release
Building release
Step 1/22 : FROM alpine AS test
---> 3fd9065eaf02
Step 2/22 : LABEL application=todobackend
---> Using cache
---> 23e0c2657711
...
...
Successfully built 5b20207e3e9c
Successfully tagged todobackend_release:latest
WARNING: Image for service release was built because it did not already exist. To rebuild this image you must use `docker-compose build` or `docker-compose up --build`.
Creating todobackend_release_1 ... done
Attaching to todobackend_release_1
...
...
release_1 | *** uWSGI is running in multiple interpreter mode ***
release_1 | spawned uWSGI master process (pid: 1)
release_1 | spawned uWSGI worker 1 (pid: 6, cores: 1)
release_1 | spawned uWSGI http 1 (pid: 7)