gmile
Smallest docker container with elixir app
After docker released native support for multi-stage builds, I decided to make my own small docker container with app.
Below container is based on the one from @bitwalker. It is the smallest one I could come up with in one sitting, given quite limited knowledge of docker & distillery releases:
# Dockerfile
FROM bitwalker/alpine-elixir:1.4.4 as builder
ADD . /app
WORKDIR /app
ENV MIX_ENV=prod
RUN mix do deps.get, deps.compile, release
FROM alpine:3.6
RUN apk add --no-cache \
ca-certificates \
openssl-dev \
ncurses-dev \
unixodbc-dev \
zlib-dev
WORKDIR /app
COPY --from=builder /app/_build/prod/rel/ritm/releases/0.0.1/ritm.tar.gz /app
ENV PORT=4000
RUN tar -xzf ritm.tar.gz; rm ritm.tar.gz
CMD ["bin/ritm", "foreground"]
It’s around 64Mb when pushed to docker hub. That’s a lot, although I don’t expect this to grow significantly (more app code & deps shouldn’t contribute that much).
I am aware guys from @Nerves-Core-Team build super-small linux images with the app installed, but I am not sure what could be derived from their work in order to build my own small container with docker.
How can I reduce the size of container even further?
Most Liked Responses
gmile
In case anyone is interested, this is the container I’m building right now. It uses:
alpine:3.6as a builder/runner image,Erlang 20.1.1Elixir 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
gmile
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.
melpon
I think openssl package is not needed if Erlang is built with ./configure --disable-dynamic-ssl-lib ....









