Files
firezone/elixir/Dockerfile

145 lines
4.1 KiB
Docker

ARG ALPINE_VERSION=3.18.4
ARG OTP_VERSION=26.1.1
ARG ELIXIR_VERSION=1.15.6
ARG BUILDER_IMAGE="firezone/elixir:${ELIXIR_VERSION}-otp-${OTP_VERSION}"
ARG RUNNER_IMAGE="alpine:${ALPINE_VERSION}"
FROM ${BUILDER_IMAGE} as compiler
WORKDIR /app
# Install build deps
RUN apk add --update --no-cache \
make \
git \
nodejs \
npm \
build-base
# Add pnpm
RUN npm i -g pnpm
# Copy only the files needed to fetch the dependencies,
# to leverage Docker layer cache for them
RUN echo "0.0.0" > VERSION
COPY mix.exs mix.lock ./
COPY apps/domain/mix.exs ./apps/domain/mix.exs
COPY apps/web/mix.exs ./apps/web/mix.exs
COPY apps/api/mix.exs ./apps/api/mix.exs
COPY config config
# Fetch and compile the dependencies
ARG MIX_ENV="prod"
RUN mix deps.get --only ${MIX_ENV}
RUN mix deps.compile --skip-umbrella-children
# Copy the files needed to fetch asset deps
COPY apps/web/assets/package.json ./apps/web/assets/
COPY apps/web/assets/pnpm-lock.yaml ./apps/web/assets/
# Install npm deps and assets pipeline
RUN cd apps/web \
&& mix assets.setup
# Copy the files needed to compile application assets
COPY apps/web/assets ./apps/web/assets
COPY apps/web/priv ./apps/web/priv
# Install pipeline and compile assets for Web app
RUN cd apps/web \
&& mix assets.deploy
# Copy the rest of the application files and compile them
COPY VERSION ./
COPY priv priv
COPY apps apps
RUN mix compile
FROM ${BUILDER_IMAGE} as builder
# Install build deps
RUN apk add --update --no-cache \
git
WORKDIR /app
# Copy the compiled dependencies from the previous step
# leveraging the possible layer cache
COPY --from=compiler /app /app
COPY rel rel
ARG APPLICATION_NAME
ARG MIX_ENV="prod"
RUN mix release ${APPLICATION_NAME}
# start a new build stage so that the final image will only contain
# the compiled release and other runtime necessities
FROM ${RUNNER_IMAGE} AS runtime
ENV LANG=C.UTF-8 \
HOME=/app/ \
TERM=xterm
ARG RUNNER_IMAGE
RUN set -xe \
&& ALPINE_MINOR_VERSION=$(echo ${RUNNER_IMAGE} | cut -d ':' -f 2 | cut -d '.' -f 1,2) \
&& echo "@main http://dl-cdn.alpinelinux.org/alpine/v${ALPINE_MINOR_VERSION}/main" >> /etc/apk/repositories \
&& echo "@community http://dl-cdn.alpinelinux.org/alpine/v${ALPINE_MINOR_VERSION}/community" >> /etc/apk/repositories \
&& echo "@edge http://dl-cdn.alpinelinux.org/alpine/edge/main" >> /etc/apk/repositories
RUN set -xe \
# Upgrade Alpine and base packages
&& apk --no-cache --update-cache --available upgrade \
# Install bash, Erlang/OTP and Elixir deps
&& apk add --no-cache --update-cache \
bash \
libstdc++ \
ca-certificates \
ncurses \
openssl \
pcre \
unixodbc \
zlib \
tini \
# Update ca certificates
&& update-ca-certificates --fresh
# Create default user and home directory, set owner to default
RUN set -xe \
mkdir -p /app && \
adduser -s /bin/sh -u 1001 -G root -h /app -S -D default && \
chown -R 1001:0 /app
WORKDIR /app
ARG APPLICATION_NAME
ENV APPLICATION_NAME=$APPLICATION_NAME
# Only copy the final release from the build stage
ARG MIX_ENV="prod"
COPY --from=builder /app/_build/${MIX_ENV}/rel/${APPLICATION_NAME} ./
# Change user to "default" to limit runtime privileges
USER default
# This is critical when you run this container in containers where
# running process would get a PID 1.
#
# BEAM is usually not started by itself but via some shell script (eg. the one generated by
# Elixir 1.9 releases or Distillery) and this script is not designed to become an init script
# for a Docker container and it DOES NOT reap zombie processes.
#
# So whenever a child process runs and terminates inside the same container it would result in memory and
# PID leak to a point where host VM would get unresponsive.
#
# A good example why you would start a process within container is the `ping` command for liveness probes.
# It starts a VM to issue an RPC command to a live node and then terminates, but would never be reaped.
#
# Tini would become an entrypoin script and would take care of zombie reaping no matter how you start the VM.
ENTRYPOINT ["/sbin/tini", "--"]
CMD bin/server