diff --git a/.github/workflows/backend-release.yml b/.github/workflows/backend-release.yml index 85ba4b48..2b766b01 100644 --- a/.github/workflows/backend-release.yml +++ b/.github/workflows/backend-release.yml @@ -1,8 +1,8 @@ name: Backend release # A version bump on main tags the commit, creates the GitHub release, then -# publishes the backend and frontend Docker images and the PyPI package by -# calling those workflows. +# publishes the backend, frontend and sandbox 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 @@ -123,6 +123,19 @@ jobs: contents: read packages: write + sandbox: + needs: release + if: needs.release.outputs.created == 'true' + uses: $/.github/workflows/sandbox-image.yml + with: + version: ${{ needs.release.outputs.version }} + secrets: + DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }} + DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }} + permissions: + contents: read + packages: write + pypi: needs: release if: needs.release.outputs.created == 'true' diff --git a/.github/workflows/sandbox-image.yml b/.github/workflows/sandbox-image.yml new file mode 100644 index 00000000..4541be4f --- /dev/null +++ b/.github/workflows/sandbox-image.yml @@ -0,0 +1,168 @@ +name: Build and push the docsgpt-sandbox image + +# The opt-in code-execution runner (deployment/sandbox). Compose builds it from +# the checkout, but Kubernetes cannot build, so the manifest under +# deployment/k8s/deployments/sandbox-deploy.yaml needs a published image. +# +# Three ways in: a push to main that touches the runner (tagged `develop`), a +# release created by hand (the release event), or a call from 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. `github.event_name` is the event that started the +# whole run, which is `push` when backend-release calls this, so the tag comes +# from the inputs and the release payload instead. + +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 + push: + branches: [main] + paths: + - 'deployment/sandbox/**' + - '.github/workflows/sandbox-image.yml' + +permissions: + contents: read + +env: + # The version being published, or `develop` for a push to main. + RELEASE_TAG: ${{ inputs.version || github.event.release.tag_name || 'develop' }} + # A release also moves `latest`; a push to main moves nothing but `develop`. + MOVING_TAG: ${{ (inputs.version || github.event.release.tag_name) && 'latest' || '' }} + +jobs: + build: + if: github.repository == 'arc53/DocsGPT' + # Publishing jobs run in a GitHub Actions environment so the registry + # credentials can be scoped to it and protection rules (required reviewers, + # branch restrictions) applied in the repository settings. + environment: docker-hub + env: + # Public namespace the compose files and manifests pull from; the login + # secret only authenticates the push. + DOCKERHUB_NAMESPACE: arc53 + strategy: + matrix: + include: + - platform: linux/amd64 + runner: ubuntu-latest + suffix: amd64 + - platform: linux/arm64 + runner: ubuntu-24.04-arm + suffix: arm64 + runs-on: ${{ matrix.runner }} + permissions: + contents: read + packages: write + steps: + - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0 + with: + persist-credentials: false + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0 + with: + driver: docker-container + install: true + + - name: Login to DockerHub + uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0 + with: + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_PASSWORD }} + + - name: Login to ghcr.io + uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0 + with: + registry: ghcr.io + username: ${{ github.repository_owner }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Image metadata (OCI labels) + id: meta + uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # v5.10.0 + with: + images: | + ${{ env.DOCKERHUB_NAMESPACE }}/docsgpt-sandbox + ghcr.io/${{ github.repository_owner }}/docsgpt-sandbox + labels: | + org.opencontainers.image.title=DocsGPT sandbox runner + org.opencontainers.image.version=${{ env.RELEASE_TAG }} + + - name: Build and push platform-specific images + uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6.19.2 + with: + file: './deployment/sandbox/Dockerfile' + platforms: ${{ matrix.platform }} + context: ./deployment/sandbox + push: true + tags: | + ${{ env.DOCKERHUB_NAMESPACE }}/docsgpt-sandbox:${{ env.RELEASE_TAG }}-${{ matrix.suffix }} + ghcr.io/${{ github.repository_owner }}/docsgpt-sandbox:${{ env.RELEASE_TAG }}-${{ matrix.suffix }} + labels: ${{ steps.meta.outputs.labels }} + provenance: false + sbom: false + cache-from: type=registry,ref=${{ env.DOCKERHUB_NAMESPACE }}/docsgpt-sandbox:develop + cache-to: type=inline + + manifest: + if: github.repository == 'arc53/DocsGPT' + # Publishing jobs run in a GitHub Actions environment so the registry + # credentials can be scoped to it and protection rules (required reviewers, + # branch restrictions) applied in the repository settings. + environment: docker-hub + env: + # Public namespace the compose files and manifests pull from; the login + # secret only authenticates the push. + DOCKERHUB_NAMESPACE: arc53 + needs: build + runs-on: ubuntu-latest + permissions: + packages: write + steps: + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0 + with: + driver: docker-container + install: true + + - name: Login to DockerHub + uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0 + with: + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_PASSWORD }} + + - name: Login to ghcr.io + uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0 + with: + registry: ghcr.io + username: ${{ github.repository_owner }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Create and push multi-arch manifests + env: + TAG: ${{ env.RELEASE_TAG }} + MOVING: ${{ env.MOVING_TAG }} + run: | + set -e + # $MOVING is deliberately unquoted: it is empty for a push to main, + # and an empty word would create a manifest named "$repo:". + for repo in "$DOCKERHUB_NAMESPACE/docsgpt-sandbox" "ghcr.io/${{ github.repository_owner }}/docsgpt-sandbox"; do + for name in "$TAG" $MOVING; do + docker manifest create "$repo:$name" \ + --amend "$repo:$TAG-amd64" \ + --amend "$repo:$TAG-arm64" + docker manifest push "$repo:$name" + done + done diff --git a/deployment/k8s/deployments/sandbox-deploy.yaml b/deployment/k8s/deployments/sandbox-deploy.yaml index ca82601b..d917cc1c 100644 --- a/deployment/k8s/deployments/sandbox-deploy.yaml +++ b/deployment/k8s/deployments/sandbox-deploy.yaml @@ -21,6 +21,12 @@ # sibling kernels or bypass the session cap). The gateway fails closed if the # token is unset. # +# The image is built from deployment/sandbox and published as +# arc53/docsgpt-sandbox (also ghcr.io/arc53/docsgpt-sandbox) by the release +# workflow, with `develop` tracking main. It is pulled here by the floating +# `latest` tag: pin it to a release tag if you would rather not pick up a new +# runner runtime on a pod restart. +# # On Linux prod, schedule this onto a gVisor `runsc` RuntimeClass for kernel # isolation (uncomment `runtimeClassName` once the node has it installed). #