Skip to content

Commit ec35015

Browse files
authored
Use a tiny base image for Docker builds (#52519)
Closes #51670, closes #50838. Introduce a tiny base image for Docker builds. It aims to create a basic filesystem with as little as possible, which is mostly glibc, busybox and bash. A statically-built curl is also provided. We still use CentOS 8 as a base. All the fun stuff happens in the Dockerfile.
1 parent c24546f commit ec35015

File tree

8 files changed

+316
-51
lines changed

8 files changed

+316
-51
lines changed

distribution/docker/build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ ext.expansions = { Architecture architecture, boolean oss, DockerBase base, bool
5858
RUN curl --retry 8 -S -L \\
5959
--output /opt/elasticsearch.tar.gz \\
6060
https://artifacts.elastic.co/downloads/elasticsearch/$elasticsearch
61-
"""
61+
""".trim()
6262
}
6363

6464
return [

distribution/docker/docker-test-entrypoint.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,4 @@ cd /usr/share/elasticsearch/bin/
44
echo "testnode" > /tmp/password
55
cat /tmp/password | ./elasticsearch-keystore add -x -f -v 'xpack.security.transport.ssl.keystore.secure_password'
66
cat /tmp/password | ./elasticsearch-keystore add -x -f -v 'xpack.security.http.ssl.keystore.secure_password'
7-
/usr/local/bin/docker-entrypoint.sh | tee > /usr/share/elasticsearch/logs/console.log
7+
/usr/local/bin/docker-entrypoint.sh | tee /usr/share/elasticsearch/logs/console.log

distribution/docker/src/docker/Dockerfile

Lines changed: 184 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -14,16 +14,17 @@
1414
layout/presentation here has been adjusted so that it looks reasonable when rendered,
1515
at the slight expense of how it looks here.
1616
*/ %>
17+
<% if (docker_base == "ubi") { %>
1718
################################################################################
1819
# Build stage 0 `builder`:
1920
# Extract Elasticsearch artifact
2021
################################################################################
2122
2223
FROM ${base_image} AS builder
23-
<% if (docker_base == 'ubi') { %>
24+
2425
# Install required packages to extract the Elasticsearch distribution
2526
RUN ${package_manager} install -y tar gzip
26-
<% } %>
27+
2728
# `tini` is a tiny but valid init for containers. This is used to cleanly
2829
# control how ES and any child processes are shut down.
2930
#
@@ -41,21 +42,167 @@ RUN set -eux ; \\
4142
curl --retry 8 -S -L -O https://github.com/krallin/tini/releases/download/v0.19.0/\${tini_bin}.sha256sum ; \\
4243
sha256sum -c \${tini_bin}.sha256sum ; \\
4344
rm \${tini_bin}.sha256sum ; \\
44-
mv \${tini_bin} /tini ; \\
45-
chmod +x /tini
45+
mv \${tini_bin} /bin/tini ; \\
46+
chmod +x /bin/tini
47+
<% } else { %>
48+
################################################################################
49+
# Stage 1. Build curl statically. Installing it from RPM on CentOS pulls in too
50+
# many dependencies.
51+
################################################################################
52+
FROM alpine:latest AS curl
53+
54+
ENV VERSION 7.71.0
55+
ENV TARBALL_URL https://curl.haxx.se/download/curl-\${VERSION}.tar.xz
56+
ENV TARBALL_PATH curl-\${VERSION}.tar.xz
57+
58+
# Install dependencies
59+
RUN apk add gnupg gcc make musl-dev openssl-dev openssl-libs-static file
60+
61+
RUN mkdir /work
62+
WORKDIR /work
63+
64+
# Fetch curl sources and files for validation
65+
RUN wget "https://daniel.haxx.se/mykey.asc" -O "curl-gpg.pub" && \\
66+
wget "\${TARBALL_URL}.asc" -O "\${TARBALL_PATH}.asc" && \\
67+
wget "\${TARBALL_URL}" -O "\${TARBALL_PATH}"
68+
69+
# Validate source
70+
RUN gpg --import --always-trust "curl-gpg.pub" && \\
71+
gpg --verify "\${TARBALL_PATH}.asc" "\${TARBALL_PATH}"
72+
73+
# Unpack and build
74+
RUN tar xfJ "\${TARBALL_PATH}" && \\
75+
cd "curl-\${VERSION}" && \\
76+
./configure --disable-shared --with-ca-fallback --with-ca-bundle=/etc/pki/tls/certs/ca-bundle.crt && \\
77+
make curl_LDFLAGS="-all-static" && \\
78+
cp src/curl /work/curl && \\
79+
strip /work/curl
80+
81+
################################################################################
82+
# Step 2. Create a minimal root filesystem directory. This will form the basis
83+
# for our image.
84+
################################################################################
85+
FROM ${base_image} AS rootfs
86+
87+
ENV BUSYBOX_VERSION 1.31.0
88+
ENV TINI_VERSION 0.19.0
89+
90+
# Start off with an up-to-date system
91+
RUN ${package_manager} update --setopt=tsflags=nodocs -y
92+
93+
# Create a directory into which we will install files
94+
RUN mkdir /rootfs
95+
96+
# Create required devices
97+
RUN mkdir -m 755 /rootfs/dev && \\
98+
mknod -m 600 /rootfs/dev/console c 5 1 && \\
99+
mknod -m 600 /rootfs/dev/initctl p && \\
100+
mknod -m 666 /rootfs/dev/full c 1 7 && \\
101+
mknod -m 666 /rootfs/dev/null c 1 3 && \\
102+
mknod -m 666 /rootfs/dev/ptmx c 5 2 && \\
103+
mknod -m 666 /rootfs/dev/random c 1 8 && \\
104+
mknod -m 666 /rootfs/dev/tty c 5 0 && \\
105+
mknod -m 666 /rootfs/dev/tty0 c 4 0 && \\
106+
mknod -m 666 /rootfs/dev/urandom c 1 9 && \\
107+
mknod -m 666 /rootfs/dev/zero c 1 5
108+
109+
# Install a minimal set of dependencies, and some for Elasticsearch
110+
RUN ${package_manager} --installroot=/rootfs --releasever=/ --setopt=tsflags=nodocs \\
111+
--setopt=group_package_types=mandatory -y \\
112+
--skip-broken \\
113+
install basesystem bash zip zlib
114+
115+
# `tini` is a tiny but valid init for containers. This is used to cleanly
116+
# control how ES and any child processes are shut down.
117+
#
118+
# The tini GitHub page gives instructions for verifying the binary using
119+
# gpg, but the keyservers are slow to return the key and this can fail the
120+
# build. Instead, we check the binary against the published checksum.
121+
#
122+
# Also, we use busybox instead of installing utility RPMs, which pulls in
123+
# all kinds of stuff we don't want.
124+
RUN set -e ; \\
125+
TINI_BIN="" ; \\
126+
BUSYBOX_ARCH="" ; \\
127+
case "\$(arch)" in \\
128+
aarch64) \\
129+
BUSYBOX_ARCH='armv8l' ; \\
130+
TINI_BIN='tini-arm64' ; \\
131+
;; \\
132+
x86_64) \\
133+
BUSYBOX_ARCH='x86_64' ; \\
134+
TINI_BIN='tini-amd64' ; \\
135+
;; \\
136+
*) echo >&2 "Unsupported architecture \$(arch)" ; exit 1 ;; \\
137+
esac ; \\
138+
curl --retry 8 -S -L -O "https://github.com/krallin/tini/releases/download/v0.19.0/\${TINI_BIN}" ; \\
139+
curl --retry 8 -S -L -O "https://github.com/krallin/tini/releases/download/v0.19.0/\${TINI_BIN}.sha256sum" ; \\
140+
sha256sum -c "\${TINI_BIN}.sha256sum" ; \\
141+
rm "\${TINI_BIN}.sha256sum" ; \\
142+
mv "\${TINI_BIN}" /rootfs/bin/tini ; \\
143+
chmod +x /rootfs/bin/tini ; \\
144+
curl --retry 10 -L -o /rootfs/bin/busybox \\
145+
"https://busybox.net/downloads/binaries/\${BUSYBOX_VERSION}-defconfig-multiarch-musl/busybox-\${BUSYBOX_ARCH}" ; \\
146+
chmod +x /rootfs/bin/busybox
147+
148+
# Add links for most of the Busybox utilities
149+
RUN set -e ; \\
150+
for path in \$( /rootfs/bin/busybox --list-full | grep -v bin/sh | grep -v telnet | grep -v unzip); do \\
151+
ln /rootfs/bin/busybox /rootfs/\$path ; \\
152+
done
153+
154+
# Curl needs files under here. More importantly, we change Elasticsearch's
155+
# bundled JDK to use /etc/pki/ca-trust/extracted/java/cacerts instead of
156+
# the bundled cacerts.
157+
RUN mkdir -p /rootfs/etc && \\
158+
cp -a /etc/pki /rootfs/etc/
159+
160+
# Cleanup the filesystem
161+
RUN ${package_manager} --installroot=/rootfs -y clean all && \\
162+
cd /rootfs && \\
163+
rm -rf \\
164+
etc/{X11,centos-release*,csh*,profile*,skel*,yum*} \\
165+
sbin/sln \\
166+
usr/bin/rpm \\
167+
{usr,var}/games \\
168+
usr/lib/{dracut,systemd,udev} \\
169+
usr/lib64/X11 \\
170+
usr/local \\
171+
usr/share/{awk,centos-release,cracklib,desktop-directories,gcc-*,i18n,icons,licenses,xsessions,zoneinfo} \\
172+
usr/share/{man,doc,info,games,gdb,ghostscript,gnome,groff,icons,pixmaps,sounds,backgrounds,themes,X11} \\
173+
usr/{{lib,share}/locale,{lib,lib64}/gconv,bin/localedef,sbin/build-locale-archive} \\
174+
var/cache/yum \\
175+
var/lib/{rpm,yum} \\
176+
var/log/yum.log
177+
178+
# ldconfig
179+
RUN rm -rf /rootfs/etc/ld.so.cache /rootfs/var/cache/ldconfig && \\
180+
mkdir -p --mode=0755 /rootfs/var/cache/ldconfig
181+
182+
COPY --from=curl /work/curl /rootfs/usr/bin/curl
183+
184+
################################################################################
185+
# Step 3. Fetch the Elasticsearch distribution and configure it for Docker
186+
################################################################################
187+
FROM ${base_image} AS builder
188+
<% } %>
46189

