Dev Environments

On-demand, ephemeral development environments for every branch and PR

View as Markdown

Modbox can power ephemeral development environments — fully functional, isolated cloud workspaces that spin up in seconds and disappear when no longer needed.

Use cases

  • PR preview environments — Each pull request gets its own live environment
  • Pair programming sessions — Share a live environment URL with a teammate
  • Workshops and tutorials — Each participant gets their own pre-configured workspace
  • Feature testing — QA engineers get isolated environments for testing branches

Example: PR preview environments

Trigger a sandbox provisioning from your CI/CD pipeline when a PR is opened:

# .github/workflows/preview.yml
name: PR Preview
on:
pull_request:
types: [opened, synchronize]
jobs:
provision-preview:
runs-on: ubuntu-latest
steps:
- name: Provision sandbox
run: |
RESPONSE=$(curl -s -X POST https://api.modbox.run/sandboxes/provision \
-H "Authorization: Bearer ${{ secrets.MODBOX_API_TOKEN }}" \
-H "Content-Type: application/json" \
-d '{
"task_id": "pr-${{ github.event.pull_request.number }}",
"image_id": "${{ vars.DEV_IMAGE_ID }}",
"ttl_seconds": 86400,
"env_vars": {
"GIT_BRANCH": "${{ github.head_ref }}",
"PR_NUMBER": "${{ github.event.pull_request.number }}"
}
}')
SANDBOX_ID=$(echo $RESPONSE | jq -r '.sandbox_id')
echo "SANDBOX_ID=$SANDBOX_ID" >> $GITHUB_ENV
- name: Wait for ready
run: |
curl -s -X POST https://api.modbox.run/sandboxes/wait \
-H "Authorization: Bearer ${{ secrets.MODBOX_API_TOKEN }}" \
-H "Content-Type: application/json" \
-d '{"task_id": "pr-${{ github.event.pull_request.number }}", "timeout": 120}'
- name: Get sandbox URL
run: |
SANDBOX=$(curl -s https://api.modbox.run/sandboxes/detail/$SANDBOX_ID \
-H "Authorization: Bearer ${{ secrets.MODBOX_API_TOKEN }}")
URL=$(echo $SANDBOX | jq -r '.sandbox_url')
echo "Preview ready: $URL" >> $GITHUB_STEP_SUMMARY
- name: Comment on PR
uses: actions/github-script@v7
with:
script: |
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: '**Preview environment ready!**\n\nYour sandbox is live and accessible. It will auto-destroy after 24 hours.'
})

Cleanup on PR close

# Destroy sandbox when the PR is closed
on:
pull_request:
types: [closed]
jobs:
destroy-preview:
runs-on: ubuntu-latest
steps:
- name: Destroy sandbox
run: |
curl -s -X POST https://api.modbox.run/sandboxes/destroy \
-H "Authorization: Bearer ${{ secrets.MODBOX_API_TOKEN }}" \
-H "Content-Type: application/json" \
-d '{"task_id": "pr-${{ github.event.pull_request.number }}"}'

Workshop environments

Provision environments for all participants at once:

import { ModboxClient } from "modbox-sdk";
const modbox = new ModboxClient({ token: process.env.MODBOX_API_TOKEN });
const participants = ["alice", "bob", "carol", "dave"];
// Provision all environments in parallel
const sandboxes = await Promise.all(
participants.map((name) =>
modbox.provisionSandbox({
taskId: `workshop-${name}`,
imageId: process.env.WORKSHOP_IMAGE_ID,
ttlSeconds: 14400, // 4 hours
envVars: { PARTICIPANT: name },
})
)
);
// Wait for all to be ready
await Promise.all(
participants.map((name) =>
modbox.waitForSandbox({ taskId: `workshop-${name}`, timeout: 60 })
)
);
// Print the access URLs
for (const name of participants) {
const sandbox = await modbox.getSandbox(`workshop-${name}`);
console.log(`${name}: ${sandbox.sandboxUrl}`);
}
// alice: https://workshop-alice.sandbox.modbox.run
// bob: https://workshop-bob.sandbox.modbox.run
// ...