Use Alpine without shell as default base image - CLM-39066#190
Use Alpine without shell as default base image - CLM-39066#190rpokorny wants to merge 105 commits into
Conversation
The wolfi-base apk requires the target root to have etc/apk/keys and etc/apk/repositories before installing packages into an isolated root.
The --initdb flag creates the database and directory structure that apk needs before it can install packages into an alternate root.
The infosec dev image has neither apk nor wget. Move the download and checksum verification into the packages stage (wolfi-base) which has both. The builder stage now only handles config sed and javac.
wolfi-base is minimal and doesn't include wget by default.
The infosec runtime image is fully distroless with no shell. Include busybox to provide /bin/sh needed for RUN commands during build and for start.sh at runtime.
The infosec runtime image defaults to a non-root user. Need USER root before mkdir/chmod/chown operations, then switch back to 65532.
The nonroot user doesn't exist in /etc/passwd in the distroless runtime image. Use numeric IDs which work regardless.
- Add DL3006 ignore for untagged wolfi-base FROM - Add DL3018 ignore for unpinned apk add - Create /etc/passwd and /etc/group entries for nonroot (65532) so ls output shows names instead of numeric IDs - Switch port expectations from curl to wget (busybox)
Use -O /dev/null instead of --spider for busybox wget compatibility.
Busybox symlinks aren't created by apk --initdb --root, so wget/etc aren't available as standalone commands. Add curl as an explicit package and restore the original curl-based port expectations.
Add --app flag to healthcheck.java that checks port 8070 and prints the response body. Update expectations to use the healthcheck class instead of curl, checking for DOCTYPE in the app port response. This removes curl from the production image.
Retry connections for up to 120 seconds to handle the case where the server hasn't fully started yet (matches old curl --connect-timeout).
Port 8070 may return non-200 (e.g. redirect) without a license. In --app mode, any response means the port is up. Also handle error streams for non-2xx responses to avoid exceptions.
- Runtime stage now uses sonatype-infosec/jre:openjdk-17 instead of the full JDK, reducing attack surface (no compiler in production) - Rename healthcheck -> Healthcheck (public class, capitalized) - Use 2-space indentation - Fix --app mode to use response-code based exit status
The slim and alpine variants need to continue publishing during the transition to infosec hardened base images (~3 releases).
Replace bundle tarball download with direct downloads of the server jar and jvm.options file from the Maven repository. This removes the dependency on the bundle distribution which is being sunset.
Update to 1.203.0-SNAPSHOT with real SHA values. Switch artifact download URL from maven-private-releases to maven-private (group repo) so that both release and snapshot artifacts can be resolved.
Replace curl-based artifact downloads with Maven dependency:copy. This solves two problems: authentication (Maven uses credentials from settings.xml) and SNAPSHOT resolution (Maven resolves timestamped builds automatically). The settings.xml is passed via BuildKit --secret mount so credentials never appear in any image layer. All docker buildx build invocations (Jenkinsfile, Jenkinsfile.release, build_and_push_images.sh) now pass the secret. Jenkinsfile.release simplified: removed bundle checksum handling since artifacts are now downloaded by Maven (which does its own verification). The updateIQServerVersion function only updates the version ARG and release label.
The Jenkins agents don't have settings.xml at $HOME/.m2/; Maven settings are managed by the Config File Provider plugin with ID 'private-settings.xml'. Use configFileProvider to write settings.xml to the workspace, then pass that path as the BuildKit secret. build_and_push_images.sh now uses a MAVEN_SETTINGS env var (with fallback to $HOME/.m2/settings.xml for local use).
Maven only needs a JRE to run dependency:copy (no compilation). The JRE package is lighter than the full JDK.
…CLM-39201_minimize_os_deps
Added as submodule at ./localcheck pointing to sonatype/localcheck. Will be compiled during Docker build instead of using Wolfi package since we are migrating from Wolfi to Alpine. CLM-39066
Replace Chainguard Wolfi base images with Alpine 3. - Build stage: Alpine with Maven for artifact download, gcc for launcher, cargo for localcheck - Runtime stage: FROM scratch with isolated-root apk packages - Uses openjdk17-jre-headless from Alpine apk (amd64 + arm64) - Strips busybox/shell from runtime (only needed for apk scripts) - Compiles localcheck from submodule for healthcheck - No shell, no package manager in runtime image CLM-39066
- launcher at /usr/bin/launcher (Alpine /bin -> /usr/bin symlink) - use Alpine from sonatype.repo.sonatype.app registry CLM-39066
The main Dockerfile is now Alpine-based, making these redundant: - Dockerfile.alpine - Jenkinsfile.alpine - Jenkinsfile.alpine.release - alpine-expectations.groovy CLM-39066
Remove alpine pull from prepare stage (image is now Alpine-based). CLM-39066
- /tmp should be root-owned with 1777 permissions, not chown'd to nexus user - Jenkinsfile.release now publishes VERSION-alpine and latest-alpine tags from the main Dockerfile (replaces removed Jenkinsfile.alpine.release) CLM-39066
…ult_base # Conflicts: # Dockerfile.alpine # Jenkinsfile.alpine # Jenkinsfile.alpine.release
| @@ -0,0 +1,3 @@ | |||
| [submodule "localcheck"] | |||
| path = localcheck | |||
| url = git@github.com:sonatype/localcheck.git | |||
There was a problem hiding this comment.
localcheck is a simple tool for http healthchecks written by our infosec dept. At the moment, its only published form is a Wolfi-compatible apk package on our repo server, which we can't use in alpine. So instead the approach for the moment is to check out the source code as a submodule and build it locally during our docker build.
| && chown -R ${UID}:${GID} /runtime-deps/etc/nexus-iq-server \ | ||
| && chown -R ${UID}:${GID} /runtime-deps/var/log/nexus-iq-server | ||
|
|
||
| # Download the server jar and JVM options file as individual Maven artifacts. |
There was a problem hiding this comment.
An alternative that I considered, since we aren't using infosec's specially hardened JVM in this approach, was to download the jreleaser bundles and not have a globally-installed JRE on the docker image. However, that won't work because we don't have a jreleaser bundle for aarch64 MUSL, and apparently the reason we don't is because temurin doesn't provide a build for that configuration. So system JVM it is.
787146c to
de2d962
Compare
- Update description and migration section for Alpine base - Remove SHA256 checksum build args (no longer used) - Note that VERSION and VERSION-alpine tags are the same image CLM-39066
de2d962 to
c7130b7
Compare
The main Dockerfile now produces a scratch-based image with Alpine/musl packages, but is not itself an Alpine image. Restore the separate Alpine pipeline files (Dockerfile.alpine, Jenkinsfile.alpine, etc.) so that the -alpine tags continue to be published by their own pipeline. Remove -alpine tags from Jenkinsfile.release since that is the main image pipeline. CLM-39066
After #183 and #182 got closed, this is the next attempt to move off of RedHat base images and remove the shell. This time, using an alpine builder image and NO runtime base image.