47190
RUN mkdir /usr/share/elasticsearch
48191
WORKDIR /usr/share/elasticsearch
49192

193+
# Fetch the appropriate Elasticsearch distribution for this architecture
50194
${source_elasticsearch}
51195

52196
RUN tar zxf /opt/elasticsearch.tar.gz --strip-components=1
197+
198+
# Configure the distribution for Docker
53199
RUN sed -i -e 's/ES_DISTRIBUTION_TYPE=tar/ES_DISTRIBUTION_TYPE=docker/' /usr/share/elasticsearch/bin/elasticsearch-env
54200
RUN mkdir -p config config/jvm.options.d data logs
55201
RUN chmod 0775 config config/jvm.options.d data logs
56202
COPY config/elasticsearch.yml config/log4j2.properties config/
57203
RUN chmod 0660 config/elasticsearch.yml config/log4j2.properties
58204

205+
<% if (docker_base == "ubi") { %>
59206
################################################################################
60207
# Build stage 1 (the actual Elasticsearch image):
61208
#
@@ -65,12 +212,10 @@ RUN chmod 0660 config/elasticsearch.yml config/log4j2.properties
65212

66213
FROM ${base_image}
67214

68-
ENV ELASTIC_CONTAINER true
69-
70215
RUN for iter in {1..10}; do \\
71216
${package_manager} update --setopt=tsflags=nodocs -y && \\
72217
${package_manager} install --setopt=tsflags=nodocs -y \\
73-
nc shadow-utils zip unzip <%= docker_base == 'ubi' ? 'findutils procps-ng' : '' %> && \\
218+
nc shadow-utils zip unzip findutils procps-ng && \\
74219
${package_manager} clean all && exit_code=0 && break || exit_code=\$? && echo "${package_manager} error: retry \$iter in 10s" && \\
75220
sleep 10; \\
76221
done; \\
@@ -80,10 +225,30 @@ RUN groupadd -g 1000 elasticsearch && \\
80225
adduser -u 1000 -g 1000 -G 0 -d /usr/share/elasticsearch elasticsearch && \\
81226
chmod 0775 /usr/share/elasticsearch && \\
82227
chown -R 1000:0 /usr/share/elasticsearch
228+
<% } else { %>
229+
################################################################################
230+
# Stage 4. Build the final image, using the rootfs above as the basis, and
231+
# copying in the Elasticsearch distribution
232+
################################################################################
233+
FROM scratch
234+
235+
# Setup the initial filesystem.
236+
COPY --from=rootfs /rootfs /
237+
238+
RUN addgroup -g 1000 elasticsearch && \\
239+
adduser -D -u 1000 -G elasticsearch -g elasticsearch -h /usr/share/elasticsearch elasticsearch && \\
240+
addgroup elasticsearch root && \\
241+
chmod 0775 /usr/share/elasticsearch && \\
242+
chgrp 0 /usr/share/elasticsearch
243+
<% } %>
244+
245+
ENV ELASTIC_CONTAINER true
83246

