fix(ci): promote versioned Daytona snapshots to Render (#1073)

Co-authored-by: Omar McAdam <omar@OpenWork-Studio.localdomain>
This commit is contained in:
Omar McAdam
2026-03-20 09:35:25 -07:00
committed by GitHub
parent ace32c69d9
commit a9bf75da0a
4 changed files with 128 additions and 8 deletions

99
.github/workflows/deploy-den.yml vendored Normal file
View File

@@ -0,0 +1,99 @@
name: Deploy Den
on:
workflow_call:
inputs:
daytona_snapshot:
description: "Daytona snapshot name to promote into Render"
required: true
type: string
workflow_dispatch:
inputs:
daytona_snapshot:
description: "Daytona snapshot name to promote into Render"
required: true
type: string
permissions:
contents: read
concurrency:
group: deploy-den-${{ inputs.daytona_snapshot }}
cancel-in-progress: false
jobs:
deploy-den:
name: Update Render Daytona Snapshot
runs-on: blacksmith-4vcpu-ubuntu-2404
env:
RENDER_API_BASE: https://api.render.com/v1
RENDER_API_KEY: ${{ secrets.RENDER_API_KEY }}
RENDER_SERVICE_ID: ${{ secrets.RENDER_DEN_CONTROL_PLANE_SERVICE_ID }}
DAYTONA_SNAPSHOT: ${{ inputs.daytona_snapshot }}
steps:
- name: Validate required configuration
shell: bash
run: |
set -euo pipefail
if [ -z "${DAYTONA_SNAPSHOT:-}" ]; then
echo "daytona_snapshot input is required" >&2
exit 1
fi
if [ -z "${RENDER_API_KEY:-}" ]; then
echo "Missing required secret: RENDER_API_KEY" >&2
exit 1
fi
if [ -z "${RENDER_SERVICE_ID:-}" ]; then
echo "Missing required secret: RENDER_DEN_CONTROL_PLANE_SERVICE_ID" >&2
exit 1
fi
- name: Update DAYTONA_SNAPSHOT on Render
shell: bash
run: |
set -euo pipefail
payload="$(python3 -c 'import json, sys; print(json.dumps({"value": sys.argv[1]}))' "$DAYTONA_SNAPSHOT")"
response_file="$(mktemp)"
status_code="$(curl -sS -o "$response_file" -w "%{http_code}" \
-X PUT "${RENDER_API_BASE}/services/${RENDER_SERVICE_ID}/env-vars/DAYTONA_SNAPSHOT" \
-H "Accept: application/json" \
-H "Authorization: Bearer ${RENDER_API_KEY}" \
-H "Content-Type: application/json" \
--data "$payload")"
if [ "$status_code" -lt 200 ] || [ "$status_code" -ge 300 ]; then
echo "Failed to update Render DAYTONA_SNAPSHOT (HTTP $status_code)" >&2
python3 -c 'from pathlib import Path; import sys; print(Path(sys.argv[1]).read_text(errors="replace"))' "$response_file"
exit 1
fi
echo "Render DAYTONA_SNAPSHOT set to ${DAYTONA_SNAPSHOT}"
- name: Trigger Render deploy
id: deploy
shell: bash
run: |
set -euo pipefail
response_file="$(mktemp)"
status_code="$(curl -sS -o "$response_file" -w "%{http_code}" \
-X POST "${RENDER_API_BASE}/services/${RENDER_SERVICE_ID}/deploys" \
-H "Accept: application/json" \
-H "Authorization: Bearer ${RENDER_API_KEY}" \
-H "Content-Type: application/json" \
--data '{}')"
if [ "$status_code" -lt 200 ] || [ "$status_code" -ge 300 ]; then
echo "Failed to trigger Render deploy (HTTP $status_code)" >&2
python3 -c 'from pathlib import Path; import sys; print(Path(sys.argv[1]).read_text(errors="replace"))' "$response_file"
exit 1
fi
deploy_id="$(python3 -c 'import json, sys; from pathlib import Path; text = Path(sys.argv[1]).read_text(errors="replace").strip(); data = json.loads(text) if text else {}; print(data.get("id", "") if isinstance(data, dict) else "")' "$response_file")"
echo "deploy_id=${deploy_id}" >> "$GITHUB_OUTPUT"
echo "Triggered Render deploy ${deploy_id:-<unknown>} for snapshot ${DAYTONA_SNAPSHOT}"

View File

@@ -7,6 +7,11 @@ on:
description: "Tag to build from (e.g., v0.11.200). Defaults to current ref."
required: false
type: string
deploy_den:
description: "Whether to promote the published snapshot into the Den Render service"
required: false
type: boolean
default: true
snapshot_name:
description: "Optional explicit Daytona snapshot name"
required: false
@@ -21,6 +26,11 @@ on:
description: "Tag to build from (e.g., v0.11.200). Defaults to release tag/current ref."
required: false
type: string
deploy_den:
description: "Whether to promote the published snapshot into the Den Render service"
required: false
type: boolean
default: true
snapshot_name:
description: "Optional explicit Daytona snapshot name"
required: false
@@ -41,6 +51,10 @@ jobs:
publish-daytona-snapshot:
name: Build and Push Daytona Snapshot
runs-on: blacksmith-4vcpu-ubuntu-2404
outputs:
release_tag: ${{ steps.resolve.outputs.release_tag }}
snapshot_name: ${{ steps.resolve.outputs.snapshot_name }}
snapshot_region: ${{ steps.resolve.outputs.snapshot_region }}
steps:
- name: Resolve release tag and snapshot name
id: resolve
@@ -49,7 +63,6 @@ jobs:
INPUT_TAG: ${{ inputs.tag }}
INPUT_SNAPSHOT_NAME: ${{ inputs.snapshot_name }}
INPUT_SNAPSHOT_REGION: ${{ inputs.snapshot_region }}
DEFAULT_SNAPSHOT_NAME: ${{ vars.DAYTONA_SNAPSHOT }}
SNAPSHOT_NAME_BASE: ${{ vars.DAYTONA_SNAPSHOT_NAME_BASE }}
DEFAULT_SNAPSHOT_REGION: ${{ vars.DAYTONA_SNAPSHOT_REGION }}
run: |
@@ -67,11 +80,9 @@ jobs:
exit 1
fi
base_name="${SNAPSHOT_NAME_BASE:-openwork-runtime}"
base_name="${SNAPSHOT_NAME_BASE:-openwork}"
if [ -n "${INPUT_SNAPSHOT_NAME:-}" ]; then
snapshot_name="${INPUT_SNAPSHOT_NAME}"
elif [ -n "${DEFAULT_SNAPSHOT_NAME:-}" ]; then
snapshot_name="${DEFAULT_SNAPSHOT_NAME}"
else
snapshot_name="${base_name}-${tag#v}"
fi
@@ -150,3 +161,12 @@ jobs:
echo "Publishing Daytona snapshot: ${DAYTONA_SNAPSHOT_NAME}"
./scripts/create-daytona-openwork-snapshot.sh "${DAYTONA_SNAPSHOT_NAME}"
deploy-den:
name: Promote Daytona Snapshot to Den Render Service
needs: [publish-daytona-snapshot]
if: ${{ inputs.deploy_den }}
uses: ./.github/workflows/deploy-den.yml
with:
daytona_snapshot: ${{ needs.publish-daytona-snapshot.outputs.snapshot_name }}
secrets: inherit

View File

@@ -67,7 +67,7 @@ Expected: worker creation `202` with a healthy cloud-backed instance once provis
- `.github/workflows/deploy-den.yml`
It updates Render env vars and triggers a deploy for the configured service ID. Daytona is intended for local/dev worker testing unless you build a separate hosted Den deployment path for it.
It updates Render env vars and triggers a deploy for the configured service ID. Release snapshot publishing now calls it after a successful Daytona snapshot push so Den picks up the new `DAYTONA_SNAPSHOT` value.
## Common failure modes

View File

@@ -130,7 +130,7 @@ Useful optional overrides:
After the snapshot is pushed, set it in `.env.daytona`:
```env
DAYTONA_SNAPSHOT=openwork-runtime
DAYTONA_SNAPSHOT=openwork-0.11.174
```
Then start Den in Daytona mode:
@@ -146,9 +146,10 @@ If you do not set `DAYTONA_SNAPSHOT`, Den falls back to `DAYTONA_SANDBOX_IMAGE`.
GitHub workflow `.github/workflows/release-daytona-snapshot.yml` builds and pushes a new Daytona snapshot whenever a GitHub release is published.
- Trigger: `release.published` (or manual `workflow_dispatch`)
- Snapshot naming: `DAYTONA_SNAPSHOT` repo variable if set, otherwise `openwork-runtime-<tag-without-v>`
- Snapshot naming: `snapshot_name` input if provided, otherwise `${DAYTONA_SNAPSHOT_NAME_BASE:-openwork}-<tag-without-v>`
- Required secret: `DAYTONA_API_KEY`
- Optional repo vars: `DAYTONA_API_URL`, `DAYTONA_TARGET`, `DAYTONA_SNAPSHOT_REGION`, `DAYTONA_SNAPSHOT_NAME_BASE`
- After the snapshot publish succeeds, the workflow calls `.github/workflows/deploy-den.yml` to set Render's `DAYTONA_SNAPSHOT` env var and trigger a Den controller deploy.
## Auth setup (Better Auth)
@@ -186,7 +187,7 @@ pnpm db:migrate:sql
## CI deployment (dev == prod)
The workflow `.github/workflows/deploy-den.yml` updates Render env vars and deploys the service on every push to `dev` when this service changes.
The workflow `.github/workflows/deploy-den.yml` updates Render env vars and triggers a deploy for the Den controller service. It can be run manually with a snapshot name, and release automation calls it after successful Daytona snapshot publishing.
Required GitHub Actions secrets: