Elixir releases need to be built on the same system as they run on. Seems like Docker is perfect for building the releases and running them.

Well, it’s prefect for building the release. Running in Docker can make some of the BEAM stuff harder, like live code updates and clustering. Looks like there are some solutions for both, but for now I don’t mind just restarting the Docker containers.

This is part of a larger set of posts, see Deploying Elixir Umbrella Apps for an overview.

Docker

Deploying with Docker

This guide was really good. It builds a container on Alpine Linux to build the release, then it builds a second small container used to deploy the app.

Docker worked pretty well without needing too many changes. I’m building a image for each app, Admin & Web using the Dockerfile below.

# The version of Alpine to use for the final image
# This should match the version of Alpine that the `elixir:1.7.2-alpine` image uses
ARG ALPINE_VERSION=3.8

FROM elixir:1.7.3-alpine AS builder

# The following are build arguments used to change variable parts of the image.
# The name of your application/release (required)
ARG APP_NAME
# The version of the application we are building (required)
ARG APP_VSN
# The environment to build with
ARG MIX_ENV=prod
# Set this to true if this release is not a Phoenix app
ARG SKIP_ASSETS=false
# If you are using an umbrella project, you can change this
# argument to the directory the Phoenix app is in so that the assets
# can be built
ARG PHOENIX_SUBDIR=./apps/company_admin

ENV SKIP_ASSETS=${SKIP_ASSETS} \
    APP_NAME=${APP_NAME} \
    APP_VSN=${APP_VSN} \
    MIX_ENV=${MIX_ENV}

# By convention, /opt is typically used for applications
WORKDIR /opt/app

# This step installs all the build tools we'll need
RUN apk update && \
  apk upgrade --no-cache && \
  apk add --no-cache \
    nodejs \
    yarn \
    git \
    build-base && \
  mix local.rebar --force && \
  mix local.hex --force

# This copies our app source code into the build container
COPY . .

RUN mix do deps.get, deps.compile, compile

# This step builds assets for the Phoenix app (if there is one)
# If you aren't building a Phoenix app, pass `--build-arg SKIP_ASSETS=true`
# This is mostly here for demonstration purposes
RUN if [ ! "$SKIP_ASSETS" = "true" ]; then \
  cd ${PHOENIX_SUBDIR}/assets && \
  yarn install && \
  yarn deploy && \
  cd .. && \
  mix phx.digest; \
fi

RUN \
  mkdir -p /opt/built && \
  mix release --name ${APP_NAME} --env ${MIX_ENV} --verbose && \
  cp _build/${MIX_ENV}/rel/${APP_NAME}/releases/${APP_VSN}/${APP_NAME}.tar.gz /opt/built && \
  cd /opt/built && \
  tar -xzf ${APP_NAME}.tar.gz && \
  rm ${APP_NAME}.tar.gz

# From this line onwards, we're in a new image, which will be the image used in production
FROM alpine:${ALPINE_VERSION}

# The name of your application/release (required)
ARG APP_NAME

RUN apk update && \
    apk add --no-cache \
      bash \
      openssl-dev

ENV REPLACE_OS_VARS=true \
    APP_NAME=${APP_NAME}

WORKDIR /opt/app

COPY --from=builder /opt/built .

CMD trap 'exit' INT; /opt/app/bin/${APP_NAME} migrate "" && /opt/app/bin/${APP_NAME} foreground

This is using the Distillery release target we defined earlier.

What’s next

I did setup the docker.env and docker-compose.yml files from the docs, but didn’t setup the database container so the app will start, but can’t connect to my local database unless I allow external connections. I don’t plan to develop using Docker, so it’s more of a check to make sure the image is good.

On to Terraform with Beanstalk

References