Dev Environments

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

View as MarkdownOpen in Claude

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:

1# .github/workflows/preview.yml
2name: PR Preview
3
4on:
5 pull_request:
6 types: [opened, synchronize]
7
8jobs:
9 provision-preview:
10 runs-on: ubuntu-latest
11 steps:
12 - name: Provision sandbox
13 run: |
14 RESPONSE=$(curl -s -X POST https://api.modbox.run/sandboxes/provision \
15 -H "Authorization: Bearer ${{ secrets.MODBOX_API_TOKEN }}" \
16 -H "Content-Type: application/json" \
17 -d '{
18 "task_id": "pr-${{ github.event.pull_request.number }}",
19 "image_id": "${{ vars.DEV_IMAGE_ID }}",
20 "ttl_seconds": 86400,
21 "env_vars": {
22 "GIT_BRANCH": "${{ github.head_ref }}",
23 "PR_NUMBER": "${{ github.event.pull_request.number }}"
24 }
25 }')
26 SANDBOX_ID=$(echo $RESPONSE | jq -r '.sandbox_id')
27 echo "SANDBOX_ID=$SANDBOX_ID" >> $GITHUB_ENV
28
29 - name: Wait for ready
30 run: |
31 curl -s -X POST https://api.modbox.run/sandboxes/wait \
32 -H "Authorization: Bearer ${{ secrets.MODBOX_API_TOKEN }}" \
33 -H "Content-Type: application/json" \
34 -d '{"task_id": "pr-${{ github.event.pull_request.number }}", "timeout": 120}'
35
36 - name: Get sandbox URL
37 run: |
38 SANDBOX=$(curl -s https://api.modbox.run/sandboxes/detail/$SANDBOX_ID \
39 -H "Authorization: Bearer ${{ secrets.MODBOX_API_TOKEN }}")
40 URL=$(echo $SANDBOX | jq -r '.sandbox_url')
41 echo "Preview ready: $URL" >> $GITHUB_STEP_SUMMARY
42
43 - name: Comment on PR
44 uses: actions/github-script@v7
45 with:
46 script: |
47 github.rest.issues.createComment({
48 issue_number: context.issue.number,
49 owner: context.repo.owner,
50 repo: context.repo.repo,
51 body: '**Preview environment ready!**\n\nYour sandbox is live and accessible. It will auto-destroy after 24 hours.'
52 })

Cleanup on PR close

1# Destroy sandbox when the PR is closed
2on:
3 pull_request:
4 types: [closed]
5
6jobs:
7 destroy-preview:
8 runs-on: ubuntu-latest
9 steps:
10 - name: Destroy sandbox
11 run: |
12 curl -s -X POST https://api.modbox.run/sandboxes/destroy \
13 -H "Authorization: Bearer ${{ secrets.MODBOX_API_TOKEN }}" \
14 -H "Content-Type: application/json" \
15 -d '{"task_id": "pr-${{ github.event.pull_request.number }}"}'

Workshop environments

Provision environments for all participants at once:

1import { ModboxClient } from "modbox-sdk";
2
3const modbox = new ModboxClient({ token: process.env.MODBOX_API_TOKEN });
4
5const participants = ["alice", "bob", "carol", "dave"];
6
7// Provision all environments in parallel
8const sandboxes = await Promise.all(
9 participants.map((name) =>
10 modbox.provisionSandbox({
11 taskId: `workshop-${name}`,
12 imageId: process.env.WORKSHOP_IMAGE_ID,
13 ttlSeconds: 14400, // 4 hours
14 envVars: { PARTICIPANT: name },
15 })
16 )
17);
18
19// Wait for all to be ready
20await Promise.all(
21 participants.map((name) =>
22 modbox.waitForSandbox({ taskId: `workshop-${name}`, timeout: 60 })
23 )
24);
25
26// Print the access URLs
27for (const name of participants) {
28 const sandbox = await modbox.getSandbox(`workshop-${name}`);
29 console.log(`${name}: ${sandbox.sandboxUrl}`);
30}
31// alice: https://workshop-alice.sandbox.modbox.run
32// bob: https://workshop-bob.sandbox.modbox.run
33// ...