# Using Docker Compose

### What is Docker Compose?

Essentially, what Docker Compose is, is a recipe card — it’s a recipe of services that make up an application and the `docker-compose.yml` dictates how the services are mixed together.

In more technical terms:

> Docker-Compose defines a complete system where services can communicate with each other on an isolated network, interacting with external resources as defined by the user.

Docker Compose is built on top of the Docker Engine, the very same engine that runs the Dockerfiles and Docker CLI, but the most important aspect to keep in mind about Compose is to think of it like a recipe.

The image below, illustrates it pretty well.

![](https://miro.medium.com/max/1400/1*6fRugPB9AEYWNga6Jg2_dg.png)

Above is what a Docker Compose environment looks like when running. Application A is a backend micro service that can freely communicate with App B and App C but that is all, it cannot communicate with the host in the outside world at all. This is because of how its `docker-compose.yml` is set up, I’ll show that next.

Application B is a database, it has been structured in the `docker-compose.yml` with an externally mounted volume `volumes: — opt/app:/opt/appB/app` that allows it to write a persistent data layer to the outside world. With this data layer in the application, even if the entire Docker environment is destroyed for some reason, when it’s brought back up, the database will see the data written to it before, and replace it so you can begin where you were before.

Finally, Application C is the user interface portion of the app. Also, due to the `docker-compose.yml`, this application can talk to the host machine on a port that’s been exposed: `ports="8080:8080"`. This means, a user can open up [http://localhost:8080](http://localhost:8080/) on their machine and access the running application.

And that’s what the environment looks like. This is a pretty simple representation, but it can be that simple, or it can be much more complex depending on your needs. Now, let’s look at what a Docker Compose YAML file looks like and go through some of the nuances that can come with it.

### How to Docker Compose?

The last big question is: how to write a `docker-compose.yml`, and it’s actually very easy and follows a standard formula.

Here is a template of what any `docker-compose.yml` will look like.

* **Sample Docker Compose Template**

![](https://miro.medium.com/max/1196/1*aoqVz3DDSJ7JR0qUp05pcA.png)

Every `docker-compose` file will start with a minimum of `version: "2"`, if you’re doing a Docker Swarm file it will need `version: "3"`, but for a single `docker-compose.yml`, you’ll need v2.

Next, you will list all your services (each app running in the `docker-compose`will have its own name, and you’ll need to provide _**either**_ a `build` or an `image` from the Docker registry. You _**cannot**_ have both.

* A custom Docker image will be built based on the Dockerfile provided by the build path OR
* Docker hub will supply all the necessary commands and setup to make the image build and run successfully.

Underneath that, environment variables are set, ports are exposed, and external volumes are mounted. All these same things could be done with the Docker CLI, but this is a cleaner, easier to read file that doesn’t require remembering (and correctly typing) them all when executing commands in the terminal.

Here is what a sample `docker-compose.yml` could look like.

* **Sample Docker Compose Yaml**

![](https://miro.medium.com/max/932/1*qv-9s_lV5F5Pi6231eYruw.png)

This `docker-compose.yml` begins with `version: "2"` (as required for Docker Compose), and then we have three services listed below: `appA`, `appB`, `appC`. Since `appA` is the UI that is built from a custom `Dockerfile` and needs to be exposed to the host machine, it has a `build:` path to said `Dockerfile` and a `ports:` that maps the internally running port of the UI, port 80 to the same port on the host machine, also 80.

`appB` is a mongo database, so it can be pulled down from the Docker registry when it’s time to start up the application. There’s no build to be done ahead of time, nothing but declaring the image, and for data persistence adding a `volumes:` specification of where database data can be persisted to the host machine and it’s good to go.

And `appC` is also a custom Docker image, so it has a `build:` path to its own Dockerfile, an `environment:` variable specifying a configuration file for the backend, and not one but two `instances:`. This application in particular can have two instances because it is not being exposed in any way to the host machine. If a port was being opened (or it was some type of database), it would only be able to have one instance to avoid port collisions on the host or to avoid writing to two separate databases within the Docker environment. But since this is purely a backend service, it can have as many instances as necessary (good for load balancing heavy volumes of traffic), and all of them can run as separate containers within the Docker environment and communicate with each other as needed.

I recommend reading the [Docker Compose documentation](https://docs.docker.com/compose/overview/) for more examples and descriptions of all the things you can do with within a `docker-compose.yml`.

Ok, the `docker-compose.yml` has been written, let’s look at the Docker Compose commands needed to get this system up and running. Don’t worry, they’re not very complicated.

### Docker Compose Commands

Before I go any further, I must issue the following disclaimer.

**DISCLAIMER: For the purposes of this blog post, it will be assumed all docker-compose commands will be executed in the same directory as the `docker-compose.yml`. Yes, it is possible to execute the commands from different levels of the directory, but that is outside of the scope of this blog about Docker Compose.**

Now that that’s been made clear, I can continue. There’s just a handful of Docker commands you’ll need to type in to the terminal to get these magical files to do their thing. And after I’ve described them, I’ve provided another handy graphic that illustrates the three different phases a Docker Compose environment can exist in.

* **`docker-compose ps` —** lists all the services in a network. This is especially helpful when troubleshooting a service as it will give you the container ID and you can then run `docker -it exec <ID> bash` to enter the container and debug as needed.
* **`docker-compose build` —** generates any needed images from custom `Dockerfiles`. It will not pull images from the Docker hub, only generate custom images.
* **`docker-compose up` —** brings up the network for the services to run in
* **`docker-compose stop` —** stops the network and saves the state of all the services
* **`docker-compose start` —** restarts the services and brings them back up with the state they had when they were stopped
* **`docker-compose down` —** burns the entire Docker network with fire. The network and all the services contained within are totally destroyed.

![](https://miro.medium.com/max/1400/1*X98d4dsk_ymmjqnM0ne4Xg.png)

Here’s the various states the Docker Compose environment exists in, depending on what commands you’ve run.

`docker-compose build` and `docker-compose down` mean the Docker environment _**is not**_ running, and the network _**does not**_ exist.

`docker-compose up` and `docker-compose start` means the Docker environment _**is**_ running, and the network _**does**_ exist.

`docker-compose stop` means the Docker environment _**is not**_ running, but the network still _**does**_ exist.

That’s it. All the commands you’ll need with your `docker-compose.yml` to build and run all your connected microservices.

### Docker Compose Examples

{% embed url="https://github.com/kumartarun/docker-compose-example" %}

### Example - 01&#x20;

Without docker compose , we will build image from docker file and run it&#x20;

```
FROM node:carbon

# create the app directory for inside the Docker image
WORKDIR /usr/src/app

# install app dependencies from the package.json (and the package-lock.json)
COPY package*.json ./

RUN npm install

# bundle app source inside Docker image
COPY . .

# expose port 8080 to have it mapped by Docker daemon
EXPOSE 8080

# define the command to run the app (it's the npm start script from the package.json)
# npm start
CMD [ "npm", "start" ]
```

* [ ] `cd node-example`
* [ ] `docker build -t example1/node-example .`
* [ ] `docker run -p 3003:8080 -d example1/node-example`
* [ ] Go to [http://localhost:3003](http://localhost:3003/)

### Example -2&#x20;

Our basic docker-compose file to spin Node js and java container&#x20;

```
version: "3"

services:
  java-example:
    build: java-example/
    ports:
      - 3001:8080
  node-example:
    build: node-example/
    ports:
      - 3002:8080
```

* [ ] `docker-compose build`
* [ ] `docker-compose up`
* [ ] Go to [http://localhost:3001](http://localhost:3001/)
* [ ] And go to [http://localhost:3002](http://localhost:3002/)

Example -3&#x20;

```
version: "3"

services:
  web:
    image: nginx
    volumes:
     - ./nginx/localhost.conf:/etc/nginx/conf.d/localhost.conf
    ports:
     - "80:80"
    environment:
     - NGINX_HOST=localhost
     - NGINX_PORT=80
    command: /bin/bash -c "envsubst < /etc/nginx/conf.d/localhost.conf > /etc/nginx/conf.d/default.conf && nginx -g 'daemon off;'"
    depends_on:
      - java-example
      - node-example
  java-example:
    build: java-example/
  node-example:
    build: node-example/
```

* [ ] `docker-compose build`
* [ ] `docker-compose up`
* [ ] Go to [http://localhost/java/](http://localhost/java/)
* [ ] And go to [http://localhost/node](http://localhost/node)
