> ## Documentation Index
> Fetch the complete documentation index at: https://docs.dedaluslabs.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# PR Preview Environments

> A full-stack preview environment per pull request. Sleeps when idle, wakes on a reviewer click.

One Dedalus Machine per open PR. The PR's branch is checked out, dependencies installed, services running. When nobody touches the preview URL for 5 minutes the VM sleeps. When the next reviewer opens the link, it wakes in under a second with the database, seed data, and log history intact.

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
npm install dedalus
```

## 1. On PR opened: provision

```typescript theme={"theme":{"light":"github-light","dark":"github-dark"}}
import Dedalus from "dedalus";
const dedalus = new Dedalus({ apiKey: process.env.DEDALUS_API_KEY! });

async function waitForPreview(machineId: string, previewId: string) {
  let preview = await dedalus.machines.previews.retrieve({
    machine_id: machineId,
    preview_id: previewId,
  });
  while (preview.status === "wake_in_progress") {
    await new Promise((resolve) =>
      setTimeout(resolve, preview.retry_after_ms ?? 500),
    );
    preview = await dedalus.machines.previews.retrieve({
      machine_id: machineId,
      preview_id: previewId,
    });
  }
  if (preview.status !== "ready" || !preview.url) {
    throw new Error(preview.error_message ?? `preview ${preview.status}`);
  }
  return preview;
}

async function provisionPreview(prNumber: number, branch: string) {
  const m = await dedalus.machines.create({
    vcpu: 2,
    memory_mib: 4096,
    storage_gib: 20,
  });

  await runAndWait(m.machine_id, ["/bin/bash", "-c", `
    set -e
    apt-get update && apt-get install -y git nodejs postgresql
    git clone --depth 1 -b ${branch} https://github.com/your-org/your-app /root/app
    cd /root/app && npm install
    pg_ctlcluster 16 main start && createdb app && npm run migrate && npm run seed
    nohup npm run dev -- --host 0.0.0.0 --port 3000 >/var/log/app.log 2>&1 &
  `]);

  const preview = await dedalus.machines.previews.create({
    machine_id: m.machine_id,
    port: 3000,
    protocol: "https",
    visibility: "org",   // or "public" for external reviewers
  });
  const ready = await waitForPreview(m.machine_id, preview.preview_id);

  return { machineId: m.machine_id, url: ready.url };
}
```

## 2. On webhook hit (or middleware): wake

The simplest pattern is a small router that proxies `https://pr-123.previews.your-app.com` to the machine's preview URL. Before forwarding, wake the machine.

```typescript theme={"theme":{"light":"github-light","dark":"github-dark"}}
import http from "node:http";

async function waitUntilRunning(machineId: string) {
  let machine = await dedalus.machines.wake({ machine_id: machineId });
  while (machine.status.phase !== "running") {
    if (machine.status.phase === "failed") {
      throw new Error(machine.status.reason);
    }
    await new Promise((resolve) => setTimeout(resolve, 500));
    machine = await dedalus.machines.retrieve({ machine_id: machineId });
  }
}

http.createServer(async (req, res) => {
  const prNumber = parseInt(req.headers["x-pr-number"] as string);
  const machineId = await lookupMachine(prNumber);   // your DB

  await waitUntilRunning(machineId);
  // ...then proxy req → the preview URL stored at provision time.
}).listen(8080);
```

`wake` is idempotent, but its response may still be `placement_pending` or `starting`. Queue the request until the machine reaches `running`, then forward it to the stored preview URL.

## 3. On PR merged or closed: destroy

```typescript theme={"theme":{"light":"github-light","dark":"github-dark"}}
async function teardownPreview(prNumber: number) {
  const machineId = await lookupMachine(prNumber);
  await dedalus.machines.delete({ machine_id: machineId });
}
```

## Why a microVM beats a container here

* **Postgres, Redis, real systemd**, anything else that doesn't survive container restart, just runs. Migrations + seed data persist across sleep/wake.
* **Sleep means zero compute cost** while the PR is dormant. A 3-week-old PR costs only the storage GiB-month — not 24/7 uptime.
* **Fast resume.** Queue the first request while the machine restores, then forward it normally.
* **Idle auto-sleep is built in** (`autosleep`, default `"5m"`). If your wake-on-traffic proxy forgets to put the VM back to sleep, the platform does it for you after five idle minutes.

## Notes

* **Set `visibility: "public"`** if external collaborators need to reach the preview without a Dedalus account. Use `"org"` to gate it on org membership, or `"private"` for the creator only.
* **Wake latency** depends on storage size. A 20 GiB machine wakes faster than a 200 GiB one.
* **Serialize cold-start traffic per machine.** Queue concurrent requests behind one readiness check instead of forwarding while the machine is still starting.
