Skip to content
GitHub Get Started
Operating System

Networking & Previews

Proxy HTTP requests into VM services with vmFetch and create time-limited, token-based preview URLs (with per-call expiration and revocation), all carried over one transport (the kernel socket table) that is loopback-only by default under a three-layer confinement model.

Guest code runs a normal Node HTTP server: it binds a loopback port inside the VM exactly like any Node process. Write the server file and spawn it.

client.ts
import { createClient } from "@rivet-dev/agentos/client";
import type { registry } from "./server";
const client = createClient<typeof registry>({ endpoint: "http://localhost:6420" });
const agent = client.vm.getOrCreate("my-agent");
// Write a simple Node HTTP server and run it inside the VM. It binds a loopback
// port (3000) exactly like any normal Node process.
await agent.writeFile(
"/home/agentos/server.js",
`const http = require("http");
http.createServer((req, res) => {
res.writeHead(200, { "Content-Type": "text/plain" });
res.end("Hello from inside the VM");
}).listen(3000, () => console.log("listening on http://127.0.0.1:3000"));`,
);
const { pid } = await agent.spawn("node", ["/home/agentos/server.js"]);
console.log("server pid:", pid);

See Full Example

With the HTTP server running in the VM (above), send requests to it with vmFetch, including custom methods, headers, and body.

client.ts
import { createClient } from "@rivet-dev/agentos/client";
import type { registry } from "./server";
const client = createClient<typeof registry>({ endpoint: "http://localhost:6420" });
const agent = client.vm.getOrCreate("my-agent");
// Simple GET from the VM service started above.
const response = await agent.vmFetch(3000, "/");
console.log("Status:", response.status);
console.log("Body:", new TextDecoder().decode(response.body));
// With a custom method, headers, and body.
const posted = await agent.vmFetch(3000, "/api/data", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ key: "value" }),
});
console.log("Status:", posted.status, posted.statusText);
console.log("Headers:", posted.headers);
console.log("Body:", new TextDecoder().decode(posted.body));

See Full Example

Preview URLs are port forwarding for VM services: a time-limited, public URL that proxies HTTP to a port inside the VM, for browser or external access (use vmFetch for server-to-server). Tokens survive sleep/wake; see Security for details.

Token lifetime is controlled per call via the second argument to createSignedPreviewUrl(port, ttlSeconds), defaulting to 1 hour when omitted or 0:

server.ts
import { agentOS, setup } from "@rivet-dev/agentos";
const vm = agentOS({ software: [] });
export const registry = setup({ use: { vm } });
registry.start();

See Full Example

Mint short-lived preview tokens so access expires automatically; the lifetime is exactly the ttlSeconds you pass (no server-side maximum is currently enforced — omit it for the 1-hour default).

client.ts
import { createClient } from "@rivet-dev/agentos/client";
import type { registry } from "./server";
const client = createClient<typeof registry>({ endpoint: "http://localhost:6420" });
// Mint a short-lived preview token so access expires automatically.
const preview = await client.vm.getOrCreate("my-agent").createSignedPreviewUrl(3000, 300); // 5 minutes
console.log("Preview path:", preview.path);
console.log("Expires at:", new Date(preview.expiresAt));

See Full Example

Network access is governed by the VM permission policy. By default the guest cannot reach the network; grant it, or allow only specific destinations:

const vm = agentOS({
permissions: {
network: {
default: "deny",
rules: [{ mode: "allow", operations: ["*"], patterns: ["api.example.com"] }],
},
},
});

See Permissions for the full configuration.

Constrain which ports guest code may bind with the listen field on the VM config. It bounds the allowed listen range and gates privileged (below 1024) ports:

const vm = agentOS({
listen: {
portMin: 3000, // lowest bindable port (validated 1–65535)
portMax: 9000, // highest bindable port (portMin <= portMax)
allowPrivileged: false, // default false; gates ports < 1024
},
});
  • portMin / portMax bound which ports the guest may bind and where automatic port allocation searches. Both are validated to be between 1 and 65535, with portMin <= portMax.
  • allowPrivileged defaults to false and gates ports below 1024. With it left false, binding a privileged port fails with EACCES.
  • A bind to a port outside [portMin, portMax], or to a privileged port without allowPrivileged: true, fails with EACCES.