1---
2title: Sprites
3description: An Effect program running in a Fly.io Sprite. It hibernates when idle.
4---
5
6A [`Sprite`](/providers/fly/sprite) is an Effect program running in
7a Fly.io Sprite. Sprites are org-scoped Linux sandboxes. They
8hibernate when idle and wake on demand.
9
10A [`Service`](/fly/compute/services) is an always-on Machine on an
11[App](/fly/compute/apps). A Sprite has no parent App. Alchemy
12bundles `main` and writes it onto the Sprite. There is no Docker
13image.
14
15Auth is `FLY_API_TOKEN`. Alchemy mints a Sprites bearer from that
16token. There is no extra token to store.
17
18## Declare a Sprite
19
20A Sprite is a class. Props describe the sandbox. The Effect is the
21program that runs on it.
22
23```typescript
24// src/box.ts
25import * as Fly from "alchemy/Fly";
26import * as Effect from "effect/Effect";
27
28export default class Box extends Fly.Sprite<Box>()(
29 "Box",
30 { main: import.meta.url },
31 Effect.gen(function* () {
32 return {};
33 }),
34) {}
35```
36
37`main: import.meta.url` is the bundle entrypoint. Alchemy bundles
38this file with Rolldown, writes it onto the Sprite, and runs it as
39a Sprite service on `port` (default `3000`).
40
41## Serve HTTP with `fetch`
42
43Return `fetch` from the constructor Effect to boot an HTTP server. The
44Sprite URL proxies to `port`.
45
46```diff lang="typescript"
47import * as Fly from "alchemy/Fly";
48import * as Effect from "effect/Effect";
49+import * as HttpServerResponse from "effect/http/HttpServerResponse";
50
51export default class Box extends Fly.Sprite<Box>()(
52 "Box",
53 { main: import.meta.url },
54 Effect.gen(function* () {
55- return {};
56+ return {
57+ fetch: Effect.succeed(HttpServerResponse.text("hello")),
58+ };
59 }),
60) {}
61```
62
63## The public URL
64
65Yield the Sprite in the Stack. `box.url` is
66`https://{name}-….sprites.app`.
67
68```typescript
69export default Alchemy.Stack(
70 "MyApp",
71 { providers: Fly.providers(), state: Alchemy.localState() },
72 Effect.gen(function* () {
73 const box = yield* Box;
74 return { url: box.url };
75 }),
76);
77```
78
79## URL auth
80
81`urlAuth` is `public` or `sprite`. Default is `public` so `url`
82answers without extra auth.
83
84```diff lang="typescript"
85export default class Box extends Fly.Sprite<Box>()(
86 "Box",
87- { main: import.meta.url },
88+ { main: import.meta.url, urlAuth: "sprite" },
89```
90
91:::note[Default is public]
92Fly's API default is `sprite`. Alchemy defaults to `public` so a
93`fetch` handler is reachable.
94:::
95
96## Config
97
98Yield `Config` in the constructor. Alchemy reads the value from the env of
99whoever deploys and writes it onto the Sprite. Do not pass
100`env: { ... }` on a Sprite.
101
102Yield `FileSystem.FileSystem` in the constructor, never inside `fetch`.
103
104```typescript
105import * as Config from "effect/Config";
106import * as FileSystem from "effect/FileSystem";
107import * as Redacted from "effect/Redacted";
108
109export default class Box extends Fly.Sprite<Box>()(
110 "Box",
111 { main: import.meta.url },
112 Effect.gen(function* () {
113 const apiKey = yield* Config.Redacted("API_KEY");
114 const fs = yield* FileSystem.FileSystem;
115 yield* fs.makeDirectory("/tmp", { recursive: true });
116
117 return {
118 fetch: Effect.gen(function* () {
119 const token = Redacted.value(apiKey);
120 return HttpServerResponse.text("ok");
121 }),
122 };
123 }),
124) {}
125```
126
127## Service vs Sprite
128
129Reach for a [Service](/fly/compute/services) when the process should
130stay up on a Fly Machine behind `{app}.fly.dev`. Reach for a Sprite
131when the sandbox can sleep. A Service needs Docker on the machine
132that deploys. A Sprite does not.
133
134## Exec
135
136[`Exec`](/providers/fly/exec) runs a command on the Sprite. Provide
137[`ExecHttp`](/providers/fly/exechttp).
138
139```typescript
140const exec = yield* Fly.Exec(Box);
141const result = yield* exec({ cmd: ["ls", "-la"] });
142```
143
144## Checkpoint
145
146[`Checkpoint`](/providers/fly/checkpoint) snapshots and restores the
147Sprite disk. Provide
148[`CheckpointHttp`](/providers/fly/checkpointhttp).
149
150```typescript
151const checkpoint = yield* Fly.Checkpoint(Box);
152yield* checkpoint.create({ comment: "before" });
153yield* checkpoint.restore("v1");
154```
155
156:::caution[Restore is destructive]
157The disk rewinds. Later writes are gone.
158:::
159
160## A stable name
161
162Omit `name` and Alchemy generates one from the stack, stage, and
163logical ID.
164
165```typescript
166export default class Box extends Fly.Sprite<Box>()(
167 "Box",
168 { main: import.meta.url, name: "box" },
169 Effect.gen(function* () {
170 return {
171 fetch: Effect.succeed(HttpServerResponse.text("hello")),
172 };
173 }),
174) {}
175```
176
177:::caution[Changing `name` replaces the Sprite]
178Fly cannot rename a Sprite. Alchemy creates the new name, then
179deletes the old one.
180:::
181
182## Where next
183
184[Services](/fly/compute/services) are always-on Machines on an App.
185[Secrets](/fly/data/secrets) covers `Config.Redacted`.
186The [`Sprite` reference](/providers/fly/sprite) lists every prop.