Docker Compose helps you run multi-container applications. In this tutorial, we expand a Docker project, which has only a Dockerfile, with Docker Compose. Before you continue, make sure you have set up Docker properly. If you haven't installed the docker-compose dependency, you can do it with Homebrew on MacOS with the following instruction on the command line:
brew install docker-compose
We will use the following Dockerfile from a Node.js with Docker project as the base. But feel free to use a Dockerfile from another project (e.g. React with create-react-app or custom React with Webpack). Our Dockerfile should look like this:
FROM node:10WORKDIR /usr/src/appCOPY package*.json ./RUN npm installCOPY . .
Notice how we have no EXPOSE
or CMD
statements in this Dockerfile anymore. The Dockerfile is only there to create the application as a Docker image. There are no ports exposed nor any commands to run it. That's where Docker Compose comes into play. First, create a docker-compose.yml file in your project's root directory from the command line:
touch docker-compose.yml
Then copy the following content to it:
version: '3'services:dev:build:context: .ports:- 4680:3000command: npm start
In a Docker Compose file, you can define so-called services. Our first service will only start the application and expose it to a new port by remapping the origin port. Run the following commands on the command line to build the Docker service with the base image from the Dockerfile and to run the service eventually.
docker-compose build devdocker-compose up dev
Before we can visit our application in the browser, we need to find out the IP address of our running Docker engine:
docker-machine ip default-> 192.168.99.100
Finally you should be able to visit http://192.168.99.100:4680
. Be aware that your IP address and port may vary. Congratulations, you have shipped your first application in a Docker container with Docker Compose.
However, there is not much different from the previous Docker setup. Instead of relying only on a Dockerfile, we are using Docker Compose to build and run our container. But it's still only one container (here as a service) running. Let's change this by adding another Docker Compose service in our docker-compose.yml file:
version: '3'services:dev:build:context: .ports:- 4680:3000command: npm starttest:build:context: .environment:- CI=truecommand: npm test
Note: If you haven't setup any test script in your Node.js project yet, or any tests at all, you may want to follow one of these Node.js testing tutorials: Mocha, Chai, and Sinon or Jest. Otherwise, you may want to use any other script that you have at hand for the second service in the Docker Compose file.
We used the CI=true
flag to run all our tests only once, because some test runners (e.g. Jest) would run the tests in watch mode, and thus would never exit the process. Finally, you can start this service as a Docker container. It's only there for testing purposes; which is why it's clearly separated from the dev
service which starts your application for development purposes. We are using the --rm
flag to remove the container automatically after this service terminates.
docker-compose build testdocker-compose run --rm test
All your tests should run through. In addition, sometimes you want your Docker container to write back to your Docker host. In other words, everything that happens in the Docker container and generated new files should be replicated in your project's source code. That's where volumes come into play:
version: '3'services:dev:build:context: .ports:- 4680:3000command: npm starttest:build:context: .environment:- CI=truecommand: npm testvolumes:- './:/usr/src/app'
Now, everything that happens in the Docker container (./usr/src/app
) is written to the Docker host (./
) and vice versa. The relative path for the Docker container should match the WORKDIR
from the Dockerfile. Personally, I had to use this technique to simulate visual regression testing in React in a Docker container while being able to write the result back into my source code.
Congratulations, you now know how to run two Docker services based on one Dockerfile with Docker Compose. Whereas one service simulates the development environment from within a Docker environment, the other service runs all your tests in a Docker environment. Now you can scale your services horizontally. In the end, your CI could take over and execute the test services, linting services, and other services in parallel.