# Multi-stage approach to minimize final image size and attack surface FROM ubuntu:24.04 AS installer ARG USER_ID=1000 ARG GROUP_ID=1000 ARG USERNAME=toolbox ARG TEA_VERSION=0.11.1 ENV DEBIAN_FRONTEND=noninteractive # ROOT STAGE 1: System package installation for Docker QA tools only RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ --mount=type=cache,target=/var/lib/apt/lists,sharing=locked \ apt-get update \ && apt-get install -y --no-install-recommends \ ca-certificates \ curl \ git \ jq \ bc \ locales \ openssh-client \ zsh \ unzip \ zip \ python3 \ python3-pip \ wget \ # Docker and container tools \ docker.io \ # Security scanning tools \ clamav \ # Static analysis tools \ shellcheck \ # JSON/YAML tools \ yq \ # Development tools for custom scripts \ make \ gcc \ && apt-get clean \ && rm -rf /var/lib/apt/lists/* # ROOT: Configure locale RUN locale-gen en_US.UTF-8 ENV LANG=en_US.UTF-8 \ LANGUAGE=en_US:en \ LC_ALL=en_US.UTF-8 # ROOT: Create non-root user with matching UID/GID for host mapping RUN if getent passwd "${USER_ID}" >/dev/null; then \ existing_user="$(getent passwd "${USER_ID}" | cut -d: -f1)"; \ userdel --remove "${existing_user}"; \ fi \ && if ! getent group "${GROUP_ID}" >/dev/null; then \ groupadd --gid "${GROUP_ID}" "${USERNAME}"; \ fi \ && useradd --uid "${USER_ID}" --gid "${GROUP_ID}" --shell /usr/bin/zsh --create-home "${USERNAME}" # ROOT: Set up toolbox user home directory with proper permissions RUN chown -R "${USER_ID}:${GROUP_ID}" "/home/${USERNAME}" # SWITCH TO NON-ROOT USER: All further operations as toolbox user USER ${USERNAME} WORKDIR /home/${USERNAME} # NON-ROOT: Install mise runtime manager for toolbox user RUN curl -sSfL https://mise.jdx.dev/install.sh | sh # NON-ROOT: Update PATH for mise tools ENV PATH=/home/${USERNAME}/.local/bin:/home/${USERNAME}/.local/share/mise/shims:$PATH # NON-ROOT: Install Node.js via mise as toolbox user RUN mise install node@22.13.0 && mise use -g node@22.13.0 # NON-ROOT: Install aqua package manager for toolbox user RUN curl -sSfL https://raw.githubusercontent.com/aquaproj/aqua-installer/v2.3.1/aqua-installer > /tmp/aqua-installer.sh && \ chmod +x /tmp/aqua-installer.sh && \ AQUA_ROOT_DIR=/home/${USERNAME}/.local/share/aquaproj-aqua /tmp/aqua-installer.sh && \ rm /tmp/aqua-installer.sh # NON-ROOT: Update PATH for aqua tools ENV PATH=/home/${USERNAME}/.local/share/aquaproj-aqua/bin:$PATH # NON-ROOT: Install Oh My Zsh RUN git clone --depth=1 https://github.com/ohmyzsh/ohmyzsh.git ~/.oh-my-zsh # NON-ROOT: Configure shells (zsh, bash) with all customizations RUN cp ~/.oh-my-zsh/templates/zshrc.zsh-template ~/.zshrc \ && sed -i "s/^plugins=(git)$/plugins=(git docker docker-compose)/" ~/.zshrc \ && printf "\nexport PATH=\"\$HOME/.local/share/aquaproj-aqua/bin:\$HOME/.local/share/mise/shims:\$HOME/.local/bin:\$PATH\"\n" >> ~/.zshrc \ && printf "\n# Starship prompt\neval \"\$(starship init zsh)\"\n" >> ~/.zshrc \ && printf "\n# mise runtime manager\neval \"\$(mise activate zsh)\"\n" >> ~/.zshrc \ && printf "\n# direnv\nexport DIRENV_LOG_FORMAT=\"\"\neval \"\$(direnv hook zsh)\"\n" >> ~/.zshrc \ && printf "\nexport AQUA_GLOBAL_CONFIG=\"\$HOME/.config/aquaproj-aqua/aqua.yaml\"\n" >> ~/.bashrc \ && printf "\n# mise runtime manager (bash)\neval \"\$(mise activate bash)\"\n" >> ~/.bashrc \ && printf "\n# direnv\nexport DIRENV_LOG_FORMAT=\"\"\neval \"\$(direnv hook bash)\"\n" >> ~/.bashrc # NON-ROOT: Install aqua packages for Docker QA tools RUN mkdir -p ~/.config/aquaproj-aqua \ && echo "version: 1.0.0\nregistries:\n - type: standard\n ref: v4.431.0\npackages:\n - name: aquasecurity/trivy@v0.54.1\n - name: hadolint/hadolint@v2.14.0\n - name: github/gh@v2.69.0\n - name: dandavison/delta@0.18.2\n - name: ajeetdsouza/zoxide@v0.9.8\n - name: mikefarah/yq@v4.48.1\n - name: direnv/direnv@v2.37.1" > ~/.config/aquaproj-aqua/aqua.yaml \ && aqua install \ && aqua install --all # NON-ROOT: Install additional Docker QA tools via npm RUN mise exec -- npm install -g dockerfilelint@latest && mise reshim # NON-ROOT: Install additional Python-based tools using --break-system-packages RUN pip3 install --break-system-packages docker-image-py # ROOT: Set up workspace directory USER root RUN mkdir -p /workspace && chown "${USER_ID}:${GROUP_ID}" /workspace USER ${USERNAME} # NON-ROOT: Verify all tools are accessible during build RUN bash -c 'command -v docker && command -v dockerfilelint' \ && bash -c 'docker --version && node --version && npm --version' # NON-ROOT: Final mise reshim to ensure all tools are properly linked RUN mise reshim # FINAL STAGE: Copy completed setup to minimize image and enhance security FROM ubuntu:24.04 ARG USER_ID=1000 ARG GROUP_ID=1000 ARG USERNAME=toolbox ARG TEA_VERSION=0.11.1 ENV DEBIAN_FRONTEND=noninteractive # ROOT: Install minimal runtime dependencies only RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ --mount=type=cache,target=/var/lib/apt/lists,sharing=locked \ apt-get update \ && apt-get install -y --no-install-recommends \ ca-certificates \ curl \ git \ jq \ bc \ locales \ openssh-client \ zsh \ unzip \ zip \ python3 \ docker.io \ && apt-get clean \ && rm -rf /var/lib/apt/lists/* # ROOT: Restore system-wide configurations RUN locale-gen en_US.UTF-8 ENV LANG=en_US.UTF-8 \ LANGUAGE=en_US:en \ LC_ALL=en_US.UTF-8 # ROOT: Create non-root user with matching UID/GID for host mapping RUN if getent passwd "${USER_ID}" >/dev/null; then \ existing_user="$(getent passwd "${USER_ID}" | cut -d: -f1)"; \ userdel --remove "${existing_user}"; \ fi \ && if ! getent group "${GROUP_ID}" >/dev/null; then \ groupadd --gid "${GROUP_ID}" "${USERNAME}"; \ fi \ && useradd --uid "${USER_ID}" --gid "${GROUP_ID}" --shell /usr/bin/zsh --create-home "${USERNAME}" # ROOT: Copy the complete user environment from the installer stage COPY --from=installer --chown=${USER_ID}:${GROUP_ID} /home/${USERNAME} /home/${USERNAME} # ROOT: Create workspace directory RUN mkdir -p /workspace && chown "${USER_ID}:${GROUP_ID}" /workspace # ROOT: Install system-wide tools (tea and starship) which were in the source image RUN curl -fsSL "https://dl.gitea.io/tea/${TEA_VERSION}/tea-${TEA_VERSION}-linux-amd64" -o /tmp/tea \ && curl -fsSL "https://dl.gitea.io/tea/${TEA_VERSION}/tea-${TEA_VERSION}-linux-amd64.sha256" -o /tmp/tea.sha256 \ && sed -n 's/ .*//p' /tmp/tea.sha256 | awk '{print $1 " /tmp/tea"}' | sha256sum -c - \ && install -m 0755 /tmp/tea /usr/local/bin/tea \ && rm -f /tmp/tea /tmp/tea.sha256 RUN curl -fsSL https://starship.rs/install.sh | sh -s -- -y -b /usr/local/bin # ROOT: Security hardening - remove sudo if present RUN apt-get remove -y sudo 2>/dev/null || true && apt-get autoremove -y 2>/dev/null || true && rm -rf /var/lib/apt/lists/* 2>/dev/null || true # ROOT: Final environment variables ENV PATH=/home/${USERNAME}/.local/share/aquaproj-aqua/bin:/home/${USERNAME}/.local/share/mise/shims:/home/${USERNAME}/.local/bin:/usr/local/bin:/usr/local/sbin:/usr/bin:/usr/sbin:/bin:/sbin ENV SHELL=/usr/bin/zsh \ AQUA_GLOBAL_CONFIG=/home/${USERNAME}/.config/aquaproj-aqua/aqua.yaml # FINAL USER: Switch to toolbox user for runtime USER ${USERNAME} WORKDIR /workspace CMD ["/usr/bin/zsh"]