Creating a simple and reliable development environment is essential to developer productivity as well as on-boarding new team members. It's far too common for companies to have extremely intricate and fragile development environments. Teams should constantly be improving their local development environments. Small annoyances here and there may not seem like much but remember it impacts every developer using that code base. Let's take a look at one of the most more difficult parts of a developer environment, data stores. We will see how to manage them cross platform utilizing Docker Compose.

What is Docker?

According to their website "Docker is an open platform for developers and sysadmins to build, ship, and run distributed applications, whether on laptops, data center VMs, or the cloud." In layman's terms it's basically an image containing a low level OS and all software preconfigured. Essentially a much more lightweight VM image. Multiple docker containers can run in a single OS unlike VMs where each VM runs an OS of its own eating up resources. Containers can be anything along the lines of a database, document store, message broker, search service, an existing application, or anything else you can think of. Our use case is quite simple, we want MySQL and Elasticsearch running in our development environment without the need to manually install them. This also gives us flexibility to have different docker compose files for different applications and have no need to worry about naming conflicts or different versions of a dependency across projects.

Docker Hub

Docker Hub is a repository of shared docker containers you can readily import. We will be using it as the base for our MySQL and Elasticsearch containers.

Docker Compose

Docker Compose is a tool for defining and running multi-container Docker applications. Each application we have can have it's own docker compose file for a single command boot up of our development environment. Below is our StubbornJava docker compose file. We will take a look at each container later.

version: '2'
services:
  elasticsearch:
    image: docker.elastic.co/elasticsearch/elasticsearch:5.6.1
    restart: always
    container_name: stubbornjava_elasticsearch
    environment:
      - node.name=stubbornjava
      - cluster.name=stubbornjava-cluster
      - http.port=9200
      - transport.tcp.port=9300
      - discovery.zen.minimum_master_nodes=1
      - discovery.type=single-node
      - http.cors.enabled=true
      - node.data=true
      - node.master=true
      - xpack.security.enabled=false
      - bootstrap.memory_lock=true
      - "ES_JAVA_OPTS=-Xms1024m -Xmx1024m"
    ulimits:
      memlock:
        soft: -1
        hard: -1
    volumes:
      - stubbornjava_esdata:/usr/share/elasticsearch/data
    ports:
      - 9200:9200
  mysql:
    build: mysql
    restart: always
    container_name: stubbornjava_mysql
    environment:
      - MYSQL_ROOT_PASSWORD=
      - MYSQL_ALLOW_EMPTY_PASSWORD=yes
    ports:
      - "3306:3306"
    volumes:
      - stubbornjava_mysql:/var/lib/mysql

volumes:
  stubbornjava_esdata:
    driver: local
  stubbornjava_mysql:
    driver: local

Docker Compose MySQL Container Example

We are starting with a base MySQL docker image from Docker Hub and adding some customizations to the my.cnf file. Mainly we want to support full unicode using utf8mb4 see MySQL 8.0: When to use utf8mb3 over utf8mb4? for more info.

MySQL Dockerfile

Starting with our base docker image we then apply our custom my.cnf file as well as run a script to help configure things such as creating databases or users.

FROM mysql:5.7

COPY mysqld.cnf /etc/mysql/mysql.conf.d/mysqld.cnf
COPY ./setup.sh /docker-entrypoint-initdb.d/setup.sh

EXPOSE 3306

CMD ["mysqld"]

MySQL my.cnf

Our custom mysql config file.

[mysqld]
pid-file	            = /var/run/mysqld/mysqld.pid
socket		            = /var/run/mysqld/mysqld.sock
# Where the database files are stored inside the container
datadir		            = /var/lib/mysql

# My application special configuration
max_allowed_packet          = 32M
sql-mode                    = 'STRICT_TRANS_TABLES,NO_ENGINE_SUBSTITUTION'

# Accept connections from any IP address
bind-address	            = 0.0.0.0

character-set-server=utf8mb4
collation-server=utf8mb4_unicode_ci

MySQL Setup script

Create databases and users or anything else needed to bootstrap.

#!/bin/sh

echo 'creating databases'
mysql -u root -e 'CREATE DATABASE IF NOT EXISTS stubbornjava;'

mysql -u root -e 'CREATE DATABASE IF NOT EXISTS sj_access;'

Docker Compose Elasticsearch Contianer Example

Since we are just using the default ES container we don't need a separate Dockerfile here. All of the configuration can be passed in directly from the docker compose file. Take note this is a single server set up which is probably ok for local development or small projects but you will probably want a better setup for non local environments.

Running the dev environment

Simply change directory to where the docker compose file lives and run docker-compose up. You should see logs coming from both containers and you should be up and running.

mysql -u root --host=localhost --protocol=tcp -e 'SHOW DATABASES';
+--------------------+
| Database           |
+--------------------+
| information_schema |
| mysql              |
| performance_schema |
| stubbornjava       |
| sys                |
+--------------------+

You need to specify protocol=tcp since mysql is running in a container. You can also share a socket file between the volume and local OS.

curl localhost:9200
{
  "name" : "stubbornjava",
  "cluster_name" : "stubbornjava-cluster",
  "cluster_uuid" : "-2ALiVFtTr6iJZkip8KXzA",
  "version" : {
    "number" : "5.6.1",
    "build_hash" : "667b497",
    "build_date" : "2017-09-14T19:22:05.189Z",
    "build_snapshot" : false,
    "lucene_version" : "6.6.1"
  },
  "tagline" : "You Know, for Search"
}

Both MySQL and Elasticsearch are up and running.