Running a Phoenix 1.3 project with docker-compose

A clean development environment should be as calming as the desert.

I’ve been looking at job postings, and saw that Elixir has picked up a fair bit of traction. It’s not hard to see why, syntax similar to Ruby, functional constructs like Scala, and running on top of the Erlang VM… sounds like a whole lot of winning in the rush to do more with APIs and use fewer resources.

Given the prevalence of docker for development environments, and the prospect of not having to locally install any of the Elixir/Erlang cruft on my local machine, I set out to do a small project using Phoenix 1.3 in Docker. A lot of the information I found online dealt with Phoenix 1.2 or older. Hopefully this will help others save 15–90 minues of head scratching if they followed a different tutorial.

Setting up the initial Dockerfile.

We will be going with the following software versions:

  • Elixir 1.5
  • Phoenix 1.3
  • PostgreSQL 9.6.3

Go to your base project directory, and fire up your editor of choice. We’ll be creating a Dockerfile that looks like this:

FROM elixir:1.5
MAINTAINER YOUR_NAME_HERE
RUN apt-get update && apt-get install --yes postgresql-client
ADD . /app
RUN mix local.hex --force
RUN mix archive.install --force https://github.com/phoenixframework/archives/raw/master/phx_new.ez
WORKDIR /app
EXPOSE 4000
CMD ["./run.sh"]

There are a few important things in this. First, we’re adding the postgresql client, which will allow Phoenix to nicely talk to the database. The second, is the bit after the local.hex mix call, which installs the Phoenix commands on the container. The last bit, is a little weird, instead of running CMD ["mix", "phx.server"] as our entry command, we’re instead going to run a bash file to bring up the server. I’ll explain why, after we’ve created our new Phoenix project. Before we run this, let’s set up our docker-compose file so that we can get used to running commands through that.

Setting up your docker-compose file:

We know that we’ll want a database running that the Phoenix project can talk to, so we set up a simple docker-compose.yml file. Boot up that editor of yours and create the following file:

version: '3'
services:
web:
build: .
ports:
- "4000:4000"
volumes:
- .:/app
depends_on:
- db
  db:
image: "postgres:9.6.3"

This sets up two services, web and db. Web is our Phoenix application, and db is… well our database. Now that we have that squared away, lets initialize our Phoenix application.

Setting up your Phoenix project:

From your project directory, run a command to initalize the project:

$ docker-compose run --rm web mix phx.new --no-brunch --no-html YOUR_PROJECT_NAME

You might want to use different options when creating your project, in this case it will create a project without the javascript and HTML components. Useful if you’re creating an API service. This will generate a whole bunch of files, and since we’re using a shared volume, those files will appear on our local machine, but we never had to install Erlang, Elixir, or Phoenix!

Let’s connect the database through the config, set up our run.sh file and boot up the project. In your editor, open up config/dev.exs. Find the database configuration part, and modify it to look something like this:

# Configure your database
config :phoenix_aptlist, PhoenixAptlist.Repo,
adapter: Ecto.Adapters.Postgres,
username: "postgres",
password: "postgres",
database: "YOUR_DB_NAME",
hostname: "db",
port: 5432,
pool_size: 10

The hostname needs to match the “name” of the service we defined in the docker-compose.yml file, in this case, db.

Putting it all together:

Remember that run.sh command that we’re using instead of just running the server directly? The main reason for that is our postgres instance. When postgres boots up, it does a fast shutdown and then reboots again with the non-root user. I tried to get around this using the heartbeat functionality in docker-compose, but never had success with it. For now, the annoying workaround is adding a sleep before we boot up the server. Create the run.sh file, looking something like this:

#!/bin/sh
# With help from https://dogsnog.blog/2018/02/02/a-docker-based-development-environment-for-elixirphoenix/
set -e
# Wait for Postgres to become available.
until psql -h db -U "postgres" -c '\q' 2>/dev/null; do
>&2 echo "Postgres is unavailable - sleeping"
sleep 1
done

mix ecto.create
mix ecto.migrate
mix phx.server

Make sure that you add executable properties to your run.sh file.

chmod +x run.sh

Now, we can boot up all of our services with:

docker-compose build
docker-compose up

You should see a bunch of output that initializes the database and runs migrations (which we shouldn’t have any). Your Phoenix server should now accept connections. If you ended up doing a basic server with no html, you can test if it’s up by going to the root page http://localhost:4000 and you should see something about a route not existing.

Congrats! Now it’s time to build out your API or application.