Skip to content

Sprites

A Sprite is an Effect program running in a Fly.io Sprite. Sprites are org-scoped Linux sandboxes. They hibernate when idle and wake on demand.

A Service is an always-on Machine on an App. A Sprite has no parent App. Alchemy bundles main and writes it onto the Sprite. There is no Docker image.

Auth is FLY_API_TOKEN. Alchemy mints a Sprites bearer from that token. There is no extra token to store.

A Sprite is a class. Props describe the sandbox. The Effect is the program that runs on it.

src/box.ts
import * as Fly from "alchemy/Fly";
import * as Effect from "effect/Effect";
export default class Box extends Fly.Sprite<Box>()(
"Box",
{ main: import.meta.url },
Effect.gen(function* () {
return {};
}),
) {}

main: import.meta.url is the bundle entrypoint. Alchemy bundles this file with Rolldown, writes it onto the Sprite, and runs it as a Sprite service on port (default 3000).

Return fetch from the init Effect to boot an HTTP server. The Sprite URL proxies to port.

import * as Fly from "alchemy/Fly";
import * as Effect from "effect/Effect";
import * as HttpServerResponse from "effect/unstable/http/HttpServerResponse";
export default class Box extends Fly.Sprite<Box>()(
"Box",
{ main: import.meta.url },
Effect.gen(function* () {
return {};
return {
fetch: Effect.succeed(HttpServerResponse.text("hello")),
};
}),
) {}

Yield the Sprite in the Stack. box.url is https://{name}-….sprites.app.

export default Alchemy.Stack(
"MyApp",
{ providers: Fly.providers(), state: Alchemy.localState() },
Effect.gen(function* () {
const box = yield* Box;
return { url: box.url };
}),
);

urlAuth is public or sprite. Default is public so url answers without extra auth.

export default class Box extends Fly.Sprite<Box>()(
"Box",
{ main: import.meta.url },
{ main: import.meta.url, urlAuth: "sprite" },

Yield Config in init. Alchemy reads the value from the env of whoever deploys and writes it onto the Sprite. Do not pass env: { ... } on a Sprite.

Yield FileSystem.FileSystem in init, never inside fetch.

import * as Config from "effect/Config";
import * as FileSystem from "effect/FileSystem";
import * as Redacted from "effect/Redacted";
export default class Box extends Fly.Sprite<Box>()(
"Box",
{ main: import.meta.url },
Effect.gen(function* () {
const apiKey = yield* Config.redacted("API_KEY");
const fs = yield* FileSystem.FileSystem;
yield* fs.makeDirectory("/tmp", { recursive: true });
return {
fetch: Effect.gen(function* () {
const token = Redacted.value(apiKey);
return HttpServerResponse.text("ok");
}),
};
}),
) {}

Reach for a Service when the process should stay up on a Fly Machine behind {app}.fly.dev. Reach for a Sprite when the sandbox can sleep. A Service needs Docker on the machine that deploys. A Sprite does not.

Exec runs a command on the Sprite. Provide ExecHttp.

const exec = yield* Fly.Exec(Box);
const result = yield* exec({ cmd: ["ls", "-la"] });

Checkpoint snapshots and restores the Sprite disk. Provide CheckpointHttp.

const checkpoint = yield* Fly.Checkpoint(Box);
yield* checkpoint.create({ comment: "before" });
yield* checkpoint.restore("v1");

Omit name and Alchemy generates one from the stack, stage, and logical ID.

export default class Box extends Fly.Sprite<Box>()(
"Box",
{ main: import.meta.url, name: "box" },
Effect.gen(function* () {
return {
fetch: Effect.succeed(HttpServerResponse.text("hello")),
};
}),
) {}

Services are always-on Machines on an App. Secrets covers Config.redacted. The Sprite reference lists every prop.