From 4727b1a51908d0e842ba2a2a2fdf4ac4f165a923 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:59 +0000 Subject: [PATCH 1/4] ci: build the desktop template on every cluster (foxtrot, juliett, tango) Co-Authored-By: mish@e2b.dev --- .github/workflows/build_template_cluster.yml | 95 ++++++++++++++++++++ .github/workflows/template.yml | 55 ++++++++++-- template/README.md | 20 +++++ 3 files changed, 164 insertions(+), 6 deletions(-) create mode 100644 .github/workflows/build_template_cluster.yml diff --git a/.github/workflows/build_template_cluster.yml b/.github/workflows/build_template_cluster.yml new file mode 100644 index 0000000..211ba59 --- /dev/null +++ b/.github/workflows/build_template_cluster.yml @@ -0,0 +1,95 @@ +name: Build Desktop template (single cluster) + +# Builds the `desktop` 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: '' + 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-22.04 + # Builds on the same cluster serialize; different clusters run concurrently. + concurrency: + group: Template-${{ github.ref }}-${{ inputs.cluster }} + cancel-in-progress: false + defaults: + run: + working-directory: ./template + steps: + - name: Checkout repository + uses: actions/checkout@a37ce9120846195fa4ece8f58b268e6043cb2f26 # v3.7.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 + id: tool-versions + with: + filename: '.tool-versions' + uppercase: 'true' + prefix: 'tool_version_' + + - name: Set up Python + uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 + with: + python-version: '${{ env.TOOL_VERSION_PYTHON }}' + + - name: Install and configure Poetry + uses: snok/install-poetry@a783c322200f0519c7926aa6faa857c4e23e9263 # v1.4.2 + with: + version: ${{ env.TOOL_VERSION_POETRY }} + virtualenvs-create: true + virtualenvs-in-project: true + installer-parallel: true + + - name: Install dependencies + run: poetry install + + - name: Build E2B template + run: poetry run python build_prod.py + env: + E2B_API_KEY: ${{ secrets.E2B_API_KEY }} + E2B_DOMAIN: ${{ inputs.E2B_DOMAIN }} + + - 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/template.yml b/.github/workflows/template.yml index 93d2eb9..ea1ab59 100644 --- a/.github/workflows/template.yml +++ b/.github/workflows/template.yml @@ -5,19 +5,34 @@ on: paths: - 'template/**' - '.github/workflows/template.yml' + - '.github/workflows/build_template_cluster.yml' branches: - main + workflow_dispatch: + inputs: + target_environment: + description: Target cluster (`all` = every production cluster) + required: true + type: choice + default: all + options: + - all + - foxtrot + - juliett + - tango permissions: contents: read jobs: - buildAndPublish: + # The DockerHub image is cluster-independent, so it is only rebuilt on pushes. + buildAndPushImage: + if: ${{ github.event_name == 'push' }} defaults: run: working-directory: ./template - name: Build and Push Images + name: Build and Push Image to DockerHub runs-on: ubuntu-22.04 steps: - name: Checkout repository @@ -64,7 +79,35 @@ jobs: --push \ --tag ${{ secrets.DOCKERHUB_USERNAME }}/desktop:latest -f - files - - name: Build E2B template - run: poetry run python build_prod.py - env: - E2B_API_KEY: ${{ secrets.E2B_API_KEY }} + # 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. + foxtrot: + name: foxtrot + if: ${{ github.event_name == 'push' || inputs.target_environment == 'all' || inputs.target_environment == 'foxtrot' }} + uses: ./.github/workflows/build_template_cluster.yml + with: + cluster: foxtrot + secrets: + E2B_API_KEY: ${{ secrets.E2B_API_KEY }} + + juliett: + name: juliett + if: ${{ github.event_name == 'push' || inputs.target_environment == 'all' || inputs.target_environment == 'juliett' }} + uses: ./.github/workflows/build_template_cluster.yml + with: + cluster: juliett + E2B_DOMAIN: e2b-juliett.dev + secrets: + E2B_API_KEY: ${{ secrets.E2B_JULIETT_API_KEY }} + + tango: + name: tango + if: ${{ github.event_name == 'push' || inputs.target_environment == 'all' || inputs.target_environment == 'tango' }} + uses: ./.github/workflows/build_template_cluster.yml + with: + cluster: tango + E2B_DOMAIN: e2b-tango.dev + secrets: + E2B_API_KEY: ${{ secrets.E2B_TANGO_API_KEY }} diff --git a/template/README.md b/template/README.md index a71bbe3..9470668 100644 --- a/template/README.md +++ b/template/README.md @@ -31,6 +31,26 @@ During development you can build the `desktop-dev` template instead: poetry run python build_dev.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 +(`.github/workflows/template.yml`, on pushes to `main` and on manual dispatch) +builds the template on every production cluster, one job per cluster: + +| Cluster | Role | `E2B_DOMAIN` | API key secret | +|---------|------|--------------|----------------| +| foxtrot | US production (default) | unset | `E2B_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: + +```bash +E2B_DOMAIN=e2b-juliett.dev E2B_API_KEY= poetry run python build_prod.py +``` + If you want to customize the Desktop sandbox (e.g.: add a preinstalled package) you can do that by creating a [custom sandbox template](https://e2b.dev/docs/template/quickstart). From f12a32cc7db27011a4a6a1144314cf368a6f62a6 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:09 +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 9470668..a71bbe3 100644 --- a/template/README.md +++ b/template/README.md @@ -31,26 +31,6 @@ During development you can build the `desktop-dev` template instead: poetry run python build_dev.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 -(`.github/workflows/template.yml`, on pushes to `main` and on manual dispatch) -builds the template on every production cluster, one job per cluster: - -| Cluster | Role | `E2B_DOMAIN` | API key secret | -|---------|------|--------------|----------------| -| foxtrot | US production (default) | unset | `E2B_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: - -```bash -E2B_DOMAIN=e2b-juliett.dev E2B_API_KEY= poetry run python build_prod.py -``` - If you want to customize the Desktop sandbox (e.g.: add a preinstalled package) you can do that by creating a [custom sandbox template](https://e2b.dev/docs/template/quickstart). From 84668c003cee7c7b35e336b222142dcab8f99c67 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:22 +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_template_cluster.yml | 95 ---------------- .github/workflows/template.yml | 108 +++++++++++-------- 2 files changed, 66 insertions(+), 137 deletions(-) delete mode 100644 .github/workflows/build_template_cluster.yml diff --git a/.github/workflows/build_template_cluster.yml b/.github/workflows/build_template_cluster.yml deleted file mode 100644 index 211ba59..0000000 --- a/.github/workflows/build_template_cluster.yml +++ /dev/null @@ -1,95 +0,0 @@ -name: Build Desktop template (single cluster) - -# Builds the `desktop` 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: '' - 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-22.04 - # Builds on the same cluster serialize; different clusters run concurrently. - concurrency: - group: Template-${{ github.ref }}-${{ inputs.cluster }} - cancel-in-progress: false - defaults: - run: - working-directory: ./template - steps: - - name: Checkout repository - uses: actions/checkout@a37ce9120846195fa4ece8f58b268e6043cb2f26 # v3.7.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 - id: tool-versions - with: - filename: '.tool-versions' - uppercase: 'true' - prefix: 'tool_version_' - - - name: Set up Python - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 - with: - python-version: '${{ env.TOOL_VERSION_PYTHON }}' - - - name: Install and configure Poetry - uses: snok/install-poetry@a783c322200f0519c7926aa6faa857c4e23e9263 # v1.4.2 - with: - version: ${{ env.TOOL_VERSION_POETRY }} - virtualenvs-create: true - virtualenvs-in-project: true - installer-parallel: true - - - name: Install dependencies - run: poetry install - - - name: Build E2B template - run: poetry run python build_prod.py - env: - E2B_API_KEY: ${{ secrets.E2B_API_KEY }} - E2B_DOMAIN: ${{ inputs.E2B_DOMAIN }} - - - 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/template.yml b/.github/workflows/template.yml index ea1ab59..8b30e5a 100644 --- a/.github/workflows/template.yml +++ b/.github/workflows/template.yml @@ -5,21 +5,9 @@ on: paths: - 'template/**' - '.github/workflows/template.yml' - - '.github/workflows/build_template_cluster.yml' branches: - main workflow_dispatch: - inputs: - target_environment: - description: Target cluster (`all` = every production cluster) - required: true - type: choice - default: all - options: - - all - - foxtrot - - juliett - - tango permissions: contents: read @@ -81,33 +69,69 @@ jobs: # 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. - foxtrot: - name: foxtrot - if: ${{ github.event_name == 'push' || inputs.target_environment == 'all' || inputs.target_environment == 'foxtrot' }} - uses: ./.github/workflows/build_template_cluster.yml - with: - cluster: foxtrot - secrets: - E2B_API_KEY: ${{ secrets.E2B_API_KEY }} - - juliett: - name: juliett - if: ${{ github.event_name == 'push' || inputs.target_environment == 'all' || inputs.target_environment == 'juliett' }} - uses: ./.github/workflows/build_template_cluster.yml - with: - cluster: juliett - E2B_DOMAIN: e2b-juliett.dev - secrets: - E2B_API_KEY: ${{ secrets.E2B_JULIETT_API_KEY }} - - tango: - name: tango - if: ${{ github.event_name == 'push' || inputs.target_environment == 'all' || inputs.target_environment == 'tango' }} - uses: ./.github/workflows/build_template_cluster.yml - with: - cluster: tango - E2B_DOMAIN: e2b-tango.dev - secrets: - E2B_API_KEY: ${{ secrets.E2B_TANGO_API_KEY }} + buildTemplate: + name: Build E2B template (${{ matrix.cluster }}) + runs-on: ubuntu-22.04 + strategy: + fail-fast: false + matrix: + include: + - cluster: foxtrot + domain: '' + api_key_secret: E2B_API_KEY + - cluster: juliett + domain: e2b-juliett.dev + api_key_secret: E2B_JULIETT_API_KEY + - cluster: tango + domain: e2b-tango.dev + api_key_secret: E2B_TANGO_API_KEY + # Builds on the same cluster serialize; different clusters run concurrently. + concurrency: + group: Template-${{ github.ref }}-${{ matrix.cluster }} + cancel-in-progress: false + defaults: + run: + working-directory: ./template + 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@a37ce9120846195fa4ece8f58b268e6043cb2f26 # v3.7.0 + + - name: Parse .tool-versions + uses: wistia/parse-tool-versions@32f568a4ffd4bfa7720ebf93f171597d1ebc979a # v2.1.1 + id: tool-versions + with: + filename: '.tool-versions' + uppercase: 'true' + prefix: 'tool_version_' + + - name: Set up Python + uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 + with: + python-version: '${{ env.TOOL_VERSION_PYTHON }}' + + - name: Install and configure Poetry + uses: snok/install-poetry@a783c322200f0519c7926aa6faa857c4e23e9263 # v1.4.2 + with: + version: ${{ env.TOOL_VERSION_POETRY }} + virtualenvs-create: true + virtualenvs-in-project: true + installer-parallel: true + + - name: Install dependencies + run: poetry install + + - name: Build E2B template + run: poetry run python build_prod.py From 0766d5d34e4ab3e55605cb4585c07c286b06a91a 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:07 +0000 Subject: [PATCH 4/4] ci: spell out the foxtrot domain Co-Authored-By: mish@e2b.dev --- .github/workflows/template.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/template.yml b/.github/workflows/template.yml index 8b30e5a..721ea3a 100644 --- a/.github/workflows/template.yml +++ b/.github/workflows/template.yml @@ -77,7 +77,7 @@ jobs: matrix: include: - cluster: foxtrot - domain: '' + domain: e2b.dev api_key_secret: E2B_API_KEY - cluster: juliett domain: e2b-juliett.dev