Skip to content

Bundling

Before a function can be deployed to Thunder, its source code and dependencies must be packaged into a single deployable artifact. The thunder bundle command handles this process, producing either an ESZIP package or a V8 snapshot bundle.

Thunder supports two bundle formats:

ESZIP is the default format. It packages JavaScript/TypeScript source modules and their dependency graph into a single binary file. At deploy time, the runtime deserializes the ESZIP, evaluates the modules, and starts serving requests.

Terminal window
thunder bundle --entrypoint ./src/index.ts --output ./bundle.eszip

ESZIP bundles are portable across Thunder versions and V8 engine updates. They are the safer choice when you need maximum compatibility.

The snapshot format creates a V8 heap snapshot — a serialized image of the V8 isolate after the module has been evaluated. On cold start, the runtime restores the snapshot directly into memory instead of parsing and evaluating JavaScript, resulting in faster startup times.

Terminal window
thunder bundle --entrypoint ./src/index.ts --output ./bundle.snapshot --format snapshot

Every snapshot bundle includes an embedded ESZIP fallback. If the V8 version that created the snapshot does not match the V8 version running in the target runtime, Thunder automatically falls back to ESZIP-based startup. This ensures availability even during rolling upgrades where runtime versions may differ.

When a V8 version mismatch is detected, the runtime logs a warning. Regenerate snapshot bundles with the current runtime to restore fast cold starts.

PropertyESZIPSnapshot
Cold-start speedStandardFaster (V8 heap restore)
PortabilityHigh (V8-version independent)Requires matching V8 version
FallbackEmbedded ESZIP fallback
File sizeSmallerLarger (includes heap image + ESZIP)
Default formatYesNo

Terminal window
thunder bundle --entrypoint <FILE> --output <FILE> [--format <eszip|snapshot>]
FlagShortDescription
--entrypoint-eEntrypoint JS/TS file (required).
--output-oOutput file path (required).
--formateszip (default) or snapshot.
--manifestPath to a function manifest (v2) to embed.

For TypeScript entrypoints (.ts, .mts, .cts, .tsx), the bundle command runs deno check if the Deno CLI is available in PATH. If Deno is not installed, it falls back to syntax and module-graph validation only. Full type checking is recommended for production builds.


A function manifest defines per-function configuration: environment variables, network allowlists, resource limits, and routing rules. You can embed the manifest directly into the bundle artifact so that it travels with the code.

Terminal window
thunder bundle \
--entrypoint ./src/index.ts \
--manifest ./manifest.json \
--output ./bundle.eszip

When a bundle with an embedded manifest is deployed via the admin API, the runtime extracts and applies the manifest automatically. There is no need to send the manifest separately via the x-function-manifest-b64 header (though you can still override it that way if needed).

When the manifest uses flavor: "routed-app" and routes is empty, the bundle command auto-scans a functions/ directory relative to the manifest and populates routes based on the file structure. If a sibling public/ directory exists, static asset routes are generated automatically.


Thunder supports Ed25519 bundle signing for integrity verification. When enabled on the runtime (--require-bundle-signature), every deploy and update request must include a valid signature. Unsigned or tampered bundles are rejected.

  1. Generate a key pair:

    Terminal window
    openssl genpkey -algorithm ED25519 -out bundle-signing-private.pem
    openssl pkey -in bundle-signing-private.pem -pubout -out bundle-signing-public.pem
  2. Sign the bundle:

    Terminal window
    openssl pkeyutl -sign \
    -inkey bundle-signing-private.pem \
    -rawin \
    -in ./bundle.eszip \
    -out ./bundle.eszip.sig
  3. Deploy with signature:

    Terminal window
    SIG_B64="$(base64 < ./bundle.eszip.sig | tr -d '\n')"
    curl -X POST http://127.0.0.1:9000/_internal/functions \
    -H "X-API-Key: admin-secret" \
    -H "x-function-name: my-function" \
    -H "x-bundle-signature-ed25519: ${SIG_B64}" \
    --data-binary @./bundle.eszip
FlagEnv VariableDescription
--require-bundle-signatureEDGE_RUNTIME_REQUIRE_BUNDLE_SIGNATUREReject unsigned bundles.
--bundle-public-key-pathEDGE_RUNTIME_BUNDLE_PUBLIC_KEY_PATHPath to Ed25519 public key (PEM, base64, or hex).
  • Keep the private key in your CI/CD pipeline or HSM. Never store it on the runtime host.
  • The public key can reside on the runtime host at a read-only path (e.g., /etc/thunder/keys/).
  • Rotate keys periodically. After switching the public key on the runtime, new deploys must use the matching private key. Already-running functions are not affected until they are redeployed.

Terminal window
thunder bundle \
--entrypoint ./src/index.ts \
--output ./dist/my-function.eszip
Terminal window
thunder bundle \
--entrypoint ./src/index.ts \
--output ./dist/my-function.snapshot \
--format snapshot
Terminal window
thunder bundle \
--entrypoint ./src/index.ts \
--manifest ./manifest.json \
--output ./dist/my-function.eszip
Terminal window
# 1. Bundle with manifest
thunder bundle \
--entrypoint ./src/index.ts \
--manifest ./manifest.json \
--output ./dist/my-function.eszip
# 2. Sign the bundle
openssl pkeyutl -sign \
-inkey /secrets/bundle-signing-private.pem \
-rawin \
-in ./dist/my-function.eszip \
-out ./dist/my-function.eszip.sig
# 3. Deploy with signature
SIG_B64="$(base64 < ./dist/my-function.eszip.sig | tr -d '\n')"
curl -X POST https://admin.my-edge.com:9000/_internal/functions \
-H "X-API-Key: ${ADMIN_API_KEY}" \
-H "x-function-name: my-function" \
-H "x-bundle-signature-ed25519: ${SIG_B64}" \
--data-binary @./dist/my-function.eszip