Ctrl K
ComunidadGuías…

Dockerfile

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
# Robot Dev Team Project
# File: Dockerfile
# Description: Container image for the Robot Dev Team Project webhook service.
# License: MIT
# SPDX-License-Identifier: MIT
# Copyright (c) 2025 MCKNLY LLC

ARG PYTHON_IMAGE=python:3.14.7-slim-trixie@sha256:656d12e70054d5fda18a045e2494c96701e9792dd1445f95b3d038df954f57e9
FROM ${PYTHON_IMAGE}

ARG PIP_VERSION=26.2
ARG UV_VERSION=0.12.1
ARG GLAB_VERSION=1.111.0
ARG DEBIAN_SNAPSHOT=20261006T081244Z

ENV PYTHONDONTWRITEBYTECODE=1 \
    PYTHONUNBUFFERED=1 \
    UV_PROJECT_ENVIRONMENT=/opt/venv

# OS dependencies pinned via Debian snapshot. The base image ships its own deb822 source
# pointing at the live mirrors, so it is cleared first: APT reads sources.list.d/ as well as
# sources.list, and loading both leaves the snapshot governing nothing. The check after
# `update` enforces the property -- every active index is under the snapshot -- rather than
# the filename, so a future base-image layout change cannot quietly reintroduce a live mirror.
# The upgrade step covers the debs the base image ships preinstalled, which `install` alone
# never touches; the snapshot is frozen, so it resolves to the same versions on every rebuild.
# `util-linux` supplies setpriv, which docker-entrypoint.sh uses for the privilege drop. It is a
# Required-priority package and is already present, so apt reports 0 newly installed -- naming it
# is what binds it to the snapshot and stops a base-image change from silently dropping setpriv.
# It replaced `gosu`, whose bookworm build is statically linked against an EOL Go 1.19.8
# toolchain that no snapshot bump could move, because Debian would not rebuild it.
# Trixie does ship a gosu rebuilt on a current toolchain, but setpriv stays: it carries no Go
# runtime and therefore no Go-stdlib CVE surface at all, which is the stronger property and
# the one that does not decay as the new toolchain ages. `scripts/ci-smoke-image.sh` asserts
# gosu is absent, so reintroducing it on trixie is a regression rather than an alternative.
RUN set -eux; \
    rm -rf /etc/apt/sources.list.d/*; \
    echo "deb [check-valid-until=no] http://snapshot.debian.org/archive/debian/${DEBIAN_SNAPSHOT}/ trixie main" > /etc/apt/sources.list; \
    echo "deb [check-valid-until=no] http://snapshot.debian.org/archive/debian/${DEBIAN_SNAPSHOT}/ trixie-updates main" >> /etc/apt/sources.list; \
    echo "deb [check-valid-until=no] http://snapshot.debian.org/archive/debian-security/${DEBIAN_SNAPSHOT}/ trixie-security main" >> /etc/apt/sources.list; \
    printf 'Acquire::Check-Valid-Until "false";\nAcquire::Retries "5";\n' > /etc/apt/apt.conf.d/99snapshot; \
    apt-get update; \
    apt-get indextargets --no-release-info 'Created-By: Packages' \
      | awk '/^URI:/ { print $2 }' | sort -u > /tmp/apt-sources.txt; \
    test -s /tmp/apt-sources.txt; \
    if grep -v '^http://snapshot\.debian\.org/' /tmp/apt-sources.txt; then \
      echo "ERROR: the APT sources listed above are outside the pinned snapshot" >&2; \
      exit 1; \
    fi; \
    DEBIAN_FRONTEND=noninteractive apt-get upgrade -y \
      -o Dpkg::Options::=--force-confold; \
    upgrade_plan="$(LC_ALL=C apt-get -s upgrade)"; \
    held="$(printf '%s\n' "${upgrade_plan}" | sed -n 's/^.* \([0-9][0-9]*\) not upgraded\.$/\1/p')"; \
    case "${held}" in \
      '' | *[!0-9]*) \
        echo "ERROR: could not read a held-back count from the apt-get upgrade" >&2; \
        echo "simulation below; refusing to assume nothing was held back" >&2; \
        printf '%s\n' "${upgrade_plan}" >&2; \
        exit 1; \
        ;; \
    esac; \
    if [ "${held}" -ne 0 ]; then \
      echo "ERROR: apt-get upgrade held back ${held} package(s); it never installs new" >&2; \
      echo "packages, so a fix that needs a new dependency is skipped with exit 0" >&2; \
      printf '%s\n' "${upgrade_plan}" >&2; \
      exit 1; \
    fi; \
    DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
      bash \
      bzip2 \
      ca-certificates \
      curl \
      git \
      tini \
      util-linux \
      xz-utils; \
    rm -f /tmp/apt-sources.txt; \
    rm -rf /var/lib/apt/lists/*

# Install GitLab CLI (glab). `-f` makes an HTTP error fail on the curl line with its status
# instead of saving the error page; `--retry-all-errors` also retries a truncated body.
# Keep `-o`: curl discards a partial body before a retry only from a file it opened itself,
# and cannot rewind a pipe or `>` redirect, so a retry there appends a second copy. The speed
# guard turns a stalled transfer into a retryable timeout. `sha256sum -c` stays the install
# gate. The checksum list is fetched first so a bad response fails in seconds.
RUN set -eux; \
    arch="$(dpkg --print-architecture)"; \
    case "${arch}" in \
      amd64) glab_selector="linux_amd64" ;; \
      arm64) glab_selector="linux_arm64" ;; \
      armhf) glab_selector="linux_armv6" ;; \
      i386) glab_selector="linux_386" ;; \
      *) echo "unsupported architecture: ${arch}" >&2; exit 1 ;; \
    esac; \
    glab_deb="glab_${GLAB_VERSION}_${glab_selector}.deb"; \
    glab_base="https://gitlab.com/gitlab-org/cli/-/releases/v${GLAB_VERSION}/downloads"; \
    curl -fsSL --retry 3 --retry-all-errors --retry-connrefused \
      --retry-max-time 300 \
      --connect-timeout 30 --speed-limit 1024 --speed-time 60 \
      "${glab_base}/checksums.txt" -o /tmp/glab_checksums.txt; \
    curl -fsSL --retry 3 --retry-all-errors --retry-connrefused \
      --retry-max-time 300 \
      --connect-timeout 30 --speed-limit 1024 --speed-time 60 \
      "${glab_base}/${glab_deb}" -o "/tmp/${glab_deb}"; \
    (cd /tmp && grep " ${glab_deb}$" glab_checksums.txt > glab.sha256) || { \
      echo "no checksum for ${glab_deb} in glab v${GLAB_VERSION} checksums.txt; has the release asset naming changed?" >&2; \
      exit 1; \
    }; \
    (cd /tmp && sha256sum -c glab.sha256); \
    dpkg -i "/tmp/${glab_deb}"; \
    rm "/tmp/${glab_deb}" /tmp/glab_checksums.txt /tmp/glab.sha256

# License notices for glab and for uv (installed below), neither of whose packages ships the
# notices of the Go modules and crates compiled into it. `notices/` is the upstream files
# for the pinned versions, collected by `scripts/third_party_notices.py` and committed, so the
# build fetches nothing. glab's own license becomes /usr/share/doc/glab/copyright, the file its
# .deb omits. `scripts/ci-build-image.sh` checks the installed copy against the image's SBOM, as
# the app user, before anything is pushed.
COPY notices/glab/ /usr/share/doc/glab/
COPY notices/uv/ /usr/share/doc/uv/

WORKDIR /work

COPY docker-entrypoint.sh /usr/local/bin/
RUN chmod +x /usr/local/bin/docker-entrypoint.sh

COPY gitlab-connect /usr/local/bin/
COPY glab-usr /usr/local/bin/
RUN chmod +x \
    /usr/local/bin/gitlab-connect \
    /usr/local/bin/glab-usr

COPY pyproject.toml README.md uv.lock /work/
COPY app/ /work/app/

RUN pip install --no-cache-dir --upgrade "pip==${PIP_VERSION}" && \
    pip install --no-cache-dir "uv==${UV_VERSION}" && \
    uv sync --frozen --no-dev --no-install-project --no-cache

COPY prompts/ /work/prompts/
COPY config/ /work/config/
COPY scripts/ /work/scripts/
RUN mkdir -p /work/run-logs

ENV HOME=/home/appuser \
    PATH="/opt/venv/bin:/home/appuser/.local/bin:${PATH}"

RUN useradd -u 10001 -ms /bin/bash appuser && chown -R appuser:appuser /work /home/appuser

# The public source repository and the Debian snapshot the APT sources above used, readable from
# the pulled image with `docker inspect` as part of the corresponding-source pointer
# (docs/LICENSE_REVIEW.md). The snapshot values come from the same ARG, so they cannot disagree.
# Both archive roots are named because the timestamp alone does not say which one a security
# update came from. Packages inherited from the base image can be newer than the snapshot, which
# is why the labels are a record of the sources, not of every package. Never put the canonical
# GitLab host in a label: the image is public. `tests/test_runtime_install_contract.py` pins this.
LABEL org.opencontainers.image.source="https://github.com/mcknly/robot-dev-team" \
      com.mcknly.robot-dev-team.debian-snapshot="${DEBIAN_SNAPSHOT}" \
      com.mcknly.robot-dev-team.debian-source="http://snapshot.debian.org/archive/debian/${DEBIAN_SNAPSHOT}/ http://snapshot.debian.org/archive/debian-security/${DEBIAN_SNAPSHOT}/"

EXPOSE 8080

ENTRYPOINT ["/usr/bin/tini", "--", "docker-entrypoint.sh"]
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8080"]