Skip to content

Secrets

Most secrets in a Service come from your .env. Yield Config.redacted in init. Alchemy binds the value onto the Machine.

Use Fly.Secret when the value should be shared across Machines and managed in one place by Fly. Use SecretKey for Fly KMS.

import * as Config from "effect/Config";
import * as Redacted from "effect/Redacted";
export default class Api extends Fly.Service<Api>()(
"Api",
{ app: Site, main: import.meta.url, port: 3000 },
Effect.gen(function* () {
const apiKey = yield* Config.redacted("API_KEY");
return {
fetch: Effect.gen(function* () {
const token = Redacted.value(apiKey);
// ...
}),
};
}),
) {}

Config.redacted("API_KEY") reads API_KEY from the env of whoever runs the deploy and writes it onto the Machine. At runtime the same line resolves from that env var.

The value is Redacted<string>. Unwrap with Redacted.value only where you need the raw string. Do not pass env: { ... } on a Service. Yield Config.

See Secrets & Config for combinators and stages.

Fly.Secret is the App vault. Fly injects it as an environment variable on every Machine. Use it when the value is shared and managed in one place, not when only this Service reads a deploy-time .env key.

import * as Fly from "alchemy/Fly";
import * as Redacted from "effect/Redacted";
export const ApiToken = Fly.Secret("ApiToken", {
app: Site,
name: "API_TOKEN",
value: Redacted.make("…"),
});

name is the env-var Machines see. Omit it and Alchemy generates an ownership-stamped name. Updating value is in place. Changing app or name replaces.

The resolved resource exposes name, digest, createdAt, and updatedAt. Never the plaintext.

GetSecret is bound to one Secret. ListSecrets is bound to the App.

export default class Api extends Fly.Service<Api>()(
"Api",
{ app: Site, main: import.meta.url, port: 3000 },
Effect.gen(function* () {
const get = yield* Fly.GetSecret(ApiToken);
return {
fetch: Effect.gen(function* () {
const got = yield* get().pipe(Effect.orDie);
return HttpServerResponse.json({ name: got.name });
}),
};
}).pipe(Effect.provide(Fly.GetSecretHttp)),
) {}
const list = yield* Fly.ListSecrets(Site);
const { secrets } = yield* list();

From an Action, the org token can list any App in the org. From a Machine, deploy tokens are per-App. Mixing Apps on one Machine shares one FLY_API_TOKEN and is not supported.

Fly only returns plaintext from a Machine in the same App. From deploy-time Actions you get metadata (name, digest, timestamps).

Write from an Action with WriteSecret:

const Seed = Alchemy.Action(
"Seed",
Effect.gen(function* () {
const secrets = yield* Fly.WriteSecret(ApiToken);
return Effect.fn(function* () {
yield* secrets.update("API_TOKEN", Redacted.make("sk_live_rotated"));
});
}).pipe(Effect.provide(Fly.WriteSecretHttp)),
);

Inside a Service, Alchemy mints an App deploy token for the Machine. Inside an Action, the ambient FLY_API_TOKEN is used instead.

SecretKey is an App KMS key, not an env secret. Generate a random key or set raw material. Private bytes never appear in attributes.

export const Signing = Fly.SecretKey("Signing", {
app: Site,
type: "nacl_sign",
});
export const Box = Fly.SecretKey("Box", {
app: Site,
type: "nacl_secretbox",
});

type is Fly’s key type (nacl_sign, nacl_box, nacl_secretbox, hs256, es256, xaes256gcm, …). Changing app, name, or type replaces the key.

One binding per operation. The key is fixed when you bind.

const encrypt = yield* Fly.Encrypt(Box);
const decrypt = yield* Fly.Decrypt(Box);
const { ciphertext } = yield* encrypt({ plaintext: bytes });
const { plaintext } = yield* decrypt({ ciphertext });
const raw = Redacted.value(plaintext);
const sign = yield* Fly.Sign(Signing);
const verify = yield* Fly.Verify(Signing);
const { signature } = yield* sign({ plaintext: bytes });
yield* verify({ plaintext: bytes, signature });

Provide the matching *Http layer on the Service or Action. Generate, set, and delete stay on the SecretKey resource.

Decrypt wraps plaintext in Redacted. Fly crypto ops need a KMS token. Org API tokens are typed Forbidden. Encrypt and sign from a Service, not a laptop Action.

The tutorial stores an App secret and reads it from a Service. Services covers Config.redacted for values from .env. Postgres, Redis, and Tigris bind typed clients (ConnectPostgres, ReadWriteRedis, PutObject) that use Fly-owned secrets internally. See the Secret, GetSecret, ListSecrets, SecretKey, Encrypt, Decrypt, Sign, and Verify references.