ci: publish Docker images and the PyPI package from the release workflow

backend-release creates the GitHub release with GITHUB_TOKEN, and GitHub
never starts workflows from events that token produces, so the
`release: published` triggers on the Docker and PyPI publish workflows only
fired for releases made by hand. 0.18.0 got no Docker images for that
reason, and 0.19.0 reached PyPI by a manual run.

backend-release now calls both publish workflows after creating the release,
passing the version it tagged. Both workflows gain a `workflow_call` trigger
with a `version` input and read the tag from it or from the release event,
so a release created by hand still publishes through the release trigger.
The Docker Hub credentials are passed by name; the PyPI job authenticates
through trusted publishing, which matches the called workflow's filename and
environment, so the publisher configuration is unchanged.

Permissions in backend-release move from the workflow to the jobs: the
release job writes contents; the Docker call writes contents (the compose
file attached to the release) and packages; the PyPI call gets an OIDC token.
This commit is contained in:
Alex committed 2026-09-08 18:46:20 +01:00
1 parent 55239fc345
commit d983febe43
3 files changed
+84 -15

No files matched your search

+36 -2
View File
@@ -1,5 +1,12 @@
name: Backend release
# A version bump on main tags the commit, creates the GitHub release, then
# publishes the Docker images and the PyPI package by calling those workflows.
# The release is created with GITHUB_TOKEN, and GitHub never starts workflows
# from events that token produces, so the `release: published` triggers on the
# publish workflows would not fire (0.18.0 got no images that way). Releases
# created by hand still publish through those triggers.
on:
push:
branches: [main]
@@ -7,8 +14,7 @@ on:
- 'docsgpt/version.py'
workflow_dispatch:
permissions:
contents: write
permissions: {}
concurrency:
group: backend-release
@@ -18,6 +24,11 @@ jobs:
release:
if: github.repository == 'arc53/DocsGPT'
runs-on: ubuntu-latest
permissions:
contents: write
outputs:
version: ${{ steps.ver.outputs.version }}
created: ${{ steps.check.outputs.exists == 'false' }}
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
with:
@@ -71,3 +82,26 @@ jobs:
gh release create "$VERSION" \
--title "v$VERSION" \
--generate-notes
docker:
needs: release
if: needs.release.outputs.created == 'true'
uses: $/.github/workflows/ci.yml
with:
version: ${{ needs.release.outputs.version }}
secrets:
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
permissions:
contents: write # release-assets attaches the compose file to the release
packages: write
pypi:
needs: release
if: needs.release.outputs.created == 'true'
uses: $/.github/workflows/pypi-publish.yml
with:
version: ${{ needs.release.outputs.version }}
permissions:
contents: read
id-token: write
+28 -5
View File
@@ -1,8 +1,31 @@
name: Build and push DocsGPT Docker image
# Runs for a release created by hand (the release event), or called by the
# backend-release workflow with the version it just tagged: GitHub never starts
# workflows from events GITHUB_TOKEN produces, so a bot-created release does not
# fire the release trigger on its own.
on:
release:
types: [published]
workflow_call:
inputs:
version:
description: Release tag to build and push (the images are tagged with it)
type: string
required: true
secrets:
DOCKER_USERNAME:
required: true
DOCKER_PASSWORD:
required: true
permissions:
contents: read
env:
# The tag being published: passed in by the caller, or the release's own.
RELEASE_TAG: ${{ inputs.version || github.event.release.tag_name }}
jobs:
build:
@@ -58,7 +81,7 @@ jobs:
ghcr.io/${{ github.repository_owner }}/docsgpt
labels: |
org.opencontainers.image.title=DocsGPT${{ matrix.variant }}
org.opencontainers.image.version=${{ github.event.release.tag_name }}
org.opencontainers.image.version=${{ env.RELEASE_TAG }}
- name: Build and push platform-specific images
uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6.19.2
@@ -71,8 +94,8 @@ jobs:
EXTRAS=${{ matrix.variant == '-docling' && 'docling' || '' }}
INSTALL_TESSERACT=${{ matrix.variant == '-docling' && 'true' || 'false' }}
tags: |
${{ env.DOCKERHUB_NAMESPACE }}/docsgpt:${{ github.event.release.tag_name }}${{ matrix.variant }}-${{ matrix.platform == 'linux/arm64' && 'arm64' || 'amd64' }}
ghcr.io/${{ github.repository_owner }}/docsgpt:${{ github.event.release.tag_name }}${{ matrix.variant }}-${{ matrix.platform == 'linux/arm64' && 'arm64' || 'amd64' }}
${{ env.DOCKERHUB_NAMESPACE }}/docsgpt:${{ env.RELEASE_TAG }}${{ matrix.variant }}-${{ matrix.platform == 'linux/arm64' && 'arm64' || 'amd64' }}
ghcr.io/${{ github.repository_owner }}/docsgpt:${{ env.RELEASE_TAG }}${{ matrix.variant }}-${{ matrix.platform == 'linux/arm64' && 'arm64' || 'amd64' }}
labels: ${{ steps.meta.outputs.labels }}
provenance: false
sbom: false
@@ -118,7 +141,7 @@ jobs:
- name: Create and push multi-arch manifests
env:
TAG: ${{ github.event.release.tag_name }}${{ matrix.variant }}
TAG: ${{ env.RELEASE_TAG }}${{ matrix.variant }}
LATEST: latest${{ matrix.variant }}
run: |
set -e
@@ -145,6 +168,6 @@ jobs:
- name: Attach the standalone compose file to the release
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TAG: ${{ github.event.release.tag_name }}
TAG: ${{ env.RELEASE_TAG }}
run: |
gh release upload "$TAG" deployment/docker-compose-standalone.yaml --clobber
+20 -8
View File
@@ -1,13 +1,21 @@
name: Publish to PyPI
# Trusted publishing: PyPI trusts this workflow file in arc53/DocsGPT running
# in the `pypi` environment, so no API token is stored. A published release
# (which the backend-release workflow creates when docsgpt/version.py changes
# on main) publishes to PyPI; a manual run publishes to TestPyPI by default.
# in the `pypi` environment, so no API token is stored. The backend-release
# workflow calls this one after it tags a version bump on main (a release it
# creates with GITHUB_TOKEN never fires the release trigger); a release created
# by hand publishes through that trigger; a manual run publishes to TestPyPI
# by default.
on:
release:
types: [published]
workflow_call:
inputs:
version:
description: Version the archives must carry (the release tag)
type: string
required: true
workflow_dispatch:
inputs:
target:
@@ -19,6 +27,11 @@ on:
permissions:
contents: read
env:
# The version being released: passed in by the caller, or the release's tag;
# empty for a manual run, which publishes whatever main builds.
RELEASE_VERSION: ${{ inputs.version || github.event.release.tag_name }}
jobs:
build:
if: github.repository == 'arc53/DocsGPT'
@@ -37,11 +50,9 @@ jobs:
run: uv build
- name: The archives carry the released version
if: github.event_name == 'release'
env:
TAG: ${{ github.event.release.tag_name }}
if: env.RELEASE_VERSION != ''
run: |
ls "dist/docsgpt-${TAG}.tar.gz" "dist/docsgpt-${TAG}-py3-none-any.whl"
ls "dist/docsgpt-${RELEASE_VERSION}.tar.gz" "dist/docsgpt-${RELEASE_VERSION}-py3-none-any.whl"
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
@@ -50,7 +61,8 @@ jobs:
if-no-files-found: error
publish-pypi:
if: github.event_name == 'release' || inputs.target == 'pypi'
# A release event, a call from backend-release, or a manual run aimed at PyPI.
if: github.event_name == 'release' || inputs.version != '' || inputs.target == 'pypi'
needs: build
runs-on: ubuntu-latest
environment: