forked from firezone/firezone
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathDockerfile
325 lines (271 loc) · 9.79 KB
/
Dockerfile
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
ARG ALPINE_VERSION="3.19.0"
ARG ERLANG_VERSION="26.1.2"
ARG ERLANG_DOWNLOAD_SHA256="f1074cf3a54f1f87e66027d5abebab2fa76a0243453fa58bc5f30d0ce0313921"
ARG ELIXIR_VERSION="1.15.7"
ARG ELIXIR_DOWNLOAD_SHA256="78bde2786b395515ae1eaa7d26faa7edfdd6632bfcfcd75bccb6341a18e8798f"
FROM alpine:${ALPINE_VERSION} as base
# Important! Update this no-op ENV variable when this Dockerfile
# is updated with the current date. It will force refresh of all
# of the base images and things like `apk add` won't be using
# old cached versions when the Dockerfile is built.
ENV REFRESHED_AT=2023-10-05 \
LANG=C.UTF-8 \
HOME=/app/ \
TERM=xterm
# Add tagged repos as well as the edge repo so that we can selectively install edge packages
ARG ALPINE_VERSION
RUN set -xe \
&& ALPINE_MINOR_VERSION=$(echo ${ALPINE_VERSION} | cut -d'.' -f1,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 runtime dependencies
&& apk add --no-cache --update-cache \
bash \
libstdc++ \
ca-certificates \
ncurses \
openssl \
pcre \
unixodbc \
zlib \
# Update ca certificates
&& update-ca-certificates --fresh
FROM base AS build_erlang
# Install bash and Erlang/OTP deps
RUN set -xe \
&& apk add --no-cache --update-cache --virtual .fetch-deps \
curl \
libgcc \
lksctp-tools \
zlib-dev
# Install Erlang/OTP build deps
RUN set -xe \
&& apk add --no-cache --virtual .build-deps \
dpkg-dev \
dpkg \
gcc \
g++ \
libc-dev \
linux-headers \
make \
autoconf \
ncurses-dev \
openssl-dev \
unixodbc-dev \
lksctp-tools-dev \
tar
# Download OTP
ARG ERLANG_VERSION
ARG ERLANG_DOWNLOAD_SHA256
WORKDIR /tmp/erlang-build
RUN set -xe \
&& curl -fSL -o otp-src.tar.gz "https://github.com/erlang/otp/releases/download/OTP-${ERLANG_VERSION}/otp_src_${ERLANG_VERSION}.tar.gz" \
&& tar -xzf otp-src.tar.gz -C /tmp/erlang-build --strip-components=1 \
# && sha256sum otp-src.tar.gz && exit 1 \
&& echo "${ERLANG_DOWNLOAD_SHA256} otp-src.tar.gz" | sha256sum -c -
# Configure & Build
RUN set -xe \
&& export ERL_TOP=/tmp/erlang-build \
&& export CPPFLAGS="-D_BSD_SOURCE $CPPFLAGS" \
&& export gnuArch="$(dpkg-architecture --query DEB_HOST_GNU_TYPE)" \
&& ./configure \
--build="$gnuArch" \
--prefix=/usr/local \
--sysconfdir=/etc \
--mandir=/usr/share/man \
--infodir=/usr/share/info \
--without-javac \
--without-jinterface \
--without-wx \
--without-debugger \
--without-observer \
--without-cosEvent \
--without-cosEventDomain \
--without-cosFileTransfer \
--without-cosNotification \
--without-cosProperty \
--without-cosTime \
--without-cosTransactions \
--without-et \
--without-gs \
--without-ic \
--without-megaco \
--without-orber \
--without-percept \
--without-odbc \
--without-typer \
--enable-threads \
--enable-shared-zlib \
--enable-dynamic-ssl-lib \
--enable-ssl=dynamic-ssl-lib \
$(if [[ "${TARGET}" != *"amd64"* ]]; then echo "--disable-jit"; fi) \
&& $( \
if [[ "${TARGETARCH}" == *"amd64"* ]]; \
then export CFLAGS="-g -O2 -fstack-clash-protection -fcf-protection=full"; \
else export CFLAGS="-g -O2 -fstack-clash-protection"; fi \
) \
&& make -j$(getconf _NPROCESSORS_ONLN)
# Install to temporary location, strip the install, install runtime deps and copy to the final location
RUN set -xe \
&& make DESTDIR=/tmp install \
&& cd /tmp && rm -rf /tmp/erlang-build \
&& find /tmp/usr/local -regex '/tmp/usr/local/lib/erlang/\(lib/\|erts-\).*/\(man\|doc\|obj\|c_src\|emacs\|info\|examples\)' | xargs rm -rf \
&& find /tmp/usr/local -name src | xargs -r find | grep -v '\.hrl$' | xargs rm -v || true \
&& find /tmp/usr/local -name src | xargs -r find | xargs rmdir -vp || true \
# Strip install to reduce size
&& scanelf --nobanner -E ET_EXEC -BF '%F' --recursive /tmp/usr/local | xargs -r strip --strip-all \
&& scanelf --nobanner -E ET_DYN -BF '%F' --recursive /tmp/usr/local | xargs -r strip --strip-unneeded \
&& runDeps="$( \
scanelf --needed --nobanner --format '%n#p' --recursive /tmp/usr/local \
| tr ',' '\n' \
| sort -u \
| awk 'system("[ -e /tmp/usr/local/lib/" $1 " ]") == 0 { next } { print "so:" $1 }' \
)" \
&& ln -s /tmp/usr/local/lib/erlang /usr/local/lib/erlang \
&& /tmp/usr/local/bin/erl -eval "beam_lib:strip_release('/tmp/usr/local/lib/erlang/lib')" -s init stop > /dev/null \
&& (/usr/bin/strip /tmp/usr/local/lib/erlang/erts-*/bin/* || true) \
&& apk add --no-cache --virtual .erlang-runtime-deps $runDeps lksctp-tools ca-certificates
# Cleanup after Erlang install
RUN set -xe \
&& apk del .fetch-deps .build-deps \
&& rm -rf /var/cache/apk/*
WORKDIR ${HOME}
CMD ["erl"]
FROM base AS build_elixir
# Install Elixir build deps
RUN set -xe \
&& apk add --no-cache --virtual .build-deps \
make \
curl \
tar \
git
# Download Elixir
ARG ELIXIR_VERSION
ARG ELIXIR_DOWNLOAD_SHA256
WORKDIR /tmp/elixir-build
RUN set -xe \
&& curl -fSL -o elixir-src.tar.gz "https://github.com/elixir-lang/elixir/archive/refs/tags/v${ELIXIR_VERSION}.tar.gz" \
&& mkdir -p /tmp/usr/local/src/elixir \
&& tar -xzC /tmp/usr/local/src/elixir --strip-components=1 -f elixir-src.tar.gz \
# && sha256sum elixir-src.tar.gz && exit 1 \
&& echo "${ELIXIR_DOWNLOAD_SHA256} elixir-src.tar.gz" | sha256sum -c - \
&& rm elixir-src.tar.gz
COPY --from=build_erlang /tmp/usr/local /usr/local
# Compile Elixir
RUN set -xe \
&& cd /tmp/usr/local/src/elixir \
&& make DESTDIR=/tmp install clean \
&& find /tmp/usr/local/src/elixir/ -type f -not -regex "/tmp/usr/local/src/elixir/lib/[^\/]*/lib.*" -exec rm -rf {} + \
&& find /tmp/usr/local/src/elixir/ -type d -depth -empty -delete \
&& rm -rf /tmp/elixir-build \
&& apk del .build-deps
# Cleanup apk cache
RUN rm -rf /var/cache/apk/*
WORKDIR ${HOME}
CMD ["iex"]
FROM base as elixir
WORKDIR ${HOME}
# Copy Erlang/OTP and Elixir installations
COPY --from=build_erlang /tmp/usr/local /usr/local
COPY --from=build_elixir /tmp/usr/local /usr/local
# Install hex + rebar
RUN set -xe \
&& mix local.hex --force \
&& mix local.rebar --force
CMD ["bash"]
FROM elixir 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
# Tailwind needs assets and app directories to look for used classes,
# so we can't optimize further at this stage
COPY VERSION ./
COPY priv priv
COPY apps apps
# 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
RUN mix compile
FROM elixir 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 base AS runtime
RUN set -xe \
# Install Firezone runtime deps
&& apk add --no-cache --update-cache \
curl \
tini
# 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
# Add other directories firezone needs to run
RUN set -xe \
&& mkdir -p /var/firezone \
&& chown -R 1001:0 /var/firezone
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