Skip to content

Commit 73b190d

Browse files
committed
docs: add step-by-step getting started guide for Linux
Replace the placeholder getting started page with a comprehensive, beginner-friendly guide to building a Hyperlight host and guest on Linux. Based on the cargo-hyperlight examples. The guide covers: - Prerequisites: KVM setup, build tools, Rust, cargo-hyperlight - Creating a Cargo workspace with host and guest crates - Writing a guest with #[guest_function] and #[host_function] macros - Writing a host that creates a sandbox and calls guest functions - Building and running the complete example - Annotated sequence diagram of the host-guest interaction - Next steps with links to further resources Signed-off-by: David Justice <david@justice.dev>
1 parent af51465 commit 73b190d

1 file changed

Lines changed: 353 additions & 0 deletions

File tree

Lines changed: 353 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,353 @@
1+
---
2+
title: Getting Started with Hyperlight
3+
description: A step-by-step guide to building your first Hyperlight host and guest on Linux
4+
---
5+
6+
This guide walks you through building a complete Hyperlight application from scratch on Linux. By the end, you'll have a **guest** program running inside a hardware-isolated micro-VM and a **host** program that creates the VM and calls a function inside it.
7+
8+
## What you're building
9+
10+
Hyperlight applications have two parts:
11+
12+
- **Guest** — a small program that runs *inside* a micro-VM. It has no operating system, no file system access, and no network. It can only do what the host explicitly allows.
13+
- **Host** — a normal program that creates the micro-VM, loads the guest into it, and calls functions the guest exposes.
14+
15+
In this guide, the guest will expose a `PrintOutput` function. The host will call it with a string, and the guest will ask the host to print it.
16+
17+
## Prerequisites
18+
19+
You need a Linux machine (or WSL2) with **KVM** enabled. Most modern Linux distributions ship with KVM support in the kernel.
20+
21+
### 1. Check that KVM is available
22+
23+
```sh
24+
ls -l /dev/kvm
25+
```
26+
27+
You should see output like `crw-rw---- 1 root kvm ...`. If the file doesn't exist, your CPU may not support hardware virtualization, or KVM modules aren't loaded. See the [Ubuntu KVM installation guide](https://help.ubuntu.com/community/KVM/Installation) for help.
28+
29+
Make sure your user has permission to access `/dev/kvm`:
30+
31+
```sh
32+
groups | grep kvm
33+
```
34+
35+
If `kvm` isn't listed, add yourself to the group:
36+
37+
```sh
38+
sudo usermod -aG kvm $USER
39+
```
40+
41+
Then log out and log back in for the change to take effect.
42+
43+
### 2. Install build tools
44+
45+
On Ubuntu/Debian:
46+
47+
```sh
48+
sudo apt update && sudo apt install -y build-essential
49+
```
50+
51+
On Azure Linux:
52+
53+
```sh
54+
sudo dnf install -y build-essential
55+
```
56+
57+
### 3. Install Rust
58+
59+
If you don't have Rust installed yet:
60+
61+
```sh
62+
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
63+
```
64+
65+
Hyperlight requires Rust **1.89 or later**. After installing, verify your version:
66+
67+
```sh
68+
rustc --version
69+
```
70+
71+
### 4. Install cargo-hyperlight
72+
73+
`cargo-hyperlight` is a Cargo subcommand that handles cross-compiling guest binaries for the Hyperlight target. Install it with:
74+
75+
```sh
76+
cargo install --locked cargo-hyperlight
77+
```
78+
79+
This gives you the `cargo hyperlight build` command, which takes care of all the special compilation settings that guests need — no extra configuration required.
80+
81+
---
82+
83+
## Step 1: Create a workspace
84+
85+
We'll use a Cargo workspace to keep the host and guest in one place. Create a project directory:
86+
87+
```sh
88+
mkdir hyperlight-hello && cd hyperlight-hello
89+
```
90+
91+
Create a `Cargo.toml` at the root that declares both crates:
92+
93+
```toml
94+
[workspace]
95+
members = ["guest", "host"]
96+
resolver = "2"
97+
```
98+
99+
---
100+
101+
## Step 2: Create the guest
102+
103+
The guest is the program that will run inside the micro-VM. Generate the crate:
104+
105+
```sh
106+
cargo new --name guest guest
107+
```
108+
109+
### 2a. Set up `guest/Cargo.toml`
110+
111+
Replace the contents of `guest/Cargo.toml` with:
112+
113+
```toml
114+
[package]
115+
name = "guest"
116+
version = "0.1.0"
117+
edition = "2024"
118+
119+
[dependencies]
120+
hyperlight-common = { version = "0.11.0", default-features = false }
121+
hyperlight-guest = "0.11.0"
122+
hyperlight-guest-bin = "0.11.0"
123+
```
124+
125+
Here's what each dependency does:
126+
127+
| Crate | Purpose |
128+
|---|---|
129+
| `hyperlight-common` | Shared types used by both host and guest (function call wrappers, parameter types, etc.) |
130+
| `hyperlight-guest` | Core guest library — provides the low-level VM exit mechanism and host communication |
131+
| `hyperlight-guest-bin` | Higher-level guest utilities — panic handler, heap initialization, host function helpers, and the `#[guest_function]` / `#[host_function]` macros |
132+
133+
### 2b. Write the guest code
134+
135+
Replace `guest/src/main.rs` with:
136+
137+
```rust
138+
#![no_std]
139+
#![no_main]
140+
extern crate alloc;
141+
142+
use alloc::string::String;
143+
use alloc::vec::Vec;
144+
145+
use hyperlight_common::flatbuffer_wrappers::function_call::FunctionCall;
146+
use hyperlight_common::flatbuffer_wrappers::guest_error::ErrorCode;
147+
148+
use hyperlight_guest::bail;
149+
use hyperlight_guest::error::Result;
150+
use hyperlight_guest_bin::{guest_function, host_function};
151+
152+
// Declare a host function that the guest can call.
153+
// The host must register a function with this exact name ("HostPrint")
154+
// before the guest tries to use it.
155+
#[host_function("HostPrint")]
156+
fn host_print(message: String) -> Result<i32>;
157+
158+
// Define a guest function that the host can call.
159+
// When the host calls "PrintOutput", this function runs inside the VM.
160+
#[guest_function("PrintOutput")]
161+
fn print_output(message: String) -> Result<i32> {
162+
// Call the host function to print the message.
163+
// The guest can't print directly — it has no OS or stdout.
164+
// Instead, it asks the host to do it.
165+
let result = host_print(message)?;
166+
Ok(result)
167+
}
168+
169+
/// Called once when the guest starts up.
170+
/// Use this for any initialization your guest needs.
171+
#[no_mangle]
172+
pub extern "C" fn hyperlight_main() {
173+
// Nothing to initialize for this simple example.
174+
}
175+
176+
/// The dispatch function routes incoming function calls from the host.
177+
/// If the host calls a function name the guest doesn't recognize, this
178+
/// returns an error.
179+
#[no_mangle]
180+
pub fn guest_dispatch_function(function_call: FunctionCall) -> Result<Vec<u8>> {
181+
let function_name = function_call.function_name;
182+
bail!(ErrorCode::GuestFunctionNotFound => "{function_name}");
183+
}
184+
```
185+
186+
Let's break down what's happening:
187+
188+
- **`#![no_std]` and `#![no_main]`** — The guest runs inside a bare-metal micro-VM with no operating system. There's no `std` library and no normal `main()` entry point.
189+
- **`extern crate alloc`** — Even without `std`, Hyperlight provides a heap allocator so you can use `String`, `Vec`, and other heap types.
190+
- **`#[host_function("HostPrint")]`** — This macro declares that a function called `HostPrint` exists on the host side. When the guest calls `host_print(...)`, it triggers a VM exit, the host executes the function, and the result comes back.
191+
- **`#[guest_function("PrintOutput")]`** — This macro registers a function that the host can call into the guest. The host will call `"PrintOutput"` by name.
192+
- **`hyperlight_main()`** — This is the guest's entry point, called once when the VM starts. You can register functions or perform setup here.
193+
- **`guest_dispatch_function()`** — This handles any function calls that aren't covered by the `#[guest_function]` macro. In this example, it just returns an error for unknown functions.
194+
195+
### 2c. Build the guest
196+
197+
```sh
198+
cargo hyperlight build --release -p guest
199+
```
200+
201+
This cross-compiles the guest for the `x86_64-hyperlight-none` target. The resulting binary will be at:
202+
203+
```
204+
target/x86_64-hyperlight-none/release/guest
205+
```
206+
207+
This binary is a tiny, self-contained program — no OS, no kernel, just your code and the Hyperlight guest runtime.
208+
209+
---
210+
211+
## Step 3: Create the host
212+
213+
The host is a normal Linux program. It creates a micro-VM, loads the guest binary into it, and calls guest functions. Generate the crate:
214+
215+
```sh
216+
cargo new --name host host
217+
```
218+
219+
### 3a. Set up `host/Cargo.toml`
220+
221+
Replace the contents of `host/Cargo.toml` with:
222+
223+
```toml
224+
[package]
225+
name = "host"
226+
version = "0.1.0"
227+
edition = "2024"
228+
229+
[dependencies]
230+
hyperlight-host = "0.11.0"
231+
anyhow = "1.0"
232+
```
233+
234+
| Crate | Purpose |
235+
|---|---|
236+
| `hyperlight-host` | The Hyperlight host library — creates and manages micro-VMs, calls guest functions |
237+
| `anyhow` | Convenient error handling (optional, but makes the example cleaner) |
238+
239+
### 3b. Write the host code
240+
241+
Replace `host/src/main.rs` with:
242+
243+
```rust
244+
use anyhow::Context as _;
245+
use hyperlight_host::GuestBinary;
246+
use hyperlight_host::sandbox::SandboxConfiguration;
247+
248+
fn main() -> anyhow::Result<()> {
249+
// Path to the guest binary we built in Step 2.
250+
let guest_path = std::env::args()
251+
.nth(1)
252+
.context("Usage: host <path-to-guest-binary>")?;
253+
254+
// Tell Hyperlight where to find the guest binary.
255+
let guest = GuestBinary::FilePath(guest_path);
256+
257+
// Configure the micro-VM's memory.
258+
// The guest runs in a fixed-size memory region — there's no virtual memory
259+
// or swap. 1 MiB each for heap and stack is plenty for this example.
260+
let mut config = SandboxConfiguration::default();
261+
config.set_heap_size(1024 * 1024); // 1 MiB heap
262+
config.set_stack_size(1024 * 1024); // 1 MiB stack
263+
264+
// Create the sandbox (micro-VM).
265+
// `new()` sets up the VM and loads the guest binary.
266+
// `evolve()` initializes it — this calls `hyperlight_main()` in the guest,
267+
// which registers the guest's functions and makes them callable.
268+
let mut sandbox = hyperlight_host::UninitializedSandbox::new(
269+
guest,
270+
Some(config),
271+
)?.evolve()?;
272+
273+
// Call the guest's "PrintOutput" function.
274+
// This triggers the VM to start executing guest code.
275+
// Inside the guest, `print_output()` will call the host's "HostPrint"
276+
// function, which prints the message to stdout.
277+
let bytes_printed: i32 = sandbox.call(
278+
"PrintOutput",
279+
"Hello from Hyperlight! I am running inside a micro-VM.\n".to_string(),
280+
)?;
281+
282+
println!("Guest function returned: {bytes_printed} bytes printed");
283+
284+
Ok(())
285+
}
286+
```
287+
288+
Here's the flow, step by step:
289+
290+
1. **Load the guest binary**`GuestBinary::FilePath(...)` tells Hyperlight to read the compiled guest from disk.
291+
2. **Configure memory**`SandboxConfiguration` controls how much heap and stack memory the guest gets. The guest can't grow beyond this.
292+
3. **Create the sandbox**`UninitializedSandbox::new(...)` creates the micro-VM using KVM. This sets up the hardware isolation — the guest gets its own memory region that the host CPU enforces.
293+
4. **Evolve**`.evolve()` transitions the sandbox from "uninitialized" to "ready." During this step, Hyperlight runs `hyperlight_main()` in the guest, which registers guest functions.
294+
5. **Call a guest function**`sandbox.call("PrintOutput", ...)` tells the VM to execute the guest's `PrintOutput` function. The guest runs, calls back to the host to print, and returns the result.
295+
296+
### 3c. Build the host
297+
298+
```sh
299+
cargo build --release -p host
300+
```
301+
302+
---
303+
304+
## Step 4: Run it
305+
306+
```sh
307+
./target/release/host ./target/x86_64-hyperlight-none/release/guest
308+
```
309+
310+
You should see:
311+
312+
```text
313+
Hello from Hyperlight! I am running inside a micro-VM.
314+
Guest function returned: 57 bytes printed
315+
```
316+
317+
That message was printed by the **host**, but the request to print it came from the **guest** running inside a hardware-isolated micro-VM. The guest called `host_print(...)`, which triggered a VM exit, the host executed the print, and the result was sent back to the guest — all in under a millisecond.
318+
319+
---
320+
321+
## What just happened?
322+
323+
Here's the full sequence of events:
324+
325+
```
326+
Host Guest (inside micro-VM)
327+
───────────────────────────── ─────────────────────────────
328+
1. Creates micro-VM via KVM
329+
2. Loads guest binary into VM
330+
3. Calls evolve()
331+
4. hyperlight_main() runs
332+
(registers PrintOutput)
333+
5. Calls "PrintOutput"
334+
6. print_output() executes
335+
7. Calls host_print() → VM exit
336+
8. Host prints the message
337+
9. Returns result to guest
338+
10. Guest receives result
339+
11. Returns to host
340+
12. Receives return value
341+
```
342+
343+
The key insight is that the guest **never has direct access to the host's memory, file system, or network**. Every interaction goes through explicitly registered functions — the host controls exactly what the guest can do.
344+
345+
---
346+
347+
## Next steps
348+
349+
- **Add more guest functions** — Use `#[guest_function("Name")]` to expose additional functions, and call them from the host with `sandbox.call("Name", ...)`.
350+
- **Register host functions** — Before calling `.evolve()`, use `sandbox.register("FunctionName", |args| { ... })` to make host-side functions available to the guest.
351+
- **Explore Hyperlight Wasm** — If you want to run WebAssembly inside a micro-VM, check out [hyperlight-wasm](https://github.com/hyperlight-dev/hyperlight-wasm).
352+
- **Browse more examples** — The [cargo-hyperlight examples](https://github.com/hyperlight-dev/cargo-hyperlight/tree/main/examples) show additional patterns including C FFI in guests.
353+
- **Join the community** — Chat with the team on the [CNCF Slack #hyperlight channel](https://cloud-native.slack.com/archives/hyperlight).

0 commit comments

Comments
 (0)