Docker Compose

Docker is software designed to package an application into a lightweight, portable, and fast self-contained environment. This environment is known as a container.

Docker Compose is a native Docker tool that allows you to manage applications comprised of several Docker containers. It allows you to create a full application, spread amongst several containers, through one configuration file (YAML) and one command.

Each Docker container has a unique Dockerfile, and a single Docker Compose file docker-compose.yml defines what images to use and their dependency upon each other. The application’s containers can then be managed using the docker-compose command line interface.

Use Case

A best practice is to have a single process per Docker container - a process that will kill the container when it dies, and vice versa. This is to promote speed, security, and portability of containers.

The issue with this is modern software often has multiple components. For example, most web servers need databases to store non-volatile data. It would be inefficient to run both, say, Apache and MySQL in the same container, so it should be split into two containers. But, they still need to communicate with eachother and be managed at the same time. That is where docker-compose comes in.

Example

Solution Stack software is commonly used today, and one example is the ELK stack.

One advantage of Docker and Docker Compose is the ability to spin up instances of these stacks in seconds, as opposed to hours or days of manual configuration, troubleshooting, and fiddling.

A FOSS project known as docker-elk does the heavy lifting of writing each Dockerfile and packaging it all up. It then leverages Docker Compose to create all ELK stack components at once.

This is the docker-compose.yml:

version: '3.2'

services:
  elasticsearch:
    build:
      context: elasticsearch/
      args:
        ELK_VERSION: $ELK_VERSION
    volumes:
      - type: bind
        source: ./elasticsearch/config/elasticsearch.yml
        target: /usr/share/elasticsearch/config/elasticsearch.yml
        read_only: true
      - type: volume
        source: elasticsearch
        target: /usr/share/elasticsearch/data
    ports:
      - "9200:9200"
      - "9300:9300"
    environment:
      ES_JAVA_OPTS: "-Xmx256m -Xms256m"
      ELASTIC_PASSWORD: changeme
      # Use single node discovery in order to disable production mode and avoid bootstrap checks
      # see https://www.elastic.co/guide/en/elasticsearch/reference/current/bootstrap-checks.html
      discovery.type: single-node
    networks:
      - elk

  logstash:
    build:
      context: logstash/
      args:
        ELK_VERSION: $ELK_VERSION
    volumes:
      - type: bind
        source: ./logstash/config/logstash.yml
        target: /usr/share/logstash/config/logstash.yml
        read_only: true
      - type: bind
        source: ./logstash/pipeline
        target: /usr/share/logstash/pipeline
        read_only: true
    ports:
      - "5000:5000/tcp"
      - "5000:5000/udp"
      - "9600:9600"
    environment:
      LS_JAVA_OPTS: "-Xmx256m -Xms256m"
    networks:
      - elk
    depends_on:
      - elasticsearch

  kibana:
    build:
      context: kibana/
      args:
        ELK_VERSION: $ELK_VERSION
    volumes:
      - type: bind
        source: ./kibana/config/kibana.yml
        target: /usr/share/kibana/config/kibana.yml
        read_only: true
    ports:
      - "5601:5601"
    networks:
      - elk
    depends_on:
      - elasticsearch

networks:
  elk:
    driver: bridge

volumes:
  elasticsearch:

Running

We can begin by cloning the Github project:

[mehlj@docker compose]$ git clone https://github.com/deviantony/docker-elk.git
Cloning into 'docker-elk'...
remote: Enumerating objects: 10, done.
remote: Counting objects: 100% (10/10), done.
remote: Compressing objects: 100% (9/9), done.
remote: Total 1530 (delta 2), reused 4 (delta 1), pack-reused 1520
Receiving objects: 100% (1530/1530), 374.45 KiB | 5.85 MiB/s, done.
Resolving deltas: 100% (599/599), done.

According to the README.md, we must modify the SELinux context of the new directory:

[mehlj@docker compose]$ sudo chcon -R system_u:object_r:admin_home_t:s0 docker-elk/

If we do not have docker-compose installed yet, we can do this by first downloading the latest version:

[mehlj@docker docker-elk]$ sudo curl -L "https://github.com/docker/compose/releases/download/1.25.3/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100   617  100   617    0     0   3485      0 --:--:-- --:--:-- --:--:--  3485
100 16.4M  100 16.4M    0     0  11.9M      0  0:00:01  0:00:01 --:--:-- 20.4M

Change the permissions:

[mehlj@docker docker-elk]$ sudo chmod +x /usr/local/bin/docker-compose

Add it to our $PATH:

[mehlj@docker docker-elk]$ sudo ln -s /usr/local/bin/docker-compose /usr/bin/docker-compose

Confirm it works:

[mehlj@docker docker-elk]$ docker-compose --version
docker-compose version 1.25.3, build d4d1b42b

Now, we can start our ELK stack using the one-liner docker-compose up (-d for background):

[mehlj@docker docker-elk]$ docker-compose up -d

Visit http://localhost:9200 for raw ElasticSearch data:

{
  "name" : "d00c8cf2b493",
  "cluster_name" : "docker-cluster",
  "cluster_uuid" : "W3SqhjoOS3KygA2CTLlT3A",
  "version" : {
    "number" : "7.5.1",
    "build_flavor" : "default",
    "build_type" : "docker",
    "build_hash" : "3ae9ac9a93c95bd0cdc054951cf95d88e1e18d96",
    "build_date" : "2019-12-16T22:57:37.835892Z",
    "build_snapshot" : false,
    "lucene_version" : "8.3.0",
    "minimum_wire_compatibility_version" : "6.8.0",
    "minimum_index_compatibility_version" : "6.0.0-beta1"
  },
  "tagline" : "You Know, for Search"
}

Visit http://localhost:5601 for Kibana access, and so forth.

After usage, we can quickly destroy our stack using docker-compose (-v flag deletes the persistent volume data):

[mehlj@docker docker-elk]$ docker-compose down -v
Written on February 10, 2020