84247
WORKDIR /usr/share/elasticsearch
85248
COPY --from=builder --chown=1000:0 /usr/share/elasticsearch /usr/share/elasticsearch
86-
COPY --from=builder --chown=0:0 /tini /tini
249+
<% if (docker_base == "ubi") { %>
250+
COPY --from=builder --chown=0:0 /bin/tini /bin/tini
251+
<% } %>
87252

88253
# Replace OpenJDK's built-in CA certificate keystore with the one from the OS
89254
# vendor. The latter is superior in several ways.
@@ -94,14 +259,15 @@ ENV PATH /usr/share/elasticsearch/bin:\$PATH
94259

95260
COPY bin/docker-entrypoint.sh /usr/local/bin/docker-entrypoint.sh
96261

97-
# The JDK's directories' permissions don't allow `java` to be executed under a different
98-
# group to the default. Fix this.
262+
# 1. The JDK's directories' permissions don't allow `java` to be executed under a different
263+
# group to the default. Fix this.
264+
# 2. Sync the user and group permissions of /etc/passwd
265+
# 3. Set correct permissions of the entrypoint
266+
# 4. Ensure that there are no files with setuid or setgid, in order to mitigate "stackclash" attacks.
99267
RUN find /usr/share/elasticsearch/jdk -type d -exec chmod 0755 '{}' \\; && \\
100268
chmod g=u /etc/passwd && \\
101-
chmod 0775 /usr/local/bin/docker-entrypoint.sh
102-
103-
# Ensure that there are no files with setuid or setgid, in order to mitigate "stackclash" attacks.
104-
RUN find / -xdev -perm -4000 -exec chmod ug-s {} +
269+
chmod 0775 /usr/local/bin/docker-entrypoint.sh && \\
270+
find / -xdev -perm -4000 -exec chmod ug-s {} +
105271

