Docker: 17.03 (Docker for Mac)
Docker Compose: 1.11.2
Ember: 2.12.2
Phoenix: 1.2.1

I'm experimenting with Phoenix as a backend to a new Ember app. Docker and Docker Compose are excellent tools for testing and validating new development tools. This post demonstrates how to set up a Docker Compose environment for Phoenix and Ember development.

Set up Docker Compose

First, create a directory structure for your project with a docker-compose.yml and child directories for your Phoenix back-end and Ember front-end.

It should end up looking something like this:

$ tree -L 1
.
├── docker-compose.yml
├── appname-client
└── appname-server

Then, copy the following into docker-compose.yml.

version: '2'
volumes:
  node_modules:
  bower_components:
  tmp:
  dist:
services:
  db:
    image: postgres:latest
  server:
    image: ntodd/phoenix:1.2.1
    command: mix phoenix.server
    volumes:
      - ./appname-server:/app
    ports:
      - "4000:4000"
    depends_on:
      - db
  client:
    image: ntodd/ember-cli:2.12.2
    command: ember server --live-reload-port 35729
    volumes:
      - ./appname-client:/app
      - node_modules:/app/node_modules
      - bower_components:/app/bower_components
      - tmp:/app/tmp
      - dist:/app/dist
    ports:
      - "4200:4200"
      - "35729:35729"

That's really all there is to it. If you don't care about the details, skip to the Usage section.

Understanding the Docker Compose configuration

Let's parse the docker-compose.yml.

version: '2'
volumes:
  node_modules:
  bower_components:
  tmp:
  dist:

First, this tells Docker Compose that we are using API version 2. Then, we are creating named volumes that we will use later in the Ember service. These named volumes are internal volumes not shared with the host.

services:
  db:
    image: postgres:latest

We need a database for our Phoenix server. This tells Docker Compose to use the latest postgres image.

  server:
    image: ntodd/phoenix:1.2.1
    command: mix phoenix.server
    volumes:
      - ./appname-server:/app
    ports:
      - "4000:4000"
    depends_on:
      - db

This is our Phoenix app. It pulls an image with Elixir 1.4.2 and Phoenix 1.2.1. When running docker-compose up it will run the mix phoenix.server command to give you a development server at port 4000. We define a shared host volume from our phoenix app directory in the host to the app working directory in the docker image. Finally, we tell Docker Compose that our Phoenix app depends on the postgres database service we defined before.

  client:
    image: ntodd/ember-cli:2.12.2
    command: ember server --live-reload-port 35729
    volumes:
      - ./appname-client:/app
      - node_modules:/app/node_modules
      - bower_components:/app/bower_components
      - tmp:/app/tmp
      - dist:/app/dist
    ports:
      - "4200:4200"
      - "35729:35729"

This is our Ember app. It pulls an image with Node 7.0 and Ember 2.12.2. docker-compose up will run the ember server command and force the live-reload-port to 35729. The ports 4200 (Ember CLI) and 35729 (livereload) are forwarded from the host to the image.

The volumes: section is a bit more complex than the Phoenix app. We first define a shared host volume from our ember app directory to the app working directory in the ember image for our application files. We then map the named volumes we created before into the app working directory for our temp, npm and bower directories. This isn't strictly necessary to do, but Ember has a tendency to be very slow on Docker for Mac due to poor performance of shared osxfs volumes when performing a large number of writes. Named volumes don't have the same performance issues, so using them for build and temp directories can increase Ember CLI build performance significantly (around 400% faster in my tests).

Usage

Helpfully, docker-compose can be run from child directories, so you can run commands from within the client and server dirs. Also, run commands will override the command: in the configuration, so it's very easy to run mix or ember commands.

Both ember init and phoenix.new will need to be invoked to ensure the generators run in the current working directory.

Generate new Phoenix app:

# Maybe you also want to pass --no-html if you are only creating an API
$ docker-compose run server mix phoenix.new . --app appname

Generate new Ember-CLI app:

$ docker-compose run client ember init --name appname

I'll leave the setup and configuration of Phoenix and Ember CLI to their excellent documentation resources.


Bonus tip: set alias dc=docker-compose in ~/.zshrc or ~/.bashrc.