Smallest docker container with elixir app

Even if I strip 11 Mb, that’d make it 48 - 11 = 37, which is still 2x times than what people here are reporting, e.g. 20 Mb.

I’ll investigate further.

Your ERTS is only 19mb though, of which 11mb is apparently debug info, the rest is presumably system libs and your application code. 20mb is the absolute smallest application people are reporting, not the average. My applications on average are ~50-75mb, so you are well within normal size in my opinion.

:wink: Not quite true …

# in a bare minimum Phoenix project with one C ext package
mix edib --hex --edib edib/edib-tool:latest --strip
# ...
docker images

REPOSITORY          ...  SIZE
local/edib_app_test ...  14.7MB

A simple hello world Elixir app without any real code and deps would be around 10ish MB with stripping.

Though I also wouldn’t recommend using stripping and/or zipping if not really necessary, of course.

In case anyone is interested, this is the container I’m building right now. It uses:

  • alpine:3.6 as a builder/runner image,
  • Erlang 20.1.1
  • Elixir 1.5.2

I like to build my own image instead of a pre-backed image in this case. I like to install elixir/erlang, as well as other packages, instead of compiling them. By looking at base image, I know that there’s no magic going on here.

It’s size, including my apps compiled code and dependencies is 55.5 Mb.

FROM alpine:3.6 as builder

RUN echo "http://dl-cdn.alpinelinux.org/alpine/edge/community/" >> /etc/apk/repositories

RUN apk add --update \
      git \
      erlang=20.1.1-r0 \
      elixir=1.5.2-r0 \
      erlang-crypto \
      erlang-parsetools \
      erlang-syntax-tools \
      erlang-runtime-tools

ADD . /app

WORKDIR /app

ENV MIX_ENV=prod

RUN mix do local.hex --force, \
           local.rebar --force, \
           deps.get, \
           deps.compile, \
           release

FROM alpine:3.6

RUN apk add --no-cache \
      ncurses-libs \
      zlib \
      ca-certificates \
      openssl \
      bash

WORKDIR /app

COPY --from=builder /app/_build/prod/rel/my_app/releases/0.0.1/my_app.tar.gz /app

RUN tar -xzf my_app.tar.gz; rm my_app.tar.gz

I’m relying on distillery for building releases. After the container is built, the app must be started by running bin/my_app foreground in the container:

docker run my_app_image bin/my_app foreground
6 Likes

So, the Dockerfile I’m using has changed significantly. I’m now using two of them:

  • a base one, which is updated much less frequently,
  • and the apps one, which is ran every time I need to pack the app into a container.

This is how they look like:

Base:

# edenlabllc/elixir:1.5.2

FROM alpine:edge

ENV REFRESHED_AT=2017-11-23

RUN apk add --update \
  erlang=20.1.7-r0 \
  elixir=1.5.2-r0 \
  erlang-crypto \
  erlang-parsetools \
  erlang-syntax-tools \
  erlang-runtime-tools \
  git \
  make

Everyday use:

FROM edenlabllc/elixir:1.5.2 as builder

ARG APP_NAME
ARG APP_VERSION

ADD . /app

WORKDIR /app

ENV MIX_ENV=prod

RUN mix do \
      local.hex --force, \
      local.rebar --force, \
      deps.get, \
      deps.compile, \
      release

FROM alpine:edge

ARG APP_NAME
ARG APP_VERSION

RUN apk add --no-cache \
      ncurses-libs \
      zlib \
      ca-certificates \
      openssl \
      bash

WORKDIR /app

COPY --from=builder /app/_build/prod/rel/${APP_NAME}/releases/${APP_VERSION}/${APP_NAME}.tar.gz /app

RUN tar -xzf ${APP_NAME}.tar.gz; rm ${APP_NAME}.tar.gz

ENV REPLACE_OS_VARS=true \
    APP=${APP_NAME}

CMD ./bin/${APP} foreground

In order to build this, I run:

docker build --tag "my_account/my_app:1.2.3" \
             --file Dockerfile \
             --build-arg APP_VERSION=1.2.3. \
             --build-arg APP_NAME=my_app .

I could merge both Dockerfiles, but it just didn’t make sense to re-install the compile-time dependencies every time I needed to build the app. After all, elixir and erlang release versions much less frequently.

6 Likes

I think openssl package is not needed if Erlang is built with ./configure --disable-dynamic-ssl-lib ....

2 Likes

@melpon unfortunately by default erlang for alpine is built using --enable-ssl=dynamic-ssl-lib

1 Like