Skip to Content
We are live but in Staging 🎉
ComputeRecipesCompile for fast cold starts

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 dodil CLI installed and authenticated — dodil ignite config set token <token> and dodil ignite config set api_endpoint rpc.dev.dodil.io:443 (or export DODIL_TOKEN=<token>). See CLI Basics.
  • Your org name for the org:name ID form. We use acme-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>()?, or req.raw().
  • ctx — execution metadata: ctx.execution_id(), ctx.function_id(), ctx.env().
  • Return value — anything that’s IntoResponse. A serde_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-macros crates from the platform SDK at build time — you don’t add a path or git dependency. Use eprintln!() (not println!()) 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 → a wasm32-wasip2 Component 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:tiny

Rust 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:tiny

Other runtimes (Python/Go/Deno) are compiled as part of the draft/publish flow, so draft compile is specifically the Rust step.

5. Publish

dodil ignite draft publish acme-corp:tiny

After 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

SymptomCauseFix
draft publish says the draft isn’t compiledRust drafts need an explicit compile stepRun dodil ignite draft compile acme-corp:tiny before publishing
--rust-target error on save--rust-target is required for RustPass --rust-target native (or wasm) on draft save
Compile times outRust compiles are memory- and time-heavy (default 2400s budget)Trim dependencies; watch dodil ignite draft compile-logs for where it stalls
Garbled output / framing errorsUsed println!() in the handlerLog with eprintln!() — stdout is reserved for the runtime framing

See also