I personally love to keep all my projects isolated in one way or another. In some cases –the most complex ones– I need to provision Virtual Machines by using tools like Vagrant and Ansible, but in most of them I can just define a bunch of Docker containers within the project’s context, which is my preferred approach so far for ease and portability.
I wanted to share my experiencie preparing a Docker environment to develop a sample project using the Elixir language and the Phoenix framework.
Let’s dive in:
The application image
In Phoenix projects we have some dependencies related to the Elixir language and the platform that it leverages from: Erlang VM. To easily match those requirements, there’s an official Elixir repo of Docker images, so let’s take advantage of that.
Here’s the Dockerfile
to build the main image. It is the only one in
this sample application since it provides all dependencies to run any
other Phoenix application instances, and we’re just gonna use it in both
development and test environments:
# ./Dockerfile
# Starting from the official Elixir 1.3.2 image:
# https://hub.docker.com/_/elixir/
FROM elixir:1.3.2
MAINTAINER David Anguita <david@davidanguita.name>
ENV DEBIAN_FRONTEND=noninteractive
# Install hex
RUN mix local.hex --force
# Install rebar
RUN mix local.rebar --force
# Install the Phoenix framework itself
RUN mix archive.install --force https://github.com/phoenixframework/archives/raw/master/phoenix_new.ez
# Install NodeJS 6.x and the NPM
RUN curl -sL https://deb.nodesource.com/setup_6.x | bash -
RUN apt-get install -y -q nodejs
# Set /app as workdir
WORKDIR /app
The containers
We’ll use Docker Compose to build and run containers. This sample application will use PostgreSQL 9.5 as database, and let’s say there aren’t extra dependencies in the test environment, to keep things simple:
# ./docker-compose.yml
web:
build: .
dockerfile: Dockerfile # That's our Dockerfile path
env_file: .env # Set environment variables from an `.env` file, if needed
command: mix phoenix.server # Start the server if no other command is specified
environment:
- MIX_ENV=dev # That's the environment mode, you know
- PORT=4000
- PG_HOST=postgres
- PG_USERNAME=postgres
volumes:
- .:/app # Here we're mounting our project's root directory inside the container
ports:
- "4000:4000"
links:
- postgres
test:
image: phoenixbootstrap_web # We're just using the already built `web` image here
env_file: .env
command: mix test # Run the entire test suite if no other command is specified
environment:
- MIX_ENV=test # That's key
- PORT=4001
- PG_HOST=postgres
- PG_USERNAME=postgres
volumes_from:
- web
links:
- postgres
postgres:
image: postgres:9.5 # https://hub.docker.com/_/postgres/
ports:
- "5432"
Configuring the repo
Before starting the containers, let’s configure our Ecto adapter to match the environment settings:
# ./config/dev.exs
config :app, App.Repo,
adapter: Ecto.Adapters.Postgres,
username: System.get_env("PG_USERNAME"),
password: System.get_env("PG_PASSWORD"),
hostname: System.get_env("PG_HOST"),
database: "app_dev",
pool_size: 10
# ./config/test.exs
config :app, App.Repo,
adapter: Ecto.Adapters.Postgres,
username: System.get_env("PG_USERNAME"),
password: System.get_env("PG_PASSWORD"),
hostname: System.get_env("PG_HOST"),
database: "app_test",
pool: Ecto.Adapters.SQL.Sandbox
Running the environment
We should be now ready to go on our Dockerized Phoenix application. Here’s a generic check-list to get everything up and running:
Development environment
# Build the Docker image and start the `web` container, daemonized
$ docker-compose up -d web
# Install application's dependencies and compile them all
$ docker-compose run web mix do deps.get, compile
# Create database and run migrations
$ docker-compose run web mix ecto.create && mix ecto.migrate
# Install (mostly) JS dependencies through `npm`
$ docker-compose run web npm config set strict-ssl false && npm install
# Execute the seeding script, if needed
$ docker-compose run web mix run priv/repo/seeds.exs
# Restart the `web` container to ensure everything's up
$ docker-compose restart web
Your application should just be reachable at
http://<your_docker_host>:4000/
🎉
Test environment
To deal with the test environment, for which we already have a proper container:
# Execute all tests (It's a `$ mix test` actually)
$ docker-compose run test
# Execute tests for specific files
$ docker-compose run test mix test test/models/user_test.exs
So, I’d say that’s a good starting point for covering any other environment needs. Thanks for reading, and please share your thoughts in the comments!