Dockerizing Elixir Umbrella Apps
Stefan Dorresteijn Oct 9 '17
This article was originally posted on Sjauf.
At Sjauf, we wanted to find a way to quickly deploy any of our applications, monitor them all at the same time, upgrade them with only a few clicks and scale them without needing to know all the ins and outs of our servers. We’re a small startup so we don’t employ a DevOps team or even a server administrator. Our solution needed to be easy to understand while remaining robust and secure. Here’s how we did it.
Deploying an application to a server can be a difficult task. Servers have a different architecture than your development machine, tend to run on worse specs and need to be configured from scratch every time you spin one up. On top of that, your development environment might not be the same as your coworker’s. This is why we use Docker to run our applications in containers that can then be deployed to our servers. Because our Docker containers always run with the same architecture with only the tools we tell it to install, we know they will behave the same on our server and our development machines. This solves a lot of our issues but creating a proper Dockerfile and environment for Elixir umbrella applications isn’t as easy as it seems.
Something something, problem problem
When we wanted to create our docker containers, we ran into a few issues. We were building our applications on Mac OSX computers to then start that build in our container. Because our Mac architecture is slightly different from our docker container architecture, the apps wouldn’t run. This meant having to build our applications from the docker container itself.
Another issue we ran into was starting our application while our Postgres server wasn’t running yet. It’s not uncommon (and I would say it’s actually better) to run your Postgres database in a separate container. Using docker-compose, you can then start your entire application — database and apps — all at once. Unfortunately, when you start your Elixir application before your database, Ecto complains and your app doesn’t start. That’s why we added our postgres waiting script, which we run as our starting command. It looks a little something like this:
#!/bin/bash # wait-for-postgres.sh set -e host=$1 username=$2 password=$3 db=$4 start_elixir=$5 until PGPASSWORD="$password" psql -h "$host" -U "$username" -d "$db" -c '\l'; do >&2 echo "Postgres is unavailable - sleeping" sleep 1 done >&2 echo "Postgres is up - executing command" exec $start_elixir
To create a docker image that can be run inside a container, you need a Dockerfile. Here’s how our Dockerfile ended up looking:
FROM ubuntu:14.04.3 # Set the locale, otherwise elixir will complain later on RUN locale-gen en_US.UTF-8 ENV LANG en_US.UTF-8 ENV LANGUAGE en_US:en ENV LC_ALL en_US.UTF-8 # wget for convenience, use curl if you want to # curl for Appsignal # postgresql-9.6 for checking if the server is running yet so we can launch our applications when PG is already up RUN apt-get -y -q install wget curl postgresql postgresql-contrib # add erlang otp RUN wget https://packages.erlang-solutions.com/erlang-solutions_1.0_all.deb RUN dpkg -i erlang-solutions_1.0_all.deb # Update apt libraries RUN apt-get update # Install build essential for Appsignal RUN apt-get install -y build-essential # Install erlang and elixir RUN apt-get install -y -q esl-erlang elixir # Create app folder ADD . /app # Copy wait-for-postgres script. We need this script to check up on Postgres and wait for it to be ready COPY scripts/wait-for-postgres.sh /app/wait-for-postgres.sh # Own wait-for-postgres script so we can run it RUN chmod +x /app/wait-for-postgres.sh # Set app folder to work dir WORKDIR /app # Installs hex locally RUN mix local.hex --force # Installs rebar locally RUN mix local.rebar --force # Clean appsignal. If we don’t do this, it can’t compile on a second build RUN mix deps.clean appsignal # Installs all dependencies for production RUN mix deps.get --only-prod # Compiles application RUN MIX_ENV=prod mix do compile, release --env=prod # Set Port ENV PORT 8080 # Run App CMD [“_build/prod/rel/sjauf_umbrella/bin/sjauf_umbrella”, “foreground”]
The comments in the Dockerfile should give you an idea of what every line does. Because our sub-apps are added as git submodules in our repository, those are always up to date. These commands make sure they’re all compiled and published automatically. Building the application is now as easy as running the docker build command:
docker build -t sjauf/sjauf_umbrella .
In this example, our organisation name is Sjauf and our repository is called sjauf_umbrella. Make sure you name your repository properly so you remember which application you were working on when you look back on this in the future, sitting on the roof of your penthouse in Miami, enjoying an entire bottle of black label with the Miami Dolphins cheerleading team.
I hope this article helps you guys setup your own Elixir Umbrella Applications without too much trouble. More articles of our DevOps antics are soon to follow!