FROM python:3.12-slim # ORCH-058 (Strategy B): stamp the image with the git commit it was built from so # the deploy hook can fail-close if a stale staging image would be promoted to prod # (INV-FRESH). Passed at build time via `--build-arg GIT_SHA=` (the staging # rebuild in check_staging_image_fresh / the --build-staging hook mode supplies it). # Without the build-arg the label is empty -> the hook treats it as a mismatch # (fail-closed). The OCI-standard key is read by `docker image inspect`. ARG GIT_SHA="" LABEL org.opencontainers.image.revision=$GIT_SHA WORKDIR /app RUN apt-get update -qq && apt-get install -y -qq openssh-client git curl ca-certificates && rm -rf /var/lib/apt/lists/* # git operations run as root over bind-mounted /repos (may be owned by host uid) -> trust it. RUN git config --system --add safe.directory '*' # ORCH-022: pinned gitleaks static Go binary for the offline secret-scan sub-gate # (07-infra I-1). Baked into the image (NOT a pip package): the gate runs INSIDE the # orchestrator container over a per-task worktree. Pinned release => deterministic # rules; gitleaks needs no network so the "a secret always blocks" guarantee (BR-2) # is independent of internet access. Multi-arch aware (amd64/arm64). ARG GITLEAKS_VERSION=8.18.4 RUN set -eux; \ arch="$(dpkg --print-architecture)"; \ case "$arch" in \ amd64) gl_arch="x64" ;; \ arm64) gl_arch="arm64" ;; \ *) echo "unsupported arch: $arch" >&2; exit 1 ;; \ esac; \ curl -fsSL -o /tmp/gitleaks.tar.gz \ "https://github.com/gitleaks/gitleaks/releases/download/v${GITLEAKS_VERSION}/gitleaks_${GITLEAKS_VERSION}_linux_${gl_arch}.tar.gz"; \ tar -xzf /tmp/gitleaks.tar.gz -C /usr/local/bin gitleaks; \ chmod +x /usr/local/bin/gitleaks; \ rm -f /tmp/gitleaks.tar.gz; \ gitleaks version # ORCH-58: compose runs the container as uid:gid 1000:1000 (ORCH-40), but the base # image has no passwd entry for uid 1000 -> ssh/whoami fail with # "No user exists for uid 1000" (rc=255), breaking the detached self-deploy ssh # launch (ORCH-36 Phase B). Create a real user 1000 with a home dir so getpwuid() # resolves and ssh can start. # ORCH-101 (D5): uid/gid/home/username are build ARGs (defaults = current prod # values); compose build.args wires APP_UID/APP_GID/APP_HOME from the SAME env # vars as the runtime user: and the mount targets, so the ORCH-040 group # (uid/gid/HOME/mounts/useradd) moves coherently. APP_USER is passwd cosmetics # (the ENTRY matters for getpwuid/ssh, not the name) — Dockerfile-default only. ARG APP_UID=1000 ARG APP_GID=1000 ARG APP_USER=slin ARG APP_HOME=/home/slin RUN groupadd -g ${APP_GID} app && useradd -u ${APP_UID} -g ${APP_GID} -m -d ${APP_HOME} -s /bin/bash ${APP_USER} COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt COPY src/ ./src/ # ORCH-021: do NOT `COPY data/ ./data/`. `data/` is gitignored (SQLite DB dir) and # is provided at runtime as a bind-mount volume (`./data:/app/data`, see # docker-compose.yml) which shadows anything baked into the image — so the COPY was # dead weight. Worse, the ORCH-058 staging rebuild (`check_staging_image_fresh`) # builds with the task *worktree* as the docker build context; a fresh worktree never # contains the untracked `data/`, so `COPY data/` failed `docker build` with exit 1 # and bounced the task off `deploy-staging`. We just ensure the mountpoint exists. RUN mkdir -p /app/data ENV PYTHONPATH=/app # ORCH-101 (D5): CMD deliberately stays exec-form with the documented 8500 # default — an ARG cannot reach a runtime CMD, and a shell-form CMD would break # the verified `init: true` + exec-form PID-1/signal semantics (B-2). The prod # port is parametrised on the compose layer (`command:` with # ${ORCH_DEPLOY_PROD_TARGET_PORT:-8500}), which overrides this CMD. CMD ["uvicorn", "src.main:app", "--host", "0.0.0.0", "--port", "8500"]