{{ include "from" ; include "shared" -}} FROM {{ from }} ENV CATALINA_HOME /usr/local/tomcat ENV PATH $CATALINA_HOME/bin:$PATH RUN mkdir -p "$CATALINA_HOME" WORKDIR $CATALINA_HOME # let "Tomcat Native" live somewhere isolated ENV TOMCAT_NATIVE_LIBDIR $CATALINA_HOME/native-jni-lib ENV LD_LIBRARY_PATH ${LD_LIBRARY_PATH:+$LD_LIBRARY_PATH:}$TOMCAT_NATIVE_LIBDIR ENV TOMCAT_MAJOR {{ major }} ENV TOMCAT_VERSION {{ .version }} ENV TOMCAT_SHA512 {{ .sha512 }} {{ if java_variant == "jdk" then ( -}} RUN set -eux; \ \ {{ if is_apt then ( -}} savedAptMark="$(apt-mark showmanual)"; \ apt-get update; \ apt-get install -y --no-install-recommends \ ca-certificates \ curl \ gnupg \ ; \ {{ ) elif is_alpine then ( -}} apk add --no-cache --virtual .fetch-deps gnupg; \ {{ ) else ( -}} {{ if is_yum then ( -}} # http://yum.baseurl.org/wiki/YumDB.html if ! command -v yumdb > /dev/null; then \ yum install -y --setopt=skip_missing_names_on_install=False yum-utils; \ yumdb set reason dep yum-utils; \ fi; \ {{ if vendor_variant | contains("oraclelinux7") then ( -}} # TODO there's an odd bug on Oracle Linux where installing "cpp" (which gets pulled in as a dependency of "gcc") and then marking it as automatically-installed will result in the "filesystem" package being removed during "yum autoremove" (which then fails), so we set it as manually-installed to compensate yumdb set reason user filesystem; \ {{ ) else "" end -}} {{ ) elif vendor_variant | variant_is_microdnf then ( -}} # removing dnf after it is installed gets really hairy, so we'll just live with it (since we need it for "dnf mark") microdnf install -y dnf; \ {{ ) else "" end -}} # a helper function to install things, but only if they aren't installed (and to mark them so "autoremove" can purge them for us) _install_temporary() { ( set -eu +x; \ local pkg todo=''; \ for pkg; do \ if ! rpm --query "$pkg" > /dev/null 2>&1; then \ todo="$todo $pkg"; \ fi; \ done; \ if [ -n "$todo" ]; then \ set -x; \ {{ if is_yum then ( -}} yum install -y --setopt=skip_missing_names_on_install=False $todo; \ yumdb set reason dep $todo; \ {{ ) else ( -}} dnf install -y $todo; \ dnf mark remove $todo; \ {{ ) end -}} fi; \ ) }; \ _install_temporary gzip tar; \ {{ if vendor_variant | contains("al20") then ( -}} # gnupg2-minimal (installed by default) conflicts with gnupg2 and does not include dirmngr so cannot fetch keys dnf install -y --allowerasing gnupg2; \ {{ ) else "" end -}} {{ ) end -}} \ ddist() { \ local f="$1"; shift; \ local distFile="$1"; shift; \ local mvnFile="${1:-}"; \ local success=; \ local distUrl=; \ for distUrl in \ # https://apache.org/history/mirror-history.html "https://dlcdn.apache.org/$distFile" \ # if the version is outdated, we have to pull from the archive "https://archive.apache.org/dist/$distFile" \ # if all else fails, let's try Maven (https://www.mail-archive.com/users@tomcat.apache.org/msg134940.html; https://mvnrepository.com/artifact/org.apache.tomcat/tomcat; https://repo1.maven.org/maven2/org/apache/tomcat/tomcat/) ${mvnFile:+"https://repo1.maven.org/maven2/org/apache/tomcat/tomcat/$mvnFile"} \ ; do \ if {{ if is_alpine then "wget -O" else "curl -fL -o" end }} "$f" "$distUrl" && [ -s "$f" ]; then \ success=1; \ break; \ fi; \ done; \ [ -n "$success" ]; \ }; \ \ ddist 'tomcat.tar.gz' "tomcat/tomcat-$TOMCAT_MAJOR/v$TOMCAT_VERSION/bin/apache-tomcat-$TOMCAT_VERSION.tar.gz" "$TOMCAT_VERSION/tomcat-$TOMCAT_VERSION.tar.gz"; \ echo "$TOMCAT_SHA512 *tomcat.tar.gz" | sha512sum {{ if is_alpine then "-c" else "--strict --check" end }} -; \ ddist 'tomcat.tar.gz.asc' "tomcat/tomcat-$TOMCAT_MAJOR/v$TOMCAT_VERSION/bin/apache-tomcat-$TOMCAT_VERSION.tar.gz.asc" "$TOMCAT_VERSION/tomcat-$TOMCAT_VERSION.tar.gz.asc"; \ GNUPGHOME="$(mktemp -d)"; export GNUPGHOME; \ {{ if is_alpine then "wget -O" else "curl -fL -o" end }} upstream-KEYS {{ "https://www.apache.org/dist/tomcat/tomcat-\(major)/KEYS" | @sh }}; \ gpg --batch --import upstream-KEYS; \ # filter upstream KEYS file to *just* known/precomputed fingerprints printf '' > filtered-KEYS; \ # see https://www.apache.org/dist/tomcat/tomcat-{{ major }}/KEYS for key in \ {{ # docker run --rm buildpack-deps:bookworm-curl bash -c 'wget -qO- https://www.apache.org/dist/tomcat/tomcat-11/KEYS | gpg --batch --import &> /dev/null && gpg --batch --list-keys --with-fingerprint --with-colons' | awk -F: '$1 == "pub" && $2 == "-" { pub = 1 } pub && $1 == "fpr" { fpr = $10 } $1 == "sub" { pub = 0 } pub && fpr && $1 == "uid" && $2 == "-" { print "\t\t\t#", $10; print "\t\t\t\"" fpr "\","; pub = 0 } END { print "\t\t\t# trailing comma 👀\n\t\t\tempty" }' { "11": [ # Mark E D Thomas "A9C5DF4D22E99998D9875A5110C01C5A2F6059E7", # Remy Maucherat "48F8E69F6390C9F25CFEDCD268248959359E722B", # trailing comma 👀 empty ], "10": [ # Mark E D Thomas "A9C5DF4D22E99998D9875A5110C01C5A2F6059E7", # Christopher Schultz "5C3C5F3E314C866292F359A8F3AD5C94A67F707E", # trailing comma 👀 empty ], "9": [ # Mark E D Thomas "DCFD35E0BF8CA7344752DE8B6FB21E8933C60243", # Mark E D Thomas "A9C5DF4D22E99998D9875A5110C01C5A2F6059E7", # Remy Maucherat "48F8E69F6390C9F25CFEDCD268248959359E722B", # trailing comma 👀 empty ], } | .[major] // error("missing GPG keys") | map( -}} {{ @sh }} \ {{ ) | add -}} ; do \ gpg --batch --fingerprint "$key"; \ gpg --batch --export --armor "$key" >> filtered-KEYS; \ done; \ {{ if vendor_variant | contains("al2") then "" else ( -}} gpgconf --kill all; \ {{ ) end -}} rm -rf "$GNUPGHOME"; \ GNUPGHOME="$(mktemp -d)"; export GNUPGHOME; \ gpg --batch --import filtered-KEYS; \ gpg --batch --verify tomcat.tar.gz.asc tomcat.tar.gz; \ tar -xf tomcat.tar.gz --strip-components=1; \ rm bin/*.bat; \ rm tomcat.tar.gz*; \ {{ if vendor_variant | variant_is_al2 or contains("oraclelinux7") then "" else ( -}} gpgconf --kill all; \ {{ ) end -}} rm -rf "$GNUPGHOME"; \ \ # https://tomcat.apache.org/tomcat-9.0-doc/security-howto.html#Default_web_applications mv webapps webapps.dist; \ mkdir webapps; \ # we don't delete them completely because they're frankly a pain to get back for users who do want them, and they're generally tiny (~7MB) \ nativeBuildDir="$(mktemp -d)"; \ tar -xf bin/tomcat-native.tar.gz -C "$nativeBuildDir" --strip-components=1; \ {{ if is_apt then ( -}} apt-get install -y --no-install-recommends \ dpkg-dev \ gcc \ libapr1-dev \ libssl-dev \ make \ ; \ {{ ) elif is_alpine then ( -}} apk add --no-cache --virtual .build-deps \ apr-dev \ dpkg-dev dpkg \ gcc \ libc-dev \ make \ {{ if is_native_ge_2 then ( -}} openssl3-dev \ {{ ) else ( -}} openssl-dev \ {{ ) end -}} ; \ {{ ) else ( -}} _install_temporary \ apr-devel \ findutils \ gcc \ make \ {{ if vendor_variant | variant_is_al2 then ( -}} openssl11-devel \ {{ ) else ( -}} openssl-devel \ {{ ) end -}} {{ if vendor_variant | variant_is_microdnf then ( -}} # "gcc: error: /usr/lib/rpm/redhat/redhat-hardened-cc1: No such file or directory" redhat-rpm-config \ {{ ) else "" end -}} ; \ {{ ) end -}} ( \ export CATALINA_HOME="$PWD"; \ cd "$nativeBuildDir/native"; \ {{ if is_apt or is_alpine then ( -}} gnuArch="$(dpkg-architecture --query DEB_BUILD_GNU_TYPE)"; \ {{ ) else "" end -}} aprConfig="$(command -v apr-1-config)"; \ ./configure \ {{ if is_apt or is_alpine then ( -}} --build="$gnuArch" \ {{ ) else "" end -}} --libdir="$TOMCAT_NATIVE_LIBDIR" \ --prefix="$CATALINA_HOME" \ --with-apr="$aprConfig" \ --with-java-home="$JAVA_HOME" \ {{ if is_native_ge_2 then "" else ( -}} --with-ssl \ {{ ) end -}} ; \ nproc="$(nproc)"; \ make -j "$nproc"; \ make install; \ ); \ rm -rf "$nativeBuildDir"; \ rm bin/tomcat-native.tar.gz; \ \ {{ if is_alpine then "" else ( -}} # sh removes env vars it doesn't support (ones with periods) # https://github.com/docker-library/tomcat/issues/77 find ./bin/ -name '*.sh' -exec sed -ri 's|^#!/bin/sh$|#!/usr/bin/env bash|' '{}' +; \ \ {{ ) end -}} # fix permissions (especially for running as non-root) # https://github.com/docker-library/tomcat/issues/35 chmod -R +rX .; \ chmod 1777 logs temp work; \ \ {{ if is_apt then ( -}} # reset apt-mark's "manual" list so that "purge --auto-remove" will remove all build dependencies apt-mark auto '.*' > /dev/null; \ [ -z "$savedAptMark" ] || apt-mark manual $savedAptMark > /dev/null; \ find "$TOMCAT_NATIVE_LIBDIR" -type f -executable -exec ldd '{}' ';' \ | awk '/=>/ { print $(NF-1) }' \ | xargs -rt readlink -e \ | sort -u \ | xargs -rt dpkg-query --search \ | cut -d: -f1 \ | sort -u \ | tee "$TOMCAT_NATIVE_LIBDIR/.dependencies.txt" \ | xargs -r apt-mark manual \ ; \ \ apt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false; \ rm -rf /var/lib/apt/lists/*; \ {{ ) elif is_alpine then ( -}} # mark any explicit dependencies as manually installed deps="$( \ scanelf --needed --nobanner --format '%n#p' --recursive "$TOMCAT_NATIVE_LIBDIR" \ | tr ',' '\n' \ | sort -u \ {{ if is_native_ge_2 and (vendor_variant | contains("alpine3.15") or contains("alpine3.16")) then ( -}} # https://git.alpinelinux.org/aports/tree/main/openssl3/APKBUILD?h=3.16-stable#n23 ("sonameprefix") # see also "apk info --all libssl3" ("so:openssl3:so:libssl.so.3=3" under "provides:") | awk 'system("[ -e /usr/local/lib/" $1 " ]") == 0 { next } { if ($1 ~ /libssl|libcrypto/) { print "so:openssl3:so:" $1 } else { print "so:" $1 } }' \ {{ ) else ( -}} | awk 'system("[ -e /usr/local/lib/" $1 " ]") == 0 { next } { print "so:" $1 }' \ {{ ) end -}} | tee "$TOMCAT_NATIVE_LIBDIR/.dependencies.txt" \ )"; \ apk add --no-network --virtual .tomcat-native-deps $deps; \ \ apk del --no-network .fetch-deps .build-deps; \ {{ ) else ( -}} # mark any explicit dependencies as manually installed find "$TOMCAT_NATIVE_LIBDIR" -type f -executable -exec ldd '{}' ';' \ | awk '/=>/ && $(NF-1) != "=>" { print $(NF-1) }' \ | xargs -rt readlink -e \ | sort -u \ | xargs -rt rpm --query --whatprovides \ | sort -u \ | tee "$TOMCAT_NATIVE_LIBDIR/.dependencies.txt" \ | xargs -r {{ if is_yum then "yumdb set reason user" else "dnf mark install" end }} \ ; \ \ # clean up anything added temporarily and not later marked as necessary {{ if is_yum then "yum" else "dnf" end }} autoremove -y; \ {{ if is_yum then "yum" else "dnf" end }} clean all; \ rm -rf /var/cache/{{ if is_yum then "yum" else "dnf" end }}; \ {{ ) end -}} \ # smoke test catalina.sh version {{ ) else ( -}} COPY --from=tomcat:{{ .version }}-jdk{{ java_version }}-{{ vendor_variant }} $CATALINA_HOME $CATALINA_HOME RUN set -eux; \ {{ if is_apt then ( -}} apt-get update; \ xargs -rt apt-get install -y --no-install-recommends < "$TOMCAT_NATIVE_LIBDIR/.dependencies.txt"; \ rm -rf /var/lib/apt/lists/* {{ ) elif is_alpine then ( -}} deps="$(cat "$TOMCAT_NATIVE_LIBDIR/.dependencies.txt")"; \ apk add --no-cache --virtual .tomcat-native-deps $deps {{ ) elif vendor_variant | contains("al20") or contains("ubi") then ( -}} # no xargs in al20XX and ubiX-minimal /o\ deps="$(cat "$TOMCAT_NATIVE_LIBDIR/.dependencies.txt")"; \ {{ if vendor_variant | variant_is_microdnf then "microdnf" else "dnf" end }} install -y $deps; \ {{ if vendor_variant | variant_is_microdnf then "microdnf" else "dnf" end }} clean all; \ rm -rf /var/cache/dnf {{ ) else ( -}} xargs -rt {{ if is_yum then "yum" elif vendor_variant | variant_is_microdnf then "microdnf" else "dnf" end }} install -y{{ if is_yum then " --setopt=skip_missing_names_on_install=False" else "" end }} < "$TOMCAT_NATIVE_LIBDIR/.dependencies.txt"; \ {{ if is_yum then "yum" elif vendor_variant | variant_is_microdnf then "microdnf" else "dnf" end }} clean all; \ rm -rf /var/cache/{{ if is_yum then "yum" else "dnf" end }} {{ ) end -}} {{ ) end -}} # verify Tomcat Native is working properly RUN set -eux; \ nativeLines="$(catalina.sh configtest 2>&1)"; \ nativeLines="$(echo "$nativeLines" | grep 'Apache Tomcat Native')"; \ nativeLines="$(echo "$nativeLines" | sort -u)"; \ if ! echo "$nativeLines" | grep -E 'INFO: Loaded( APR based)? Apache Tomcat Native library' >&2; then \ echo >&2 "$nativeLines"; \ exit 1; \ fi EXPOSE 8080 {{ if vendor_variant | contains("temurin") then ( -}} # upstream eclipse-temurin-provided entrypoint script caused https://github.com/docker-library/tomcat/issues/77 to come back as https://github.com/docker-library/tomcat/issues/302; use "/entrypoint.sh" at your own risk ENTRYPOINT [] {{ ) else "" end -}} CMD ["catalina.sh", "run"]