myapp/
├── bin/
│ └── myapp # the entry script
└── lib/
└── myapp.rb
GUIDES · 04
Package a Ruby app.
tebako press turns a source tree into one executable per platform. The developer compiles only the application's own code — the runtime and the loader arrive as prebuilt binaries.
|
Note
|
This guide pairs with parts 3 and 4 of the Basic payloads tutorial, which package a single Ruby app and then a Rails app. |
Lean or fat.
Lean — the default. The package is the bootstrap plus the payload plus a trailer. The first run on a machine downloads the shared runtime once, and every package that accepts the same ruby line reuses it. The file is smaller, and the machine keeps one runtime. Lean is chosen whenever the machine has a network path at first run.
Fat — -m fat. The runtime travels inside the package as a slot, pinned by SHA-256. The file is bigger, the network use at run time is zero, and nothing is shared. Fat suits air-gapped machines, USB hand-offs, and strict-offline environments.
The press command.
The input is a tree with an entry script:
The tree is pressed into an executable:
$ tebako press -r ./myapp -e bin/myapp -o myapp
Inside the project directory the flags are optional: a press with neither -r nor -e reads the project in place — a Gemfile marks a ruby project, the entry point is the single executable Ruby script under bin/ then exe/, or the single *.rb at the root — and proceeds with what it found. An ambiguous layout (two candidate scripts) is refused by name with the candidates listed, never guessed.
A project for another engine is refused with what the press read, not just the engine’s name: a package.json directory reports the entry it derived (bin, else main, else a root index.js) and the engines.node requirement, and points at the feedstock model — the route a pressed package for that engine takes today. pyproject.toml and Cargo.toml projects are named the same way; detection and its refusal ship for ruby first.
$ cd myapp && tebako press -o myapp
With no -o, the output takes the entry’s name; with no flags at all, one command does the whole job:
$ cd myapp && tebako press
… target entry point will be at /__tfs__/local/bin/myapp
… Created tebako package at "/…/myapp"
$ ./myapp
hello from a pressed package
-r is the source root, -e is the entry script inside it, and -o is the output file. -R picks the ruby version (default 3.3.7). -c pins the working directory the app starts in, and -p overrides the in-image prefix the root is packed under — both options are rarely needed. -l error|warn|debug|trace sets the press tool’s own log level. --format dwarfs|limnifs picks the image format the payload is written in — limnifs is the default; dwarfs stays a first-class opt-in, and existing dwarfs images read everywhere they always have.
The v1 modes are gone by design: -m classic and -m runtime fail with a named error, and there is no tebafile — -t is rejected.
Extra slices, baked policies, standalone-only.
$ tebako press -r ./myapp -e bin/myapp -o myapp \
--image acme-fonts-2.1.0.tfs:/usr/share/fonts/acme \
--jail ./jail.yaml
-
--image <path>:<mount>packs an additional payload slice into the package and mounts it at the given path; the option is repeated once per slice — a fonts image, a dataset, a native toolkit. -
--jail <spec>bakes a filesystem policy into the package — a profile word or a YAML file. Users can tighten it at run time, never loosen it; see the jails guide. -
--no-installfreezes the package to standalone-only: it executes directly, andtebako installrefuses it with a named error (exit 76).
One package, several commands: suites.
A suite presses one package that carries several entry points; each entry point becomes its own shim when the package is installed. The suite is described in YAML:
# suite.yaml
name: acme
version: 1.4.0
entries:
- name: acme
root: ./cli
entry: bin/acme
- name: acme-index
root: ./indexer
entry: bin/index
Press the suite:
$ tebako press --suite suite.yaml
An entry may pin its own runtime with a runtime_ref key; entries without one share the suite’s resolved runtime.
From a press to a feedstock.
A press proves one project on one platform. A feedstock builds the same payload for a matrix of platforms and publishes it under a release line. --emit-recipe closes the distance between the two: the press renders the feedstock recipe from the press that just ran, so the document carries the values the press resolved rather than values retyped by hand.
$ cd myapp && tebako press --emit-recipe
…
Created tebako package at "myapp"
Wrote feedstock recipe at "Tebakofile"
The rendered document names the engine and its requirement, the entry point, the press mode, the image format, the lockfile the press vendored, and the resolved runtime pair with the factory release the pair came from, which the press reads from the pair’s own provenance record. It names the platform leg the press proved, written as a platform triplet, and the executable the payload provides. The bare flag writes Tebakofile at the project root; --emit-recipe=<path> writes the document where the feedstock repository expects it.
The platform list carries one entry because a press proves one platform. A feedstock adds a leg when a continuous-integration leg proves it, and the recipe’s build.system: press tells that leg to run the press the author already ran. The command never overwrites a recipe it did not generate: a file without the generated marker is refused by name, so an authored recipe stays the author’s.
What press resolves.
Two artifacts come from outside the source tree: the ruby runtime (interpreter executable plus env image) from the runtime factory’s release index, and the bootstrap loader from the tebako releases. Both are downloaded and SHA-256 verified during press.
--prefer-local prefers artifacts already in the store over fetching. --bootstrap <path> swaps in a specific loader binary (also settable as TEBAKO_BOOTSTRAP). --tebako-version <v> pins the tebako toolchain release press fetches its artifacts from. TEBAKO_RUNTIME_MIRROR redirects runtime downloads to an operator-provided mirror (an https or file:// base URL) — the air-gapped partner of -m fat.
Testing the result.
$ ./myapp --help
$ tebako inspect ./myapp
$ tebako inspect --contract ./myapp
inspect reads the package’s trailer and manifest: what it is, which handoff contract it speaks, and its trust state. The full surface is in info & inspect.
A note on native extensions.
A payload with compiled gems pins the ruby application binary interface (ABI) line it was built against — ~> 3.3.0 — plus the platform ABI string. A machine whose cached runtimes offer no compatible line fails at resolve time with a named error, never a segfault. Pure-ruby payloads declare a range instead — >= 3.3, < 5.0 — and one universal payload serves every platform.
The manifest model, field by field, is the manifest reference.