fix(docker): pin the build workflow and keep the image on hash-locked dependencies

Four issues in the new workflow and Dockerfile:

- Every action floated on a mutable major tag, and BuildKit was pinned to
  moby/buildkit:master. This was the only workflow in the repo doing so; the
  other four already pin full commit SHAs. All seven actions are now SHA-pinned
  with a version comment, and the custom driver-opts block is dropped, which
  also removes an unneeded network=host.
- id-token: write was granted with no OIDC consumer anywhere in the workflow —
  no cosign, attestation, provenance, or OIDC login step. Dropped.
- The Dockerfile installed channel extras with 'pip install -e ".[extras]"'
  after the --require-hashes lock install. lark-oapi, qrcode and
  python-telegram-bot are in neither requirements-lock.txt nor its source, so
  the build resolved them and their transitive deps unhashed from PyPI at build
  time — six lines below a comment stating deps come from the hash-pinned lock.
  They now come from requirements-channels-lock.txt (16 pins, all hashed,
  generated under the same constraint as the main lock: 12 packages are shared
  with it and none diverge in version). The project install is --no-deps, which
  is safe because all 44 pyproject base deps are already in the main lock.
- The workflow_dispatch input was interpolated directly into the Summary step's
  run: body. It now passes through env, and the tag is validated against the
  Docker tag grammar before any registry work.

CI verifies the new lock too. The existing 'Verify hash-locked dependencies'
step only dry-ran requirements-lock.txt, so a second lock could have rotted
undetected until a release build failed; both are now dry-run separately, since
the Docker build installs them as two independent --require-hashes passes.

The summary also states that a new GHCR package is private until its visibility
is set once, so the pull command it prints is not read as working out of the box.
This commit is contained in:
Haozhe Wu committed 2026-08-13 15:37:38 +08:00
1 parent b23aabf0a0
commit adaeeb5629
5 files changed
+417 -40

No files matched your search

+47 -25
View File
@@ -1,5 +1,9 @@
name: Build and Push Docker Image
# Actions are pinned to immutable commit SHAs, matching every other workflow in
# this repository. Newer majors exist for the docker/* actions (v4/v6/v7); those
# are a deliberate, separately testable bump, not a drive-by change here.
on:
workflow_dispatch:
inputs:
@@ -20,31 +24,36 @@ jobs:
permissions:
contents: read
packages: write
id-token: write
steps:
- name: Validate image tag
env:
IMAGE_TAG: ${{ inputs.image_tag }}
run: |
# Fail early with a readable message rather than at the registry push.
if ! printf '%s' "$IMAGE_TAG" | grep -qE '^[a-zA-Z0-9_][a-zA-Z0-9._-]{0,127}$'; then
echo "::error::'$IMAGE_TAG' is not a valid Docker tag." >&2
exit 1
fi
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
with:
driver-opts: |
image=moby/buildkit:master
network=host
uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3.7.0
- name: Log in to GHCR
uses: docker/login-action@v3
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0
with:
registry: ${{ env.GHCR_REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Log in to Docker Hub
uses: docker/login-action@v3
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0
with:
registry: ${{ env.DOCKERHUB_REGISTRY }}
username: ${{ secrets.DOCKERHUB_USERNAME }}
@@ -52,7 +61,7 @@ jobs:
- name: Extract metadata for GHCR
id: meta-ghcr
uses: docker/metadata-action@v5
uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # v5.10.0
with:
images: ${{ env.GHCR_REGISTRY }}/${{ env.GHCR_IMAGE_NAME }}
tags: |
@@ -61,7 +70,7 @@ jobs:
- name: Extract metadata for Docker Hub
id: meta-dockerhub
uses: docker/metadata-action@v5
uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # v5.10.0
with:
images: ${{ env.DOCKERHUB_REGISTRY }}/${{ env.DOCKERHUB_IMAGE_NAME }}
tags: |
@@ -69,7 +78,7 @@ jobs:
type=raw,value=latest,enable=${{ inputs.image_tag != 'latest' }}
- name: Build and push
uses: docker/build-push-action@v5
uses: docker/build-push-action@ca052bb54ab0790a636c9b5f226502c73d547a25 # v5.4.0
with:
context: .
file: Dockerfile
@@ -83,7 +92,7 @@ jobs:
cache-to: type=gha,mode=max
- name: Update Docker Hub description
uses: peter-evans/dockerhub-description@v4
uses: peter-evans/dockerhub-description@432a30c9e07499fd01da9f8a49f0faf9e0ca5b77 # v4.0.2
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
@@ -92,15 +101,28 @@ jobs:
short-description: "AI trading agent with backtesting. Includes Telegram & Feishu channels."
- name: Summary
# Inputs go through env, never straight into the script body: a
# workflow_dispatch input interpolated into `run:` is a shell-injection
# vector even when only maintainers can dispatch it.
env:
IMAGE_TAG: ${{ inputs.image_tag }}
GHCR_IMAGE: ${{ env.GHCR_REGISTRY }}/${{ env.GHCR_IMAGE_NAME }}
DOCKERHUB_IMAGE: ${{ env.DOCKERHUB_REGISTRY }}/${{ env.DOCKERHUB_IMAGE_NAME }}
run: |
echo "### Docker Images Built and Pushed" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "**GHCR:** \`${{ env.GHCR_REGISTRY }}/${{ env.GHCR_IMAGE_NAME }}:${{ inputs.image_tag }}\`" >> $GITHUB_STEP_SUMMARY
echo "**Docker Hub:** \`${{ env.DOCKERHUB_REGISTRY }}/${{ env.DOCKERHUB_IMAGE_NAME }}:${{ inputs.image_tag }}\`" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "**Platforms:** linux/amd64, linux/arm64" >> $GITHUB_STEP_SUMMARY
echo "**Channels:** feishu, telegram" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo '```bash' >> $GITHUB_STEP_SUMMARY
echo "docker pull ${{ env.GHCR_REGISTRY }}/${{ env.GHCR_IMAGE_NAME }}:${{ inputs.image_tag }}" >> $GITHUB_STEP_SUMMARY
echo '```' >> $GITHUB_STEP_SUMMARY
{
echo "### Docker Images Built and Pushed"
echo ""
echo "**GHCR:** \`${GHCR_IMAGE}:${IMAGE_TAG}\`"
echo "**Docker Hub:** \`${DOCKERHUB_IMAGE}:${IMAGE_TAG}\`"
echo ""
echo "**Platforms:** linux/amd64, linux/arm64"
echo "**Channels:** feishu, telegram (from requirements-channels-lock.txt)"
echo ""
echo '```bash'
echo "docker pull ${GHCR_IMAGE}:${IMAGE_TAG}"
echo '```'
echo ""
echo "> A newly created GHCR package is **private by default**. Until its"
echo "> visibility is set to public once in the org package settings, the"
echo "> pull above works only for authenticated users with read access."
} >> "$GITHUB_STEP_SUMMARY"
+7 -6
View File
@@ -24,12 +24,13 @@ jobs:
cache: pip
- name: Verify hash-locked dependencies
run: >-
pip install
--dry-run
--ignore-installed
--require-hashes
-r requirements-lock.txt
# Both locks, separately: each must be internally complete on its own,
# because the Docker build installs them as two independent
# --require-hashes passes. Verifying only the main lock would let the
# channel lock rot until a release build failed.
run: |
pip install --dry-run --ignore-installed --require-hashes -r requirements-lock.txt
pip install --dry-run --ignore-installed --require-hashes -r requirements-channels-lock.txt
- name: Install dependencies
# `openbb` is included so agent/tests/test_openbb_bridge actually runs: