From 59cdb5082a740b31c9922ea5911cf5cf9868975d Mon Sep 17 00:00:00 2001 From: matt-domsch-sp Date: Tue, 22 Apr 2025 03:50:35 +0000 Subject: [PATCH 1/3] Produce distroless minimized container images This change produces a minimal container image (~25MB) containing only the entrypoint bash (static) executable, the split-sync or split-proxy executable, and the minimal Debian files needed for these to execute. The dependency on the `tr` application was removed from functions.sh by using bash variable parameter expansion instead. We use a static bash executable to minimize libraries needed to include in the image. Bash executes only briefly in the entrypoint which then exec's into the application. split-sync and split-proxy are dynamically linked with libc because the go net library requires it. Therefore, libc is copied into the final image too. ca-certificates are copied into the image so TLS connections to split.io can be validated. The last build stage is used to produce a single-layer final container. --- docker/Dockerfile.proxy | 45 ++++++++++++++++++++++----------- docker/Dockerfile.synchronizer | 46 +++++++++++++++++++++++----------- docker/entrypoint.sh.tpl | 2 +- docker/functions.sh | 5 ++-- 4 files changed, 65 insertions(+), 33 deletions(-) diff --git a/docker/Dockerfile.proxy b/docker/Dockerfile.proxy index 2369cb27..8ddee394 100644 --- a/docker/Dockerfile.proxy +++ b/docker/Dockerfile.proxy @@ -5,22 +5,8 @@ ARG EXTRA_BUILD_ARGS ARG FIPS_MODE RUN apt update -y -RUN apt install -y build-essential ca-certificates python3 git +RUN apt install -y build-essential ca-certificates python3 git bash-static -WORKDIR /code - -COPY . . - -RUN bash -c 'if [[ "${FIPS_MODE}" = "enabled" ]]; \ - then echo "building in fips mode"; make clean split-proxy-fips entrypoints EXTRA_BUILD_ARGS="${EXTRA_BUILD_ARGS}"; mv split-proxy-fips split-proxy; \ - else echo "building in standard mode"; make clean split-proxy entrypoints EXTRA_BUILD_ARGS="${EXTRA_BUILD_ARGS}"; \ - fi' - -# Runner stage -FROM debian:12.10 AS runner - -RUN apt update -y -RUN apt install -y bash ca-certificates RUN addgroup --gid 1000 --system 'split-proxy' RUN adduser \ --disabled-password \ @@ -31,10 +17,39 @@ RUN adduser \ --uid 1000 \ 'split-proxy' +WORKDIR /code + +COPY . . + +RUN bash -c 'if [[ "${FIPS_MODE}" = "enabled" ]]; \ + then echo "building in fips mode"; make clean split-proxy-fips entrypoints EXTRA_BUILD_ARGS="${EXTRA_BUILD_ARGS}"; mv split-proxy-fips split-proxy; \ + else echo "building in standard mode"; make clean split-proxy entrypoints EXTRA_BUILD_ARGS="${EXTRA_BUILD_ARGS}"; \ + fi' + +# copy1 stage +FROM scratch AS copy1 + COPY docker/functions.sh . COPY --from=builder /code/split-proxy /usr/bin/ COPY --from=builder /code/entrypoint.proxy.sh . +COPY --from=builder /bin/bash-static /bin/bash +COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt +COPY --from=builder /etc/passwd /etc/passwd +COPY --from=builder /etc/shadow /etc/shadow +COPY --from=builder /etc/group /etc/group +# because split-sync is dynamically linked to glibc for the net library +COPY --from=builder /lib/x86_64-linux-gnu/libc.so.6 /lib/x86_64-linux-gnu/libc.so.6 +COPY --from=builder /lib64/ld-linux-x86-64.so.2 /lib64/ld-linux-x86-64.so.2 +# Get copyright statements for included components for legal compliance +COPY --from=builder /usr/share/doc/ca-certificates/copyright /usr/share/doc/ca-certificates/copyright +COPY --from=builder /usr/share/doc/bash-static/copyright /usr/share/doc/bash-static/copyright +COPY --from=builder /usr/share/doc/libc6/copyright /usr/share/doc/libc6/copyright + +# runner stage squashed minimum layer +FROM scratch + +COPY --from=copy1 / / EXPOSE 3000 3010 diff --git a/docker/Dockerfile.synchronizer b/docker/Dockerfile.synchronizer index f78665e1..0f5ebbea 100644 --- a/docker/Dockerfile.synchronizer +++ b/docker/Dockerfile.synchronizer @@ -5,22 +5,8 @@ ARG EXTRA_BUILD_ARGS ARG FIPS_MODE RUN apt update -y -RUN apt install -y build-essential ca-certificates python3 git +RUN apt install -y build-essential ca-certificates python3 git bash-static -WORKDIR /code - -COPY . . - -RUN bash -c 'if [[ "${FIPS_MODE}" = "enabled" ]]; \ - then echo "building in fips mode"; make clean split-sync-fips entrypoints EXTRA_BUILD_ARGS="${EXTRA_BUILD_ARGS}"; mv split-sync-fips split-sync; \ - else echo "building in standard mode"; make clean split-sync entrypoints EXTRA_BUILD_ARGS="${EXTRA_BUILD_ARGS}"; \ - fi' - -# Runner stage -FROM debian:12.10 AS runner - -RUN apt update -y -RUN apt install -y bash ca-certificates RUN addgroup --gid 1000 --system 'split-synchronizer' RUN adduser \ --disabled-password \ @@ -31,10 +17,40 @@ RUN adduser \ --uid 1000 \ 'split-synchronizer' +WORKDIR /code + +COPY . . + +RUN bash -c 'if [[ "${FIPS_MODE}" = "enabled" ]]; \ + then echo "building in fips mode"; make clean split-sync-fips entrypoints EXTRA_BUILD_ARGS="${EXTRA_BUILD_ARGS}"; mv split-sync-fips split-sync; \ + else echo "building in standard mode"; make clean split-sync entrypoints EXTRA_BUILD_ARGS="${EXTRA_BUILD_ARGS}"; \ + fi' + +# copy1 stage +FROM scratch AS copy1 + COPY docker/functions.sh . COPY --from=builder /code/split-sync /usr/bin/ COPY --from=builder /code/entrypoint.synchronizer.sh . +COPY --from=builder /bin/bash-static /bin/bash +COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt +COPY --from=builder /etc/passwd /etc/passwd +COPY --from=builder /etc/shadow /etc/shadow +COPY --from=builder /etc/group /etc/group +# because split-sync is dynamically linked to glibc for the net library +COPY --from=builder /lib/x86_64-linux-gnu/libc.so.6 /lib/x86_64-linux-gnu/libc.so.6 +COPY --from=builder /lib64/ld-linux-x86-64.so.2 /lib64/ld-linux-x86-64.so.2 +# Get copyright statements for included components for legal compliance +COPY --from=builder /usr/share/doc/ca-certificates/copyright /usr/share/doc/ca-certificates/copyright +COPY --from=builder /usr/share/doc/bash-static/copyright /usr/share/doc/bash-static/copyright +COPY --from=builder /usr/share/doc/libc6/copyright /usr/share/doc/libc6/copyright + + +# runner stage squashed minimum layer +FROM scratch + +COPY --from=copy1 / / EXPOSE 3000 3010 diff --git a/docker/entrypoint.sh.tpl b/docker/entrypoint.sh.tpl index e15da7a2..bea421dd 100755 --- a/docker/entrypoint.sh.tpl +++ b/docker/entrypoint.sh.tpl @@ -1,4 +1,4 @@ -#!/usr/bin/env bash +#!/bin/bash FLAGS=({{ARGS}}) diff --git a/docker/functions.sh b/docker/functions.sh index ce58f76f..cf5cfe9a 100644 --- a/docker/functions.sh +++ b/docker/functions.sh @@ -1,4 +1,4 @@ -#!/usr/bin/env bash +#!/bin/bash function parse_flags_from_conf_file() { fn=$1 @@ -21,7 +21,8 @@ function flag_to_env_var() { return 1 fi - echo "${prefix}_${flag}" | tr "[a-z]" "[A-Z]" | tr "-" "_" + uppercase="${prefix^^}_${flag^^}" + echo "${uppercase//-/_}" return 0 } From 7b847a03fdf5e558139e0b9e2087bbd7de31e0e3 Mon Sep 17 00:00:00 2001 From: matt-domsch-sp Date: Sun, 25 May 2025 20:41:24 +0000 Subject: [PATCH 2/3] Add healthcheck script MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit split-synchronizer doesn’t come with a health check program in the container callable by an ECS Task Definition. We can add one using bash only, which is already present in the image, so we don't have to add something large such as curl. The executable includes a GET /health/application endpoint on port 3010 which returns HTTP 200 OK on healthy, HTTP 500 on unhealthy. This script performs the minimal HTTP protocol call, placing the GET call and reading the first returned line for the status code, ignoring all other headers and body. --- docker/Dockerfile.proxy | 8 +++++--- docker/Dockerfile.synchronizer | 8 +++++--- healthcheck.sh | 17 +++++++++++++++++ 3 files changed, 27 insertions(+), 6 deletions(-) create mode 100755 healthcheck.sh diff --git a/docker/Dockerfile.proxy b/docker/Dockerfile.proxy index 8ddee394..71477935 100644 --- a/docker/Dockerfile.proxy +++ b/docker/Dockerfile.proxy @@ -26,8 +26,8 @@ RUN bash -c 'if [[ "${FIPS_MODE}" = "enabled" ]]; \ else echo "building in standard mode"; make clean split-proxy entrypoints EXTRA_BUILD_ARGS="${EXTRA_BUILD_ARGS}"; \ fi' -# copy1 stage -FROM scratch AS copy1 +# distroless stage +FROM scratch AS distroless COPY docker/functions.sh . @@ -41,6 +41,8 @@ COPY --from=builder /etc/group /etc/group # because split-sync is dynamically linked to glibc for the net library COPY --from=builder /lib/x86_64-linux-gnu/libc.so.6 /lib/x86_64-linux-gnu/libc.so.6 COPY --from=builder /lib64/ld-linux-x86-64.so.2 /lib64/ld-linux-x86-64.so.2 +# health check +COPY --from=builder /code/healthcheck.sh /usr/bin/ # Get copyright statements for included components for legal compliance COPY --from=builder /usr/share/doc/ca-certificates/copyright /usr/share/doc/ca-certificates/copyright COPY --from=builder /usr/share/doc/bash-static/copyright /usr/share/doc/bash-static/copyright @@ -49,7 +51,7 @@ COPY --from=builder /usr/share/doc/libc6/copyright /usr/share/doc/libc6/copyrigh # runner stage squashed minimum layer FROM scratch -COPY --from=copy1 / / +COPY --from=distroless / / EXPOSE 3000 3010 diff --git a/docker/Dockerfile.synchronizer b/docker/Dockerfile.synchronizer index 0f5ebbea..cf1fdab6 100644 --- a/docker/Dockerfile.synchronizer +++ b/docker/Dockerfile.synchronizer @@ -26,8 +26,8 @@ RUN bash -c 'if [[ "${FIPS_MODE}" = "enabled" ]]; \ else echo "building in standard mode"; make clean split-sync entrypoints EXTRA_BUILD_ARGS="${EXTRA_BUILD_ARGS}"; \ fi' -# copy1 stage -FROM scratch AS copy1 +# distroless stage +FROM scratch AS distroless COPY docker/functions.sh . @@ -41,6 +41,8 @@ COPY --from=builder /etc/group /etc/group # because split-sync is dynamically linked to glibc for the net library COPY --from=builder /lib/x86_64-linux-gnu/libc.so.6 /lib/x86_64-linux-gnu/libc.so.6 COPY --from=builder /lib64/ld-linux-x86-64.so.2 /lib64/ld-linux-x86-64.so.2 +# health check +COPY --from=builder /code/healthcheck.sh /usr/bin/ # Get copyright statements for included components for legal compliance COPY --from=builder /usr/share/doc/ca-certificates/copyright /usr/share/doc/ca-certificates/copyright COPY --from=builder /usr/share/doc/bash-static/copyright /usr/share/doc/bash-static/copyright @@ -50,7 +52,7 @@ COPY --from=builder /usr/share/doc/libc6/copyright /usr/share/doc/libc6/copyrigh # runner stage squashed minimum layer FROM scratch -COPY --from=copy1 / / +COPY --from=distroless / / EXPOSE 3000 3010 diff --git a/healthcheck.sh b/healthcheck.sh new file mode 100755 index 00000000..c7b7ec55 --- /dev/null +++ b/healthcheck.sh @@ -0,0 +1,17 @@ +#!/bin/bash + +HOSTNAME="localhost" +PORT=3010 + +rc=1 +lineno=0 +exec 5<> /dev/tcp/${HOSTNAME}/${PORT} +printf "GET /health/application HTTP/1.1\r\nHost: ${HOSTNAME}\r\nConnection: close\r\n\r\n" >&5 +while read LINE <&5; do + if [[ $lineno -eq 0 && ${LINE} =~ HTTP/1.1[[:space:]]200[[:space:]]OK ]]; then + rc=0 + fi + lineno=$((lineno+1)) +done + +exit $rc From 382a172c0245c1e80c02b43210cadec166af26c8 Mon Sep 17 00:00:00 2001 From: matt-domsch-sp Date: Fri, 30 May 2025 00:57:36 +0000 Subject: [PATCH 3/3] Replace bash-static with dynamic bash Since we already need glibc dynamically linked into the go applications, and bash only needs glibc plus one more small library, we can continue using the dynamically linked bash executable as long as we add libtinfo6 to the image as well. This reduces the size of the image by about 1MB and still works. --- docker/Dockerfile.proxy | 8 +++++--- docker/Dockerfile.synchronizer | 8 +++++--- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/docker/Dockerfile.proxy b/docker/Dockerfile.proxy index 71477935..92a857eb 100644 --- a/docker/Dockerfile.proxy +++ b/docker/Dockerfile.proxy @@ -5,7 +5,7 @@ ARG EXTRA_BUILD_ARGS ARG FIPS_MODE RUN apt update -y -RUN apt install -y build-essential ca-certificates python3 git bash-static +RUN apt install -y build-essential ca-certificates python3 git RUN addgroup --gid 1000 --system 'split-proxy' RUN adduser \ @@ -33,7 +33,7 @@ COPY docker/functions.sh . COPY --from=builder /code/split-proxy /usr/bin/ COPY --from=builder /code/entrypoint.proxy.sh . -COPY --from=builder /bin/bash-static /bin/bash +COPY --from=builder /bin/bash /bin/ COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt COPY --from=builder /etc/passwd /etc/passwd COPY --from=builder /etc/shadow /etc/shadow @@ -41,12 +41,14 @@ COPY --from=builder /etc/group /etc/group # because split-sync is dynamically linked to glibc for the net library COPY --from=builder /lib/x86_64-linux-gnu/libc.so.6 /lib/x86_64-linux-gnu/libc.so.6 COPY --from=builder /lib64/ld-linux-x86-64.so.2 /lib64/ld-linux-x86-64.so.2 +COPY --from=builder /lib/x86_64-linux-gnu/libtinfo* /lib/x86_64-linux-gnu/ # health check COPY --from=builder /code/healthcheck.sh /usr/bin/ # Get copyright statements for included components for legal compliance COPY --from=builder /usr/share/doc/ca-certificates/copyright /usr/share/doc/ca-certificates/copyright -COPY --from=builder /usr/share/doc/bash-static/copyright /usr/share/doc/bash-static/copyright +COPY --from=builder /usr/share/doc/bash/copyright /usr/share/doc/bash/copyright COPY --from=builder /usr/share/doc/libc6/copyright /usr/share/doc/libc6/copyright +COPY --from=builder /usr/share/doc/libtinfo6/copyright /usr/share/doc/libtinfo6/copyright # runner stage squashed minimum layer FROM scratch diff --git a/docker/Dockerfile.synchronizer b/docker/Dockerfile.synchronizer index cf1fdab6..5fa0d77a 100644 --- a/docker/Dockerfile.synchronizer +++ b/docker/Dockerfile.synchronizer @@ -5,7 +5,7 @@ ARG EXTRA_BUILD_ARGS ARG FIPS_MODE RUN apt update -y -RUN apt install -y build-essential ca-certificates python3 git bash-static +RUN apt install -y build-essential ca-certificates python3 git RUN addgroup --gid 1000 --system 'split-synchronizer' RUN adduser \ @@ -33,7 +33,7 @@ COPY docker/functions.sh . COPY --from=builder /code/split-sync /usr/bin/ COPY --from=builder /code/entrypoint.synchronizer.sh . -COPY --from=builder /bin/bash-static /bin/bash +COPY --from=builder /bin/bash /bin/ COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt COPY --from=builder /etc/passwd /etc/passwd COPY --from=builder /etc/shadow /etc/shadow @@ -41,12 +41,14 @@ COPY --from=builder /etc/group /etc/group # because split-sync is dynamically linked to glibc for the net library COPY --from=builder /lib/x86_64-linux-gnu/libc.so.6 /lib/x86_64-linux-gnu/libc.so.6 COPY --from=builder /lib64/ld-linux-x86-64.so.2 /lib64/ld-linux-x86-64.so.2 +COPY --from=builder /lib/x86_64-linux-gnu/libtinfo* /lib/x86_64-linux-gnu/ # health check COPY --from=builder /code/healthcheck.sh /usr/bin/ # Get copyright statements for included components for legal compliance COPY --from=builder /usr/share/doc/ca-certificates/copyright /usr/share/doc/ca-certificates/copyright -COPY --from=builder /usr/share/doc/bash-static/copyright /usr/share/doc/bash-static/copyright +COPY --from=builder /usr/share/doc/bash/copyright /usr/share/doc/bash/copyright COPY --from=builder /usr/share/doc/libc6/copyright /usr/share/doc/libc6/copyright +COPY --from=builder /usr/share/doc/libtinfo6/copyright /usr/share/doc/libtinfo6/copyright # runner stage squashed minimum layer