diff --git a/Dockerfile b/Dockerfile index f2629d56..0871aa7f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -65,21 +65,21 @@ ENV TZ=UTC ENV PATH="/app/.venv/bin:$PATH" # Create non-root user and group for enhanced security -RUN groupadd -g "${GID}" "${APP_GROUP}" \ - && useradd -u "${UID}" -g "${APP_GROUP}" -s /bin/sh -m "${APP_USER}" +RUN (getent group "${GID}" || groupadd -g "${GID}" "${APP_GROUP}") \ + && useradd -u "${UID}" -g "${GID}" -s /bin/sh -m "${APP_USER}" # Copy application artifacts from builder stage -COPY --chown=${APP_USER}:${APP_GROUP} --from=builder /app /app +COPY --chown=${UID}:${GID} --from=builder /app /app # Set working directory WORKDIR /app # Create necessary directories with proper permissions RUN mkdir -p /app/config /app/reports \ - && chown -R ${APP_USER}:${APP_GROUP} /app/config /app/reports + && chown -R ${UID}:${GID} /app/config /app/reports # Switch to non-root user -USER ${APP_USER}:${APP_GROUP} +USER ${UID}:${GID} # Health check HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \ diff --git a/Dockerfile.dev b/Dockerfile.dev index b426785c..f4bcc048 100644 --- a/Dockerfile.dev +++ b/Dockerfile.dev @@ -1,6 +1,11 @@ # Development Dockerfile for hot reload and debugging FROM ghcr.io/astral-sh/uv:python3.12-bookworm-slim AS development +ARG APP_USER=appuser +ARG APP_GROUP=appgrp +ARG UID=1000 +ARG GID=1000 + # Install development dependencies RUN apt-get update \ && apt-get install -y --no-install-recommends \ @@ -13,6 +18,10 @@ RUN apt-get update \ && apt-get clean \ && rm -rf /var/lib/apt/lists/* +# Create non-root user and group +RUN (getent group "${GID}" || groupadd -g "${GID}" "${APP_GROUP}") \ + && useradd -u "${UID}" -g "${GID}" -s /bin/sh -m "${APP_USER}" + # Set working directory WORKDIR /app @@ -23,17 +32,22 @@ ENV PYTHONUNBUFFERED=1 ENV PYTHONPATH=/app/src # Copy dependency files -COPY LICENSE README.md pyproject.toml uv.lock ./ +COPY --chown=${UID}:${GID} LICENSE README.md pyproject.toml uv.lock ./ # Install all dependencies including dev dependencies -RUN --mount=type=cache,target=/root/.cache/uv \ - uv sync --frozen +RUN --mount=type=cache,target=/home/${APP_USER}/.cache/uv,uid=${UID},gid=${GID} \ + uv sync --frozen \ + && chown -R ${UID}:${GID} .venv # Copy source code -COPY . . +COPY --chown=${UID}:${GID} . . # Create directories -RUN mkdir -p /app/reports /app/config +RUN mkdir -p /app/reports /app/config \ + && chown -R ${UID}:${GID} /app/reports /app/config + +# Switch to non-root user +USER ${UID}:${GID} # Set executable path ENV PATH="/app/.venv/bin:$PATH" diff --git a/docker-compose.yml b/docker-compose.yml index 29718f8a..27ae745e 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -11,8 +11,8 @@ services: UV_LINK_MODE: copy APP_USER: appuser APP_GROUP: appgrp - UID: 1000 - GID: 1000 + UID: ${HOST_UID:-1000} + GID: ${HOST_GID:-1000} # Environment variables environment: - PYTHONUNBUFFERED=1 @@ -57,6 +57,11 @@ services: context: . dockerfile: Dockerfile.dev target: development + args: + APP_USER: appuser + APP_GROUP: appgrp + UID: ${HOST_UID:-1000} + GID: ${HOST_GID:-1000} environment: - PYTHONUNBUFFERED=1 - TZ=UTC diff --git a/scripts/build.sh b/scripts/build.sh index 38aea2b4..fcafd20a 100755 --- a/scripts/build.sh +++ b/scripts/build.sh @@ -58,7 +58,9 @@ build_production() { --target production \ --build-arg UV_COMPILE_BYTECODE=1 \ --build-arg UV_LINK_MODE=copy \ - "${build_args[@]}" \ + --build-arg UID="${HOST_UID}" \ + --build-arg GID="${HOST_GID}" \ + ${build_args[@]+"${build_args[@]}"} \ -t "${NAMESPACE}/${IMAGE}" \ -t "${NAMESPACE}/${IMAGE_LATEST}" \ . @@ -87,7 +89,9 @@ build_development() { -f Dockerfile.dev \ --target development \ --build-arg UV_COMPILE_BYTECODE=0 \ - "${build_args[@]}" \ + --build-arg UID="${HOST_UID}" \ + --build-arg GID="${HOST_GID}" \ + ${build_args[@]+"${build_args[@]}"} \ -t "${NAMESPACE}/${IMAGE_DEV}" \ . diff --git a/scripts/common.sh b/scripts/common.sh index 3e9841ce..4dc8bfb1 100755 --- a/scripts/common.sh +++ b/scripts/common.sh @@ -10,6 +10,10 @@ readonly YELLOW='\033[1;33m' readonly BLUE='\033[0;34m' readonly NC='\033[0m' # No Color +# Current user +readonly HOST_UID=$(id -u) +readonly HOST_GID=$(id -g) + # Logging functions log_info() { echo -e "${BLUE}[INFO]${NC} $1" >&2 diff --git a/scripts/run.sh b/scripts/run.sh index 62c32224..a65cae2c 100755 --- a/scripts/run.sh +++ b/scripts/run.sh @@ -80,7 +80,7 @@ run_production() { if [[ "${BUILD_FIRST}" == "true" ]]; then log_info "Building production image first..." - ./scripts/build.sh "${build_opts[@]}" production + ./scripts/build.sh ${build_opts[@]+"${build_opts[@]}"} production fi local compose_opts=() @@ -89,7 +89,10 @@ run_production() { compose_opts+=("--verbose") fi - docker compose "${compose_opts[@]}" --file "${COMPOSE_FILE}" run --rm "${SERVICE_NAME}" "$@" + docker compose ${compose_opts[@]+"${compose_opts[@]}"} --file "${COMPOSE_FILE}" run --rm \ + --user "${HOST_UID}:${HOST_GID}" \ + -e HOST_UID="${HOST_UID}" -e HOST_GID="${HOST_GID}" \ + "${SERVICE_NAME}" "$@" } # Run in development mode @@ -104,7 +107,7 @@ run_development() { if [[ "${BUILD_FIRST}" == "true" ]]; then log_info "Building development image first..." - ./scripts/build.sh "${build_opts[@]}" development + ./scripts/build.sh ${build_opts[@]+"${build_opts[@]}"} development fi local compose_opts=() @@ -112,7 +115,10 @@ run_development() { compose_opts+=("--verbose") fi - docker compose "${compose_opts[@]}" --profile dev run --rm "${DEV_SERVICE_NAME}" "$@" + docker compose ${compose_opts[@]+"${compose_opts[@]}"} --profile dev run --rm \ + --user "${HOST_UID}:${HOST_GID}" \ + -e HOST_UID="${HOST_UID}" -e HOST_GID="${HOST_GID}" \ + "${DEV_SERVICE_NAME}" "$@" } # Cleanup function for run script @@ -164,6 +170,11 @@ main() { esac done + # Consume the separator if present + if [[ "${1:-}" == "--" ]]; then + shift + fi + log_info "Starting OpenUTM Verification Tool..." check_dependencies check_files