Skip to content

How To Build a Local Development Environment Using Docker

So let’s talk a little bit about development environments, shall we?

We developers are always trying to find the best solution when it comes to creating the best development environment, but that’s not always easy. Things are changing very fast nowadays and if you work on more than one project, things can get a little confusing.

About three years ago I thought I had the best solution for this problem using VirtualBox and Vagrant. I could start up a linux box in minutes and changes made in a project’s box would not affect other project’s environment. That was a nearly perfect solution when it worked. Most of the times things got out of hand when the projects got to big and complex and sometimes it was nearly impossible to recreate exacly the production environment using only one VM, and that’s when this solution failed.

Well, that was the time I first heard about Docker.

At first, the whole concept of containers in Docker kind of scared me, and I was reluctantly to start using this amazing tool. A year passed and I kept banging my head on development environment issues until I finally gave Docker a chance, and I can say that it changed my life.

What is a container

A container image is a lightweight, stand-alone, executable package of a piece of software that includes everything needed to run it: code, runtime, system tools, system libraries, settings. Available for both Linux and Windows based apps, containerized software will always run the same, regardless of the environment. Containers isolate software from its surroundings, for example differences between development and staging environments and help reduce conflicts between teams running different software on the same infrastructure.

Docker containers running on a single machine share that machine’s operating system kernel; they start instantly and use less compute and RAM. Images are constructed from filesystem layers and share common files. This minimizes disk usage and image downloads are much faster.

Installing Docker

If you don’t have Docker installed on your local machine, please refer to the Docker website for instructions on how to install the software depending on your platform.

Docker is available in two editions: Community Edition (CE) and Enterprise Edition (EE).

Docker Community Edition (CE) is ideal for developers and small teams looking to get started with Docker and experimenting with container-based apps and that’s the one we are going to use on development.

With Docker installed, we’re now able to start building our setup.

Project setup

For this example project, I’m using a Node.js app. For development purposes I want the app to run directly on my machine, so I won’t put the app inside a container too. I’m only loading containers for the services I need to run for support and that’s the database server, a mail server (I use mailhog) and a PhpMyAdmin container so I can better access the database.

An excelent tool to automate this kind of building task on Docker is Docker compose, so let’s talk a little bit about it.

Docker Compose

Compose is a tool for defining and running multi-container Docker applications. With Compose, you use a YAML file to configure your application’s services. Then, with a single command, you create and start all the services from your configuration.

sudo curl -L https://github.com/docker/compose/releases/download/1.20.0/docker-compose-`uname -s`-`uname -m` -o /usr/local/bin/docker-compose

On Windows and Mac, compose is already installed.

So, we’re all set, let’s go.

Go inside your project’s folder, create a file called docker-compose.yml.

Now we’ll add the compose version used on this document and create a separate network to add the containers. All the containers from this example are going to be enclosed in this bridged network.

version: '3'

networks:
  dev:
    driver: bridge
    ipam:
      config:
        - subnet: 172.37.0.1/16

Now let’s create a services block to add the containers and add the database container.

For this example we are going to use a MySQL image called ‘database’. We’ll set a volume so we can run .sql scripts at database startup and set a database name and root password. Since we are going to access the database from outside of the docker network, we have to open the database port and we also have to attach this container to the network we created earlier.

After all this changes, the file will look like this:

version: '3'

services:

  database:                                      # service name

    image: mysql:latest                          # docker image
    container_name: database                     # container name

    volumes:
      - ./data:/docker-entrypoint-initdb.d       # binds local 'data' folder to container 'docker-entrypoint-initdb.d' folder

    environment:
      MYSQL_DATABASE: contability                # create default database with this name
      MYSQL_ROOT_PASSWORD: asdfasdf              # set password for root user

    networks:
      - dev                                      # bind this service to a network

    ports:
      - 3306:3306                                # opens this port to the outside world - [outside port]:[container port]

Now let’s add other containers to our compose file, starting with a PhpMyAdmin container.

  pma:

    image: phpmyadmin/phpmyadmin
    container_name: pma

    environment:
      PMA_ARBITRARY: 1
      PMA_HOST: database
      PMA_USER: root
      PMA_PASSWORD: asdfasdf
      PHP_UPLOAD_MAX_FILESIZE: 1G
      PHP_MAX_INPUT_VARS: 1G

    networks:
      - dev

    ports:
      - 8080:80                                  # Forwards port 8080 on the outside to port 80 inside the container

Now a mailhog container

  mailhog:

    image: mailhog/mailhog                       # docker image
    container_name: mailhog                      # container name

    networks:
      - dev                                      # bind this service to a network

    ports:
      - 1025:1025                                # Web interface port
      - 8025:8025                                # SMTP port

The final docker-compose.yml file we’ll look like this:

version: '3'

services:

  database:

    image: mysql:latest
    container_name: database

    volumes:
      - ./data:/docker-entrypoint-initdb.d

    environment:
      MYSQL_DATABASE: contability
      MYSQL_ROOT_PASSWORD: asdfasdf

    networks:
      - dev

    ports:
      - 3306:3306

  pma:

    image: phpmyadmin/phpmyadmin
    container_name: pma

    environment:
      PMA_ARBITRARY: 1
      PMA_HOST: database
      PMA_USER: root
      PMA_PASSWORD: asdfasdf
      PHP_UPLOAD_MAX_FILESIZE: 1G
      PHP_MAX_INPUT_VARS: 1G

    networks:
      - dev

    ports:
      - 8080:80

  mailhog:

    image: mailhog/mailhog
    container_name: mailhog

    networks:
      - dev

    ports:
      - 1025:1025
      - 8025:8025

networks:
  dev:
    driver: bridge
    ipam:
      config:
        - subnet: 172.37.0.1/16

When all of this is set, just run docker-compose up to bring your development environment up. The database will be accessible on localhost:3306, PMA will be on localhost:8080 and mailhog will be on localhost:1025.

Leave a Reply