106272
EXPOSE 9200 9300
107273

@@ -138,7 +304,10 @@ RUN mkdir /licenses && \\
138304
<% } %>
139305
USER elasticsearch:root
140306

141-
ENTRYPOINT ["/tini", "--", "/usr/local/bin/docker-entrypoint.sh"]
307+
# Our actual entrypoint is `tini`, a minimal but functional init program. It
308+
# calls the entrypoint we provide, while correctly forwarding signals.
309+
ENTRYPOINT ["/bin/tini", "--", "/usr/local/bin/docker-entrypoint.sh"]
310+
142311
# Dummy overridable parameter parsed by entrypoint
143312
CMD ["eswrapper"]
144313

docs/reference/setup/install/docker.asciidoc

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
[[docker]]
22
=== Install {es} with Docker
33

4-
{es} is also available as Docker images.
5-
The images use https://hub.docker.com/_/centos/[centos:8] as the base image.
4+
{es} is also available as Docker images. Starting with version 8.0.0, these
5+
are based upon a tiny core of essential files. Prior versions used
6+
https://hub.docker.com/_/centos/[centos:8] as the base image.
67

78
A list of all published Docker images and tags is available at
89
https://www.docker.elastic.co[www.docker.elastic.co]. The source files
@@ -424,4 +425,23 @@ You must explicitly accept them either by:
424425
See {plugins}/_other_command_line_parameters.html[Plugin management]
425426
for more information.
426427

428+
The {es} Docker image only includes what is required to run {es}, and does
429+
not provide a package manager. It is possible to add additional utilities
430+
with a multi-phase Docker build. You must also copy any dependencies, for
431+
example shared libraries.
432+
433+
[source,sh,subs="attributes"]
434+
--------------------------------------------
435+
FROM centos:8 AS builder
436+
yum install -y some-package
437+
438+
FROM docker.elastic.co/elasticsearch/elasticsearch:{version}
439+
COPY --from=builder /usr/bin/some-utility /usr/bin/
440+
COPY --from=builder /usr/lib/some-lib.so /usr/lib/
441+
--------------------------------------------
442+
443+
You should use `centos:8` as a base in order to avoid incompatibilities.
444+
Use http://man7.org/linux/man-pages/man1/ldd.1.html[`ldd`] to list the
445+
shared libraries required by a utility.
446+
427447
include::next-steps.asciidoc[]

0 commit comments

Comments
 (0)