-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathDockerfile
145 lines (115 loc) · 4.86 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
# syntax=docker/dockerfile:1
# check=error=true
# SPDX-FileCopyrightText: © 2024 Sebastian Davids <[email protected]>
# SPDX-License-Identifier: Apache-2.0
# https://docs.docker.com/engine/reference/builder/
### Installer ###
# https://hub.docker.com/_/node
FROM node:22.12.0-alpine3.20 AS installer
RUN apk --no-cache add upx=4.2.4-r0 && \
upx /usr/local/bin/node
WORKDIR /opt/app/
LABEL de.sdavids.docker.group="sdavids-node-docker-image-slimming" \
de.sdavids.docker.type="builder"
### Bundler ###
# https://hub.docker.com/_/node
FROM node:22.12.0-alpine3.20 AS bundler
WORKDIR /opt/app/
COPY scripts/build.sh scripts/
COPY package.json package-lock.json ./
RUN npm config set ignore-scripts true && \
npm ci --omit optional --omit peer --audit-level=high --silent && \
node node_modules/esbuild/install.js && \
npm cache clean --force
COPY src/server.mjs src/healthcheck.mjs src/
RUN node --run build && \
chmod 400 /opt/app/dist/server.mjs /opt/app/dist/healthcheck.mjs
LABEL de.sdavids.docker.group="sdavids-node-docker-image-slimming" \
de.sdavids.docker.type="builder"
### Harden ###
# https://hub.docker.com/_/alpine
FROM alpine:3.20.3 AS hardened
# https://github.com/hadolint/hadolint/wiki/DL4006
SHELL ["/bin/ash", "-eo", "pipefail", "-c"]
# use apk repositories over HTTPS only
# hadolint ignore=DL3018
RUN echo "https://dl-cdn.alpinelinux.org/alpine/v$(cut -d . -f 1,2 < /etc/alpine-release)/main" > /etc/apk/repositories && \
echo "https://dl-cdn.alpinelinux.org/alpine/v$(cut -d . -f 1,2 < /etc/alpine-release)/community" >> /etc/apk/repositories && \
# add root certificates
apk add --no-cache ca-certificates && \
# add the app user and the working directory
addgroup -g 1001 node && \
adduser -g node -u 1001 -G node -s /sbin/nologin -S -D -h /node node && \
mkdir /node/tmp && \
chmod -R 700 /node && \
chown -R node:node /node && \
# remove unnecessary accounts
sed -i -r "/^(node|root|nobody)/!d" /etc/group && \
sed -i -r "/^(node|root|nobody)/!d" /etc/passwd && \
# remove interactive login shell for everybody
sed -i -r 's#^(.*):[^:]*$#\1:/sbin/nologin#' /etc/passwd && \
# disable password login for everybody
while IFS=: read -r username _; do passwd -l "${username}"; done < /etc/passwd || true && \
# remove account-related temp files
find /bin /etc /lib /sbin /usr -xdev -type f -regex '.*-$' -exec rm -f {} + && \
# remove admin commands
find /sbin /usr/sbin ! -type d -a ! -name apk -a -delete && \
# remove crontabs
rm -rf /var/spool/cron /etc/crontabs /etc/periodic && \
# remove SUID & SGID files
find /bin /etc /lib /sbin /usr -xdev -type f -a \( -perm +4000 -o -perm +2000 \) -delete && \
# remove world-writeable permissions
find / -xdev -type d -perm +0002 -exec chmod o-w {} + && \
find / -xdev -type f -perm +0002 -exec chmod o-w {} + && \
# remove init scripts
rm -rf /etc/init.d /lib/rc /etc/conf.d /etc/inittab /etc/runlevels /etc/rc.conf /etc/logrotate.d && \
# remove kernel tunables
rm -rf /etc/sysctl* /etc/modprobe.d /etc/modules /etc/mdev.conf /etc/acpi && \
# remove root home dir
rm -rf /root && \
# remove fstab
rm -f /etc/fstab && \
# remove symlinks without targets
find /bin /etc /lib /sbin /usr -xdev -type l -exec test ! -e {} \; -delete && \
# ensure system directories are owned and writable only by root
find /bin /etc /lib /sbin /usr -xdev -type d \
-exec chown root:root {} \; \
-exec chmod 0755 {} \; && \
# remove dangerous commands
find /bin /etc /lib /sbin /usr -xdev \( \
-iname chgrp -o \
-iname chmod -o \
-iname chown -o \
-iname hexdump -o \
-iname ln -o \
-iname od -o \
-iname strings -o \
-iname su -o \
-iname sudo \
-iname wget \
\) -delete && \
# remove apk-related files
find /bin /etc /lib /sbin /usr -xdev -type f -regex '.*apk.*' -exec rm -rf {} + && \
rm -rf /etc/apk /lib/apk /usr/share/apk
# use temp dir inside of app user's home
# https://nodejs.org/api/os.html#ostmpdir
ENV TMPDIR=/node/tmp
### Final ###
FROM hardened
WORKDIR /node
COPY --from=installer --chown=node:node /usr/lib/libgcc_s.so.1 /usr/lib/libstdc++.so.6 /usr/lib/
COPY --from=installer --chown=node:node /usr/local/bin/node /usr/bin/
COPY --from=bundler --chown=node:node /opt/app/dist/server.mjs /opt/app/dist/healthcheck.mjs ./
ENV NODE_ENV=production
ENV PORT=3000
USER node:node
EXPOSE 3000
CMD ["node", "server.mjs"]
HEALTHCHECK --interval=5s --timeout=5s --start-period=5s \
CMD node --no-warnings /node/healthcheck.mjs
# https://github.com/opencontainers/image-spec/blob/master/annotations.md
LABEL org.opencontainers.image.licenses="Apache-2.0" \
org.opencontainers.image.vendor="Sebastian Davids" \
org.opencontainers.image.title="healthcheck-js-nodejs" \
de.sdavids.docker.group="sdavids-docker-healthcheck" \
de.sdavids.docker.type="development"