deer-flow/RELEASING.md
Ryker_Feng 1b5220e35e
ci(lark): publish lark-cli-init/broker images (#4558)
* ci(lark): publish lark-cli-init/broker images (#4532)

Add .github/workflows/lark-cli-images.yaml to build and push the two
optional Lark sandbox runtime images (Pattern A init container and
Pattern B broker sidecar) to GHCR.

These track the upstream larksuite/cli version, not the DeerFlow v*
release, so the workflow is decoupled from container.yaml / the
verify-versions gate:

- Trigger via workflow_dispatch (lark_cli_version input) or a lark-cli-v*
  tag (version read from after the prefix).
- Multi-arch linux/amd64,linux/arm64 (the images stage a real
  arch-dispatched lark-cli binary), via QEMU + Buildx.
- Per-component build context: lark-cli-init builds from its own dir
  (relative COPY), lark-cli-broker from the repo root (it copies the
  shared build-runtime.sh + the harness lark_broker.py).
- Tagged by lark-cli version; no latest; gated on the upstream repo.

Docs: document the independent publishing in RELEASING.md and both image
READMEs, replacing the "publishing is a fast-follow" notes.

Closes #4532

* fix(ci): scope broker build context past root .dockerignore

The repo-root .dockerignore excludes the whole `docker/` tree, but the
lark-cli-broker image builds from a repo-root context and must COPY
`docker/lark-cli-init/build-runtime.sh` and
`docker/lark-cli-broker/entrypoint.sh` (plus the harness lark_broker.py).
Under root .dockerignore those COPYs fail (excluded from context).

Add docker/lark-cli-broker/Dockerfile.dockerignore: BuildKit uses this
per-Dockerfile ignore-file instead of the root one for `-f
docker/lark-cli-broker/Dockerfile` builds, keeping `docker/` and
`backend/` in context while still dropping .git/venv/frontend/docs noise.

lark-cli-init is unaffected (it builds from its own dir context).

* fix(ci): harden lark-cli version input against shell injection

Address PR #4558 review: pass the workflow_dispatch input through an env
var instead of interpolating ${{ inputs.lark_cli_version }} directly into
the run: script, so a dispatched value can't be expression-injected into
the runner shell if the repo gate ever widens. Also fix the multi-arch
comment verb (stage -> ships).
2026-07-29 15:24:17 +08:00

7.5 KiB

Releasing DeerFlow

DeerFlow releases are tag-driven: pushing a v* git tag triggers the publishing workflows. There is no separate release script that bumps versions — the maintainer bumps the version sources, updates the changelog, commits, and tags. The helper scripts below keep the version sources in lockstep, and CI gates the release on them agreeing with the tag.

Version sources

A release version must appear, identically, in four places:

File Field
backend/pyproject.toml version = "X.Y.Z"
frontend/package.json "version": "X.Y.Z"
deploy/helm/deer-flow/Chart.yaml version: X.Y.Z
deploy/helm/deer-flow/Chart.yaml appVersion: "X.Y.Z"

Plus the git tag vX.Y.Z itself, which is the canonical release identifier.

Container images are tagged from the git tag (not from these files), and the Helm chart version is validated against the tag — so if any source lags the tag, the release is blocked (see Version gate).

The frontend's in-app About page (Settings ▸ About) is a derived consumer, not a fifth source: it reads frontend/package.json's version at build time, so it tracks the table above automatically with no bump needed. Nightly builds override it with the chart's nightly string (<base>-nightly.<YYYYMMDD>-<short_sha>) via the APP_VERSION build-arg in nightly.yaml, so a nightly image's About page distinguishes it from a release.

Helper scripts

  • scripts/bump_version.sh <version> — set all four fields at once, then self-verify. Tolerates a leading v (e.g. v2.1.0).
    scripts/bump_version.sh 2.1.0
    
  • scripts/verify_versions.sh [version] — check that all sources agree. With no argument it requires mutual equality; with an argument it requires every source to equal it. Exits non-zero on mismatch. Run it locally before tagging to catch drift early:
    scripts/verify_versions.sh 2.1.0
    

Release procedure

  1. Bump the version across all sources:
    scripts/bump_version.sh 2.1.0
    
  2. Update CHANGELOG.md: rename the ## [Unreleased] section to ## [2.1.0] — YYYY-MM-DD (note the em dash ), and add a link reference at the bottom of the file:
    [2.1.0]: https://github.com/bytedance/deer-flow/releases/tag/v2.1.0
    
    Start a fresh ## [Unreleased] section above it for the next cycle.
  3. Commit the version + changelog changes:
    git add -A
    git commit -m "release: v2.1.0"
    
  4. Tag and push:
    git tag v2.1.0
    git push origin v2.1.0
    
    Pushing the tag triggers the publishing workflows (below).

What CI publishes on a v* tag

  • .github/workflows/container.yaml — builds and pushes backend, frontend, and provisioner images to ghcr.io, tagged with the release version (and latest on the default branch).
  • .github/workflows/chart.yaml — packages the Helm chart and pushes it as an OCI artifact to ghcr.io. Users install with:
    helm install deer-flow oci://ghcr.io/<owner>/charts/deer-flow --version 2.1.0
    

Nightly builds

.github/workflows/nightly.yaml runs on a schedule (and workflow_dispatch) to publish the same three images plus the chart from unreleased main. It is not gated by the version check (there is no v* tag) and it does not touch the latest tag, which stays pinned to the last v* release. Every job is gated on github.repository == 'bytedance/deer-flow', so it only runs on the upstream repo - a scheduled run or manual dispatch on a fork skips all jobs.

Artifacts (under the running repo's owner, where <date> is YYYYMMDD):

  • Images: ghcr.io/<owner>/deer-flow-{backend,frontend,provisioner}:nightly (rolling, overwritten each run) and :nightly-<date> (pinned to a day, but mutable within it - a same-day re-dispatch overwrites it). For a truly immutable pin, use :sha-<short>.
  • Chart: oci://ghcr.io/<owner>/charts/deer-flow, version <base>-nightly.<date>-<sha> (e.g. 2.1.0-nightly.20260710-77a3652). The short SHA makes each dispatch's chart version unique, so a same-day re-dispatch re-publishes cleanly (OCI chart versions are immutable and otherwise can't be overwritten). The packaged chart defaults image.registry=ghcr.io/<owner> and image.tag=nightly, so installing it pulls the matching nightly images with no values overrides:
    helm install deer-flow oci://ghcr.io/<owner>/charts/deer-flow \
      --version 2.1.0-nightly.20260710-77a3652
    

The chart version is patched in-workflow only - Chart.yaml and values.yaml in the repo are never modified.

lark-cli sandbox images

The two optional Lark sandbox runtime images — lark-cli-init (Pattern A) and lark-cli-broker (Pattern B) — are not part of the v* release. They track the upstream larksuite/cli version, so they publish independently via .github/workflows/lark-cli-images.yaml:

  • Trigger with workflow_dispatch (a lark_cli_version input, e.g. v1.0.65) or by pushing a lark-cli-v* tag (the version is read from after the prefix).
  • Builds multi-arch (linux/amd64,linux/arm64) and pushes ghcr.io/<owner>/deer-flow-{lark-cli-init,lark-cli-broker}:<lark-cli-version>.
  • Gated on github.repository == 'bytedance/deer-flow'; not tied to the verify-versions gate (its version is the lark-cli release, not the DeerFlow release), and it never touches latest.

Both features stay opt-in: the provisioner ignores them until LARK_CLI_INIT_IMAGE / LARK_CLI_BROKER_IMAGE point at a published tag.

Version gate

Both publishing workflows call .github/workflows/verify-versions.yml as their first job. It runs scripts/verify_versions.sh against the tag (minus the v). If any of the four version sources doesn't match the tag, the verify job fails and all publish jobs are skipped — no images, no chart.

When it fails, the job annotation names the offending file and suggests the fix:

::error::frontend/package.json is '2.0.0' but expected '2.1.0'.
Tip: run scripts/bump_version.sh 2.1.0 to align all sources.

Pre-releases (RCs)

Pre-release tags like v2.1.0-rc1 are valid v* tags and trigger the same workflows. The version sources must equal the full pre-release string (2.1.0-rc1) — the gate compares exact strings. Use the same procedure with the rc version:

scripts/bump_version.sh 2.1.0-rc1
# update CHANGELOG, commit, tag v2.1.0-rc1, push

Recovering from a failed gate

If the gate failed because a source was forgotten:

  1. Run scripts/bump_version.sh <version> to align the sources.
  2. Amend or add a follow-up commit.
  3. Delete and re-create the tag, then push it:
    git tag -d v2.1.0
    git tag v2.1.0
    git push origin :refs/tags/v2.1.0
    git push origin v2.1.0
    

Re-pushing the tag re-triggers the workflows. Because the gate blocks all artifacts when it fails, nothing was published under the bad tag, so re-tagging is safe — no images or chart were pushed to overwrite.

Post-release

Optionally draft a GitHub Release from the tag, pasting the corresponding CHANGELOG.md section as the release notes. The changelog link references point at these release URLs.

For the 2.1.0 chart release (the first chart release), pre-charts/ nightly builds remain at the legacy bare ghcr.io/<owner>/deer-flow package. That package receives no new versions after 2.1.0; delete it or revoke its visibility once nothing still pulls from it.