From 18831a42eccee7f24438677db5cbfadebf2703bb Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Mon, 7 Sep 2026 14:39:58 +0000 Subject: [PATCH 1/4] ci: build the prod template on every cluster (foxtrot, juliett, tango) Co-Authored-By: mish@e2b.dev --- .github/workflows/build_prod_template.yml | 134 +++++++----------- .../workflows/build_prod_template_cluster.yml | 91 ++++++++++++ .github/workflows/release.yml | 90 +++++++----- template/README.md | 20 +++ 4 files changed, 216 insertions(+), 119 deletions(-) create mode 100644 .github/workflows/build_prod_template_cluster.yml diff --git a/.github/workflows/build_prod_template.yml b/.github/workflows/build_prod_template.yml index ec9455b4..7480acdb 100644 --- a/.github/workflows/build_prod_template.yml +++ b/.github/workflows/build_prod_template.yml @@ -4,107 +4,69 @@ on: workflow_dispatch: inputs: target_environment: - description: Target environment + description: Target environment (`all` = every production cluster) required: true type: choice - default: foxtrot + default: all options: + - all - foxtrot - - staging - juliett + - tango + - staging skip_cache: description: Skip build cache required: false type: boolean default: false -concurrency: - group: Release-${{ github.ref }}-${{ inputs.target_environment }} - cancel-in-progress: false - permissions: contents: read +# One job per cluster, each naming its secret statically, so a build job only +# ever receives the one key it needs. Builds on the same cluster serialize via +# the concurrency group inside the called workflow. jobs: - build-template: - name: Build E2B template - runs-on: ubuntu-latest - steps: - - name: Checkout repository - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0 - - - name: Parse .tool-versions - uses: wistia/parse-tool-versions@32f568a4ffd4bfa7720ebf93f171597d1ebc979a # v2.1.1 - with: - filename: '.tool-versions' - uppercase: 'true' - prefix: 'tool_version_' - - - uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0 - with: - python-version: '${{ env.TOOL_VERSION_PYTHON }}' - - - name: Install development dependencies - working-directory: ./template - run: pip install -r requirements-dev.txt - - - name: Resolve target environment - env: - TARGET_ENVIRONMENT: ${{ inputs.target_environment }} - FOXTROT_DOMAIN: ${{ vars.E2B_DOMAIN }} - FOXTROT_API_KEY: ${{ secrets.E2B_PROD_API_KEY }} - STAGING_API_KEY: ${{ secrets.E2B_STAGING_API_KEY }} - JULIETT_API_KEY: ${{ secrets.E2B_JULIETT_API_KEY }} - run: | - set -eu - - case "$TARGET_ENVIRONMENT" in - foxtrot) - E2B_DOMAIN="$FOXTROT_DOMAIN" - E2B_API_KEY="$FOXTROT_API_KEY" - ;; - staging) - E2B_DOMAIN="e2b-staging.dev" - E2B_API_KEY="$STAGING_API_KEY" - ;; - juliett) - E2B_DOMAIN="e2b-juliett.dev" - E2B_API_KEY="$JULIETT_API_KEY" - ;; - *) - echo "Unknown target environment: $TARGET_ENVIRONMENT" >&2 - exit 1 - ;; - esac - - if [ -z "$E2B_DOMAIN" ]; then - echo "Missing E2B domain for target environment: $TARGET_ENVIRONMENT" >&2 - exit 1 - fi - - if [ -z "$E2B_API_KEY" ]; then - echo "Missing API key secret for target environment: $TARGET_ENVIRONMENT" >&2 - exit 1 - fi - - echo "::add-mask::$E2B_API_KEY" + foxtrot: + name: foxtrot + if: ${{ inputs.target_environment == 'all' || inputs.target_environment == 'foxtrot' }} + uses: ./.github/workflows/build_prod_template_cluster.yml + with: + cluster: foxtrot + E2B_DOMAIN: ${{ vars.E2B_DOMAIN }} + skip_cache: ${{ inputs.skip_cache }} + secrets: + E2B_API_KEY: ${{ secrets.E2B_PROD_API_KEY }} - { - echo "E2B_DOMAIN=$E2B_DOMAIN" - echo "E2B_API_KEY=$E2B_API_KEY" - } >> "$GITHUB_ENV" + juliett: + name: juliett + if: ${{ inputs.target_environment == 'all' || inputs.target_environment == 'juliett' }} + uses: ./.github/workflows/build_prod_template_cluster.yml + with: + cluster: juliett + E2B_DOMAIN: e2b-juliett.dev + skip_cache: ${{ inputs.skip_cache }} + secrets: + E2B_API_KEY: ${{ secrets.E2B_JULIETT_API_KEY }} - { - echo "### Build target" - echo - echo "Target: $TARGET_ENVIRONMENT" - echo "Domain: $E2B_DOMAIN" - } >> "$GITHUB_STEP_SUMMARY" + tango: + name: tango + if: ${{ inputs.target_environment == 'all' || inputs.target_environment == 'tango' }} + uses: ./.github/workflows/build_prod_template_cluster.yml + with: + cluster: tango + E2B_DOMAIN: e2b-tango.dev + skip_cache: ${{ inputs.skip_cache }} + secrets: + E2B_API_KEY: ${{ secrets.E2B_TANGO_API_KEY }} - - name: Build E2B template - id: build-template - working-directory: ./template - run: | - python build_prod.py - env: - SKIP_CACHE: ${{ inputs.skip_cache }} + staging: + name: staging + if: ${{ inputs.target_environment == 'staging' }} + uses: ./.github/workflows/build_prod_template_cluster.yml + with: + cluster: staging + E2B_DOMAIN: e2b-staging.dev + skip_cache: ${{ inputs.skip_cache }} + secrets: + E2B_API_KEY: ${{ secrets.E2B_STAGING_API_KEY }} diff --git a/.github/workflows/build_prod_template_cluster.yml b/.github/workflows/build_prod_template_cluster.yml new file mode 100644 index 00000000..2692ac9e --- /dev/null +++ b/.github/workflows/build_prod_template_cluster.yml @@ -0,0 +1,91 @@ +name: Build Prod Template (single cluster) + +# Builds the production `code-interpreter-v1` template on ONE E2B cluster. +# Every cluster is a separate tenancy with its own team, API key and template +# registry, so a template built on one cluster does not exist on the others. +# Callers run this once per cluster, each naming that cluster's secret +# statically: a dynamic `secrets[...]` index would hand the job every +# organization and repository secret instead of just the one it asked for. + +on: + workflow_call: + inputs: + cluster: + description: Cluster name, for job titles, the concurrency group and the summary. + required: true + type: string + E2B_DOMAIN: + description: E2B_DOMAIN for the cluster. Empty means the SDK default. + required: false + type: string + default: '' + skip_cache: + description: Skip build cache + required: false + type: boolean + default: false + secrets: + E2B_API_KEY: + description: API key for a team on this cluster. + required: true + +permissions: + contents: read + +jobs: + build-template: + name: Build E2B template (${{ inputs.cluster }}) + runs-on: ubuntu-latest + # Builds on the same cluster serialize, across the release workflow and + # manual dispatches alike; different clusters run concurrently. + concurrency: + group: Release-${{ github.ref }}-${{ inputs.cluster }} + cancel-in-progress: false + steps: + - name: Checkout repository + uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0 + + - name: Check the cluster API key + env: + CLUSTER: ${{ inputs.cluster }} + E2B_API_KEY: ${{ secrets.E2B_API_KEY }} + run: | + if [ -z "$E2B_API_KEY" ]; then + echo "::error::Missing API key secret for cluster: $CLUSTER" >&2 + exit 1 + fi + + - name: Parse .tool-versions + uses: wistia/parse-tool-versions@32f568a4ffd4bfa7720ebf93f171597d1ebc979a # v2.1.1 + with: + filename: '.tool-versions' + uppercase: 'true' + prefix: 'tool_version_' + + - uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0 + with: + python-version: '${{ env.TOOL_VERSION_PYTHON }}' + + - name: Install development dependencies + working-directory: ./template + run: pip install -r requirements-dev.txt + + - name: Build E2B template + working-directory: ./template + run: python build_prod.py + env: + E2B_API_KEY: ${{ secrets.E2B_API_KEY }} + E2B_DOMAIN: ${{ inputs.E2B_DOMAIN }} + SKIP_CACHE: ${{ inputs.skip_cache }} + + - name: Summarize + env: + CLUSTER: ${{ inputs.cluster }} + E2B_DOMAIN: ${{ inputs.E2B_DOMAIN }} + run: | + { + echo "### Build target" + echo + echo "Cluster: $CLUSTER" + echo "Domain: ${E2B_DOMAIN:-(default)}" + } >> "$GITHUB_STEP_SUMMARY" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 523d8af4..07477829 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -7,7 +7,10 @@ on: # makes a re-dispatch the recovery path too. workflow_dispatch: {} -concurrency: Release-${{ github.ref }}-foxtrot +# One release at a time. The per-cluster template builds serialize against +# manual `Build Prod Template` runs through their own groups, inside the +# called workflow. +concurrency: Release-${{ github.ref }} permissions: id-token: write @@ -223,46 +226,60 @@ jobs: --push \ --tag ${{ secrets.DOCKERHUB_USERNAME }}/code-interpreter:latest -f - . - build-template: - name: Build E2B template - runs-on: ubuntu-latest + # The template is built on every production cluster. Clusters are separate + # tenancies with separate template registries, so each needs its own build. + # One job per cluster, each naming its secret statically, so a build job + # only ever receives the one key it needs. + build-template-foxtrot: + name: Build E2B template (foxtrot) needs: [preflight, build-docker-image] if: (!cancelled()) && !contains(needs.*.result, 'failure') && (needs.preflight.outputs.template == 'true' || needs.preflight.outputs.charts == 'true') - steps: - - name: Checkout repository - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0 - - - name: Parse .tool-versions - uses: wistia/parse-tool-versions@32f568a4ffd4bfa7720ebf93f171597d1ebc979a # v2.1.1 - with: - filename: '.tool-versions' - uppercase: 'true' - prefix: 'tool_version_' - - - uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0 - with: - python-version: '${{ env.TOOL_VERSION_PYTHON }}' - - - name: Install development dependencies - working-directory: ./template - run: pip install -r requirements-dev.txt - - - name: Build E2B template - id: build-template - working-directory: ./template - run: | - python build_prod.py - env: - E2B_API_KEY: ${{ secrets.E2B_PROD_API_KEY }} - E2B_DOMAIN: ${{ vars.E2B_DOMAIN }} + uses: ./.github/workflows/build_prod_template_cluster.yml + with: + cluster: foxtrot + E2B_DOMAIN: ${{ vars.E2B_DOMAIN }} + secrets: + E2B_API_KEY: ${{ secrets.E2B_PROD_API_KEY }} + + build-template-juliett: + name: Build E2B template (juliett) + needs: [preflight, build-docker-image] + if: (!cancelled()) && + !contains(needs.*.result, 'failure') && + (needs.preflight.outputs.template == 'true' || needs.preflight.outputs.charts == 'true') + uses: ./.github/workflows/build_prod_template_cluster.yml + with: + cluster: juliett + E2B_DOMAIN: e2b-juliett.dev + secrets: + E2B_API_KEY: ${{ secrets.E2B_JULIETT_API_KEY }} + + build-template-tango: + name: Build E2B template (tango) + needs: [preflight, build-docker-image] + if: (!cancelled()) && + !contains(needs.*.result, 'failure') && + (needs.preflight.outputs.template == 'true' || needs.preflight.outputs.charts == 'true') + uses: ./.github/workflows/build_prod_template_cluster.yml + with: + cluster: tango + E2B_DOMAIN: e2b-tango.dev + secrets: + E2B_API_KEY: ${{ secrets.E2B_TANGO_API_KEY }} release: # Every upstream job is listed, not just the last one: a job that fails makes # its dependents *skip*, and a skipped job is not a failure — so gating on # `needs.*.result` only works for the jobs this one depends on directly. - needs: [preflight, charts-release, build-docker-image, build-template] + needs: + - preflight + - charts-release + - build-docker-image + - build-template-foxtrot + - build-template-juliett + - build-template-tango if: (!cancelled()) && !contains(needs.*.result, 'failure') && needs.preflight.outputs.release == 'true' @@ -404,7 +421,14 @@ jobs: report-failure: # `preflight` included so a failure there is reported too, whether or not # `failure()` looks past this job's direct dependencies. - needs: [preflight, charts-release, build-docker-image, build-template, release] + needs: + - preflight + - charts-release + - build-docker-image + - build-template-foxtrot + - build-template-juliett + - build-template-tango + - release if: failure() name: Code Interpreter Release Failed - Slack Notification runs-on: ubuntu-latest diff --git a/template/README.md b/template/README.md index 4f64fabe..99fb9692 100644 --- a/template/README.md +++ b/template/README.md @@ -29,6 +29,26 @@ Set `SKIP_CACHE=true` to force a clean rebuild that ignores the layer cache: SKIP_CACHE=true python build_prod.py ``` +### Clusters + +Every E2B cluster is a separate tenancy with its own team, API key and template +registry, so a template built on one cluster does not exist on the others. CI +(`release.yml` and the manual `Build Prod Template` workflow) builds the +template on every production cluster, one job per cluster: + +| Cluster | Role | `E2B_DOMAIN` | API key secret | +|---------|------|--------------|----------------| +| foxtrot | US production (default) | `vars.E2B_DOMAIN` (unset = SDK default) | `E2B_PROD_API_KEY` | +| juliett | EU production (europe-west1) | `e2b-juliett.dev` | `E2B_JULIETT_API_KEY` | +| tango | APAC production (asia-southeast1) | `e2b-tango.dev` | `E2B_TANGO_API_KEY` | + +To build locally against a non-default cluster, set that cluster's `E2B_DOMAIN` +and API key: + +``` +E2B_DOMAIN=e2b-juliett.dev E2B_API_KEY= python build_prod.py +``` + If you want to customize the Code Interpreter sandbox (e.g.: add a preinstalled package) you can do that by creating a [custom sandbox template](https://e2b.dev/docs/template/quickstart). ## Creating a custom template From 3ed1db32867f58807a3e68ba04a1a8d20e6a57bb Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Mon, 7 Sep 2026 14:58:10 +0000 Subject: [PATCH 2/4] docs: drop README clusters section Co-Authored-By: mish@e2b.dev --- template/README.md | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/template/README.md b/template/README.md index 99fb9692..4f64fabe 100644 --- a/template/README.md +++ b/template/README.md @@ -29,26 +29,6 @@ Set `SKIP_CACHE=true` to force a clean rebuild that ignores the layer cache: SKIP_CACHE=true python build_prod.py ``` -### Clusters - -Every E2B cluster is a separate tenancy with its own team, API key and template -registry, so a template built on one cluster does not exist on the others. CI -(`release.yml` and the manual `Build Prod Template` workflow) builds the -template on every production cluster, one job per cluster: - -| Cluster | Role | `E2B_DOMAIN` | API key secret | -|---------|------|--------------|----------------| -| foxtrot | US production (default) | `vars.E2B_DOMAIN` (unset = SDK default) | `E2B_PROD_API_KEY` | -| juliett | EU production (europe-west1) | `e2b-juliett.dev` | `E2B_JULIETT_API_KEY` | -| tango | APAC production (asia-southeast1) | `e2b-tango.dev` | `E2B_TANGO_API_KEY` | - -To build locally against a non-default cluster, set that cluster's `E2B_DOMAIN` -and API key: - -``` -E2B_DOMAIN=e2b-juliett.dev E2B_API_KEY= python build_prod.py -``` - If you want to customize the Code Interpreter sandbox (e.g.: add a preinstalled package) you can do that by creating a [custom sandbox template](https://e2b.dev/docs/template/quickstart). ## Creating a custom template From f8cb5ab8d73f6a37cebc3f29e9782a760f600144 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Mon, 7 Sep 2026 15:03:21 +0000 Subject: [PATCH 3/4] ci: build clusters from a matrix instead of one job per cluster Co-Authored-By: mish@e2b.dev --- .github/workflows/build_prod_template.yml | 49 +------- .../workflows/build_prod_template_cluster.yml | 91 -------------- .../build_prod_template_clusters.yml | 113 ++++++++++++++++++ .github/workflows/release.yml | 52 ++------ 4 files changed, 127 insertions(+), 178 deletions(-) delete mode 100644 .github/workflows/build_prod_template_cluster.yml create mode 100644 .github/workflows/build_prod_template_clusters.yml diff --git a/.github/workflows/build_prod_template.yml b/.github/workflows/build_prod_template.yml index 7480acdb..50971d29 100644 --- a/.github/workflows/build_prod_template.yml +++ b/.github/workflows/build_prod_template.yml @@ -23,50 +23,11 @@ on: permissions: contents: read -# One job per cluster, each naming its secret statically, so a build job only -# ever receives the one key it needs. Builds on the same cluster serialize via -# the concurrency group inside the called workflow. jobs: - foxtrot: - name: foxtrot - if: ${{ inputs.target_environment == 'all' || inputs.target_environment == 'foxtrot' }} - uses: ./.github/workflows/build_prod_template_cluster.yml + build-template: + name: Build + uses: ./.github/workflows/build_prod_template_clusters.yml with: - cluster: foxtrot - E2B_DOMAIN: ${{ vars.E2B_DOMAIN }} + target: ${{ inputs.target_environment }} skip_cache: ${{ inputs.skip_cache }} - secrets: - E2B_API_KEY: ${{ secrets.E2B_PROD_API_KEY }} - - juliett: - name: juliett - if: ${{ inputs.target_environment == 'all' || inputs.target_environment == 'juliett' }} - uses: ./.github/workflows/build_prod_template_cluster.yml - with: - cluster: juliett - E2B_DOMAIN: e2b-juliett.dev - skip_cache: ${{ inputs.skip_cache }} - secrets: - E2B_API_KEY: ${{ secrets.E2B_JULIETT_API_KEY }} - - tango: - name: tango - if: ${{ inputs.target_environment == 'all' || inputs.target_environment == 'tango' }} - uses: ./.github/workflows/build_prod_template_cluster.yml - with: - cluster: tango - E2B_DOMAIN: e2b-tango.dev - skip_cache: ${{ inputs.skip_cache }} - secrets: - E2B_API_KEY: ${{ secrets.E2B_TANGO_API_KEY }} - - staging: - name: staging - if: ${{ inputs.target_environment == 'staging' }} - uses: ./.github/workflows/build_prod_template_cluster.yml - with: - cluster: staging - E2B_DOMAIN: e2b-staging.dev - skip_cache: ${{ inputs.skip_cache }} - secrets: - E2B_API_KEY: ${{ secrets.E2B_STAGING_API_KEY }} + secrets: inherit diff --git a/.github/workflows/build_prod_template_cluster.yml b/.github/workflows/build_prod_template_cluster.yml deleted file mode 100644 index 2692ac9e..00000000 --- a/.github/workflows/build_prod_template_cluster.yml +++ /dev/null @@ -1,91 +0,0 @@ -name: Build Prod Template (single cluster) - -# Builds the production `code-interpreter-v1` template on ONE E2B cluster. -# Every cluster is a separate tenancy with its own team, API key and template -# registry, so a template built on one cluster does not exist on the others. -# Callers run this once per cluster, each naming that cluster's secret -# statically: a dynamic `secrets[...]` index would hand the job every -# organization and repository secret instead of just the one it asked for. - -on: - workflow_call: - inputs: - cluster: - description: Cluster name, for job titles, the concurrency group and the summary. - required: true - type: string - E2B_DOMAIN: - description: E2B_DOMAIN for the cluster. Empty means the SDK default. - required: false - type: string - default: '' - skip_cache: - description: Skip build cache - required: false - type: boolean - default: false - secrets: - E2B_API_KEY: - description: API key for a team on this cluster. - required: true - -permissions: - contents: read - -jobs: - build-template: - name: Build E2B template (${{ inputs.cluster }}) - runs-on: ubuntu-latest - # Builds on the same cluster serialize, across the release workflow and - # manual dispatches alike; different clusters run concurrently. - concurrency: - group: Release-${{ github.ref }}-${{ inputs.cluster }} - cancel-in-progress: false - steps: - - name: Checkout repository - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0 - - - name: Check the cluster API key - env: - CLUSTER: ${{ inputs.cluster }} - E2B_API_KEY: ${{ secrets.E2B_API_KEY }} - run: | - if [ -z "$E2B_API_KEY" ]; then - echo "::error::Missing API key secret for cluster: $CLUSTER" >&2 - exit 1 - fi - - - name: Parse .tool-versions - uses: wistia/parse-tool-versions@32f568a4ffd4bfa7720ebf93f171597d1ebc979a # v2.1.1 - with: - filename: '.tool-versions' - uppercase: 'true' - prefix: 'tool_version_' - - - uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0 - with: - python-version: '${{ env.TOOL_VERSION_PYTHON }}' - - - name: Install development dependencies - working-directory: ./template - run: pip install -r requirements-dev.txt - - - name: Build E2B template - working-directory: ./template - run: python build_prod.py - env: - E2B_API_KEY: ${{ secrets.E2B_API_KEY }} - E2B_DOMAIN: ${{ inputs.E2B_DOMAIN }} - SKIP_CACHE: ${{ inputs.skip_cache }} - - - name: Summarize - env: - CLUSTER: ${{ inputs.cluster }} - E2B_DOMAIN: ${{ inputs.E2B_DOMAIN }} - run: | - { - echo "### Build target" - echo - echo "Cluster: $CLUSTER" - echo "Domain: ${E2B_DOMAIN:-(default)}" - } >> "$GITHUB_STEP_SUMMARY" diff --git a/.github/workflows/build_prod_template_clusters.yml b/.github/workflows/build_prod_template_clusters.yml new file mode 100644 index 00000000..f9b0a672 --- /dev/null +++ b/.github/workflows/build_prod_template_clusters.yml @@ -0,0 +1,113 @@ +name: Build Prod Template (clusters) + +# Builds the production `code-interpreter-v1` template on E2B clusters. Every +# cluster is a separate tenancy with its own team, API key and template +# registry, so a template built on one cluster does not exist on the others. +# The cluster map (name, domain, API key secret) lives in the `plan` job. + +on: + workflow_call: + inputs: + target: + description: Cluster to build on, or `all` for every production cluster. + required: false + type: string + default: all + skip_cache: + description: Skip build cache + required: false + type: boolean + default: false + +permissions: + contents: read + +jobs: + plan: + name: Select clusters + runs-on: ubuntu-latest + outputs: + matrix: ${{ steps.plan.outputs.matrix }} + steps: + - id: plan + env: + TARGET: ${{ inputs.target }} + FOXTROT_DOMAIN: ${{ vars.E2B_DOMAIN }} + run: | + MATRIX=$(jq -nc --arg target "$TARGET" --arg foxtrot_domain "$FOXTROT_DOMAIN" ' + [ + { cluster: "foxtrot", domain: $foxtrot_domain, api_key_secret: "E2B_PROD_API_KEY", production: true }, + { cluster: "juliett", domain: "e2b-juliett.dev", api_key_secret: "E2B_JULIETT_API_KEY", production: true }, + { cluster: "tango", domain: "e2b-tango.dev", api_key_secret: "E2B_TANGO_API_KEY", production: true }, + { cluster: "staging", domain: "e2b-staging.dev", api_key_secret: "E2B_STAGING_API_KEY", production: false } + ] + | map(select(if $target == "all" then .production else .cluster == $target end)) + | map(del(.production)) + ') + if [ "$MATRIX" = "[]" ]; then + echo "::error::Unknown cluster: $TARGET" >&2 + exit 1 + fi + echo "matrix=$MATRIX" >> "$GITHUB_OUTPUT" + + build-template: + name: Build E2B template (${{ matrix.cluster }}) + needs: plan + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + include: ${{ fromJSON(needs.plan.outputs.matrix) }} + # Builds on the same cluster serialize, across the release workflow and + # manual dispatches alike; different clusters run concurrently. + concurrency: + group: Release-${{ github.ref }}-${{ matrix.cluster }} + cancel-in-progress: false + env: + E2B_API_KEY: ${{ secrets[matrix.api_key_secret] }} + E2B_DOMAIN: ${{ matrix.domain }} + steps: + - name: Check the cluster API key + env: + CLUSTER: ${{ matrix.cluster }} + API_KEY_SECRET: ${{ matrix.api_key_secret }} + run: | + if [ -z "$E2B_API_KEY" ]; then + echo "::error::Missing secret $API_KEY_SECRET for cluster: $CLUSTER" >&2 + exit 1 + fi + + - name: Checkout repository + uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0 + + - name: Parse .tool-versions + uses: wistia/parse-tool-versions@32f568a4ffd4bfa7720ebf93f171597d1ebc979a # v2.1.1 + with: + filename: '.tool-versions' + uppercase: 'true' + prefix: 'tool_version_' + + - uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0 + with: + python-version: '${{ env.TOOL_VERSION_PYTHON }}' + + - name: Install development dependencies + working-directory: ./template + run: pip install -r requirements-dev.txt + + - name: Build E2B template + working-directory: ./template + run: python build_prod.py + env: + SKIP_CACHE: ${{ inputs.skip_cache }} + + - name: Summarize + env: + CLUSTER: ${{ matrix.cluster }} + run: | + { + echo "### Build target" + echo + echo "Cluster: $CLUSTER" + echo "Domain: ${E2B_DOMAIN:-(default)}" + } >> "$GITHUB_STEP_SUMMARY" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 07477829..767bb350 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -226,48 +226,18 @@ jobs: --push \ --tag ${{ secrets.DOCKERHUB_USERNAME }}/code-interpreter:latest -f - . - # The template is built on every production cluster. Clusters are separate - # tenancies with separate template registries, so each needs its own build. - # One job per cluster, each naming its secret statically, so a build job - # only ever receives the one key it needs. - build-template-foxtrot: - name: Build E2B template (foxtrot) + # Builds the template on every production cluster; a failure on any of them + # blocks the release. + build-template: + name: Build E2B template needs: [preflight, build-docker-image] if: (!cancelled()) && !contains(needs.*.result, 'failure') && (needs.preflight.outputs.template == 'true' || needs.preflight.outputs.charts == 'true') - uses: ./.github/workflows/build_prod_template_cluster.yml + uses: ./.github/workflows/build_prod_template_clusters.yml with: - cluster: foxtrot - E2B_DOMAIN: ${{ vars.E2B_DOMAIN }} - secrets: - E2B_API_KEY: ${{ secrets.E2B_PROD_API_KEY }} - - build-template-juliett: - name: Build E2B template (juliett) - needs: [preflight, build-docker-image] - if: (!cancelled()) && - !contains(needs.*.result, 'failure') && - (needs.preflight.outputs.template == 'true' || needs.preflight.outputs.charts == 'true') - uses: ./.github/workflows/build_prod_template_cluster.yml - with: - cluster: juliett - E2B_DOMAIN: e2b-juliett.dev - secrets: - E2B_API_KEY: ${{ secrets.E2B_JULIETT_API_KEY }} - - build-template-tango: - name: Build E2B template (tango) - needs: [preflight, build-docker-image] - if: (!cancelled()) && - !contains(needs.*.result, 'failure') && - (needs.preflight.outputs.template == 'true' || needs.preflight.outputs.charts == 'true') - uses: ./.github/workflows/build_prod_template_cluster.yml - with: - cluster: tango - E2B_DOMAIN: e2b-tango.dev - secrets: - E2B_API_KEY: ${{ secrets.E2B_TANGO_API_KEY }} + target: all + secrets: inherit release: # Every upstream job is listed, not just the last one: a job that fails makes @@ -277,9 +247,7 @@ jobs: - preflight - charts-release - build-docker-image - - build-template-foxtrot - - build-template-juliett - - build-template-tango + - build-template if: (!cancelled()) && !contains(needs.*.result, 'failure') && needs.preflight.outputs.release == 'true' @@ -425,9 +393,7 @@ jobs: - preflight - charts-release - build-docker-image - - build-template-foxtrot - - build-template-juliett - - build-template-tango + - build-template - release if: failure() name: Code Interpreter Release Failed - Slack Notification From 6bccfc0d7924d481d976b4ee8796a14f8b278f3f Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Mon, 7 Sep 2026 15:25:10 +0000 Subject: [PATCH 4/4] ci: spell out the foxtrot domain Co-Authored-By: mish@e2b.dev --- .github/workflows/build_prod_template_clusters.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build_prod_template_clusters.yml b/.github/workflows/build_prod_template_clusters.yml index f9b0a672..8d570c97 100644 --- a/.github/workflows/build_prod_template_clusters.yml +++ b/.github/workflows/build_prod_template_clusters.yml @@ -32,11 +32,10 @@ jobs: - id: plan env: TARGET: ${{ inputs.target }} - FOXTROT_DOMAIN: ${{ vars.E2B_DOMAIN }} run: | - MATRIX=$(jq -nc --arg target "$TARGET" --arg foxtrot_domain "$FOXTROT_DOMAIN" ' + MATRIX=$(jq -nc --arg target "$TARGET" ' [ - { cluster: "foxtrot", domain: $foxtrot_domain, api_key_secret: "E2B_PROD_API_KEY", production: true }, + { cluster: "foxtrot", domain: "e2b.dev", api_key_secret: "E2B_PROD_API_KEY", production: true }, { cluster: "juliett", domain: "e2b-juliett.dev", api_key_secret: "E2B_JULIETT_API_KEY", production: true }, { cluster: "tango", domain: "e2b-tango.dev", api_key_secret: "E2B_TANGO_API_KEY", production: true }, { cluster: "staging", domain: "e2b-staging.dev", api_key_secret: "E2B_STAGING_API_KEY", production: false }