Compile for fast cold starts
Goal: deploy a compiled app (Rust native) on the managed runtime and understand why you’d reach for it — a compiled handler produces a tiny artifact and a fast cold start, which is exactly what latency-sensitive, scale-to-zero apps want.
Why compile?
When an app scales to zero, the next call is a cold start — something has to be on disk and started before your code runs. The size and shape of that “something” dominates the wait:
- Compiled (this recipe). The platform compiles your source to a single static Linux binary and bundles it into the managed runtime image. There’s no image layer for you to pull (the runtime image is already cached on the node) and nothing to warm up — the agent just execs the binary. A Rust/Go binary is a few MB and starts in milliseconds.
- Image mode (see Build an image from your code). The pod runs your container, so the node has to pull your image layers on a cold node, and an interpreted server (Python/Node) then has to boot. That’s a larger artifact and a slower first call.
So for the same handler, the compile path gives you the smallest artifact and the shortest cold-start budget. See API Reference → Cold starts vs warm pods for how that budget is spent. This recipe applies the compile branch of Code & Runtimes.
Shape:
src/lib.rs ──► app create ──► draft save ──► draft compile ──► draft publish ──► invoke
(#[ignite::main]) (--runtime rust) (--code ./src (rust-native (version 1) (cold pod
--rust-target compile pod execs a
native) builds the binary) tiny binary)Prerequisites
- The
dodilCLI installed and authenticated —dodil ignite config set token <token>anddodil ignite config set api_endpoint rpc.dev.dodil.io:443(orexport DODIL_TOKEN=<token>). See CLI Basics. - Your org name for the
org:nameID form. We useacme-corp.
1. Write the handler
The Rust runtime gives you an SDK that owns the HTTP server, request decoding, and context wiring — you write just the handler and annotate it with #[ignite::main]. The contract is:
#[ignite::main]
async fn handler(req: Request, ctx: Context) -> Result<impl IntoResponse>req— typed access to the JSON body:req.field::<T>("key")?,req.body::<T>()?, orreq.raw().ctx— execution metadata:ctx.execution_id(),ctx.function_id(),ctx.env().- Return value — anything that’s
IntoResponse. Aserde_json::Value(e.g.json!({...})) is JSON-encoded;Response::text(...)sends UTF-8.
Create src/lib.rs:
use ignite::prelude::*;
use serde_json::json;
#[ignite::main]
async fn handler(req: Request, ctx: Context) -> Result<impl IntoResponse> {
let message: String = req
.field("message")
.unwrap_or_else(|_| "Hello from Rust!".to_string());
Ok(json!({
"message": message,
"execution_id": ctx.execution_id(),
"function_id": ctx.function_id(),
}))
}And a minimal Cargo.toml next to it (in ./, with lib.rs under ./src/):
[package]
name = "tiny"
version = "0.1.0"
edition = "2021"
[dependencies]
ignite = "*"
serde_json = "1.0"
tokio = { version = "1.0", features = ["rt-multi-thread", "macros"] }The compile pod injects the
ignite/ignite-macroscrates from the platform SDK at build time — you don’t add a path or git dependency. Useeprintln!()(notprintln!()) for logs: on native, stdout is reserved for the runtime framing.
2. Create the app
dodil ignite app create tiny --runtime rust --description "Tiny compiled Rust handler"The app id is now acme-corp:tiny.
3. Save the source
draft save uploads code into the mutable draft (version 0). For the compile path the source flags are: --code (a single file → source, or a directory/archive → archive context), --runtime, and — required for Rust — --rust-target (native or wasm). Point --code at the directory so Cargo.toml and src/ are packaged:
dodil ignite draft save acme-corp:tiny \
--code ./ \
--runtime rust \
--rust-target native--rust-target native→ a native Linux binary (x86_64-unknown-linux-gnu) — the smallest, fastest-starting artifact.--rust-target wasm→ awasm32-wasip2Component Model module run under Wasmtime.- Pin a compiler version with
--toolchain <ver>if you need to (defaults to the platform’s current Rust toolchain).
The source flags are mutually exclusive:
--code(compile) is not combined with--dockerfile-path,--git-url, or--image-ref. Those select the image paths instead — see Code & Runtimes.
4. Compile
Rust drafts need an explicit compile step. This dispatches a rust-native compile pod that builds the binary and pushes it to the platform registry as an OCI artifact:
dodil ignite draft compile acme-corp:tinyRust compiles are memory-heavy and can take a few minutes — image and compile builds both run through Builds. Follow progress with the compile logs:
dodil ignite draft compile-logs acme-corp:tinyOther runtimes (Python/Go/Deno) are compiled as part of the draft/publish flow, so
draft compileis specifically the Rust step.
5. Publish
dodil ignite draft publish acme-corp:tinyAfter the first publish the app gets a direct FQDN — `tiny-acme-corp.ignite.dodil.cloud` (port 80). Verify:
dodil ignite app get acme-corp:tiny -o json | jq '{active_version, public_urls}'6. Invoke
dodil ignite invoke acme-corp:tiny -p '{"message": "hi there"}'{
"message": "hi there",
"execution_id": "01J...",
"function_id": "acme-corp/tiny"
}7. See the cold start pay off
Invoke once after the app has been idle, then again immediately, and compare:
dodil ignite execution get <exec-id> -o json | jq '{status, metrics}'metrics.cold_start_ms on the first call is the wake-from-zero wait — for a compiled binary on a node that already has the runtime image cached, that’s a short hop (exec the binary), not an image pull plus interpreter boot. billed_duration_ms is what you’re billed on (execution plus init if cold).
To make even the first call instant, keep pods warm: set ScalingConfig.reserved_capacity > 0 so a pod stays running and is exempt from idle teardown. The full cold-start budget, per-request deadlines, and the warm-pod lever are in API Reference → Cold starts vs warm pods.
Iterating
Edit src/lib.rs, draft save again (overwrites the draft slot), draft compile, then draft publish to cut a new immutable version. Roll back any time with dodil ignite version rollback acme-corp:tiny <version>.
Common gotchas
| Symptom | Cause | Fix |
|---|---|---|
draft publish says the draft isn’t compiled | Rust drafts need an explicit compile step | Run dodil ignite draft compile acme-corp:tiny before publishing |
--rust-target error on save | --rust-target is required for Rust | Pass --rust-target native (or wasm) on draft save |
| Compile times out | Rust compiles are memory- and time-heavy (default 2400s budget) | Trim dependencies; watch dodil ignite draft compile-logs for where it stalls |
| Garbled output / framing errors | Used println!() in the handler | Log with eprintln!() — stdout is reserved for the runtime framing |
See also
- Code & Runtimes — the compile vs image branches and supported runtimes
- Build an image from your code — when you need a Dockerfile or system packages instead
- API Reference → Cold starts vs warm pods — the cold-start budget and the warm-pod lever
- Deploy a Python app — the interpreted managed-runtime flow