Skip to content

ARCHITECTURE · 23

How tebako compares.

"Package once, run anywhere" is a big promise, and most tools attach conditions to it: a compatible JVM already installed, an engine already running, a machine that looks like the build machine. This page shows where tebako fits among the tools you already know — and names the cases where another tool is the better choice: a virtual machine when you need the strongest isolation, your system package manager when you administer the whole machine, and Docker where an engine is already running.

Note

Stated plainly. Tebako is not a replacement for your toolchain. You still develop against your own ruby, your own gems, your own OS packages. Tebako enters at the distribution seam: the moment a resolved, working app has to run on a machine you don’t control. Everything this page attributes to tebako — the jails, the dispatcher, the shared cache, the signing — is shipped behavior; the one forward-looking claim (non-ruby runtimes) is badged where it appears.

What a process sees, in each world.

Strip away the packaging formats and the difference is one question: when your program calls open(), what answers?

TEBAKO your process the VFS — userland, in-process mounted images host, jail-gated no kernel, no FUSE, no daemon DOCKER / OCI your process kernel namespaces + overlayfs the container image — Linux only needs the engine; a hidden VM off-Linux HOMEBREW / DNF your process the kernel — the plain host FS /opt/homebrew — mutated per install versions float with the repo RBENV / RVM your process a shell shim re-points PATH ~/.rbenv/versions/x.y.z plain host files ruby versions managed; apps not packaged A VIRTUAL MACHINE your process a guest kernel, on virtual hardware a whole guest OS + its disk image the real boundary — at a gigabyte's weight

Figure 1 — What a process sees in each world: the tebako VFS, Docker namespaces, Homebrew system paths, rbenv shims, and a virtual machine.

The eight axes.

  1. Install footprint — what lands on the machine to make the app run — megabytes, trees, daemons, hypervisors.

  2. Runtime sharing — whether N apps on one machine carry N copies of their runtime, or one.

  3. Startup cost — what happens between the user pressing enter and the app running — per run.

  4. Isolation — what the app can see and touch beyond itself, and who enforces that.

  5. Integrity model — how you know the bytes are the bytes the publisher shipped — checksums, signatures, chains of trust.

  6. Offline behavior — what works with the network cable pulled — first run and steady state.

  7. Cross-platform reach — which operating systems and libcs the same story covers, and at what cost per target.

  8. Transparency — what the end user has to know, install, or run before the app — ideally: nothing.

"Runtime sharing" deserves its own sentence, because it is tebako’s structural difference: a tebako runtime — the interpreter executable plus its environment image — is downloaded once per machine and reused by every tebako application on it. Not once per app, not once per project: once. The cache is content-verified, installed read-only, and shared by design.

The matrix.

Eight axes, eleven answers, one line each. No row is all green, including ours — read the columns you care about.

TOOL INSTALL FOOTPRINT RUNTIME SHARING STARTUP COST ISOLATION INTEGRITY MODEL OFFLINE BEHAVIOR CROSS-PLATFORM REACH TRANSPARENCY

tebako

one file; the runtime lands in the shared cache once

yes — one verified runtime for every tebako app on the machine

exec + in-process mount; no extraction, no per-run re-hashing

declarative ro/rw jails per run — userland, no daemon, no root

opt-in OpenPGP + per-slot sha256, fail closed with named exits

fat: always; lean: full after the first fetch (TEBAKO_OFFLINE=1)

linux gnu/musl · macOS · Windows; pure-language payloads are universal

total — the user need not know tebako exists

rbenv / rvm

git clone + a build toolchain + per-machine ruby builds

rubies shared across projects

a shim re-exec hop, then native

none

source tarball checksums at build; gems over TLS

yes, once rubies are built

POSIX only — no Windows

developers only

homebrew

a host-wide /opt tree plus dependency kegs

kegs shared host-wide

native exec — nothing between

none — install scripts run as you, services higher

sha256-pinned formulae, signed bottles, attestations

no — network to install or update

macOS + Linux; no Windows

the user installs brew first

rubygems / bundler

gems per interpreter, per machine, per deploy

duplicated per ruby version on the box

gem activation at every process start

none

TLS to the gem server; X.509 gem signing is rare

vendor/cache makes deploys offline-capable

anywhere ruby runs — genuinely universal

developers and servers, not end users

.dmg / .msi

a per-app bundle with the runtime embedded

none — every app re-carries its world

native exec

only what the OS sandbox gives an opted-in app

Gatekeeper notarization / Authenticode — OS-checked PKI

yes — the installer itself is the offline artifact

one OS (and arch) per artifact

total — drag, click, run

dnf / apt

a root-owned system tree

system libraries shared across every app on the box

native exec

none — packages own system paths, scripts run as root

signed repo metadata (InRelease / repomd), always on

local mirrors and offline media are first-class

per-distro, per-release; linux only

invisible after install — for admins

snap

the snapd daemon plus per-app squashfs packages

base and content snaps shared between apps

cold start: squashfs mount + confinement setup

AppArmor/seccomp — full strength on Ubuntu, varies by host kernel

store-signed assertions, verified by snapd at install

installs need the store; installed apps run offline

linux only — best on Ubuntu

the user needs snapd and the store

flatpak

the flatpak daemon, an ostree repo, and portals

runtimes shared; ostree dedups content between apps

near-native once installed

bubblewrap + portals — a real sandbox, host-dependent

ostree GPG signatures; Flathub builds from manifests

needs a remote to install; runs offline after

desktop linux only

the user installs flatpak plus a remote

AppImage

one file per app — the whole world bundled inside

none — every app re-carries its runtime

exec plus a FUSE mount of the embedded image

none in the format (firejail is a separate add-on)

no standard signatures, no chain of trust

yes — the file is the whole app

linux only

total on linux — chmod +x and run

docker / OCI

an engine plus a GB-scale content store

layer dedup across images sharing a base

container create + start through the daemon

namespaces + cgroups — real, shipped boundaries

content digests; cosign / notary signing opt-in

yes, after the first pull — but the engine must run

linux ABI; a hidden VM on macOS/Windows

the user installs docker

VMs

gigabytes per guest, plus a hypervisor

nothing shared between guests

a guest OS boot — seconds to minutes

a hardware boundary — the strongest on this page

whatever the guest’s own stack does

yes, once provisioned

any guest OS on any host

ops teams, never end users

The language packagers — a different category.

Everything above distributes software to machines generically. A second family of tools answers a narrower question: freeze this language’s app into a file. Python has PyInstaller, PyOxidizer, and PEX; ruby has ocra and traveling-ruby. They are the tools tebako is most often confused with — and the difference is the approach, not the feature list.

Tool Language What it builds The trade-off

PyInstaller

python

a one-file or one-dir bundle with CPython embedded, per target OS

nothing extra — but one-file mode unpacks to a temp dir at every start, and each OS needs its own build

PyOxidizer

python

a single binary with an embedded CPython, imports served from memory

nothing extra — but upstream development stalled in 2024; its future is uncertain

PEX

python

a .pex zipapp of your code and dependencies

a compatible python interpreter on the machine (or one fetched at first run via scies)

ocra

ruby

a Windows self-extracting exe from the ruby installed on the build machine

nothing extra — but Windows only, and it unpacks to a temp dir at every start

traveling-ruby

ruby

a tar.gz/zip bundling a prebuilt portable ruby plus your gems, one per platform

nothing extra — the app carries its own ruby copy for each platform

The difference in approach.

  • They bind a language; tebako binds none. A language packager is a per-language answer — PyInstaller will never package a ruby app. Tebako’s loader neither knows nor cares what language it starts: a runtime is just a payload that provides an interpreter, plugged into the same provides/requires graph as every other payload. Ruby is the first instance; python and julia factories follow the same shape.

  • They embed the interpreter per app; tebako shares it per machine. Ten PyInstaller apps carry ten CPython copies. Ten tebako apps share one digest-pinned runtime from the machine-wide cache — downloaded once, verified once, mounted — never extracted — by all of them.

  • They produce per-OS artifacts; tebako’s pure-language payload is universal. PyInstaller must build on each target OS; traveling-ruby ships one tarball per platform. A pure-language tebako payload is one image that runs identically on every platform triplet, over per-triplet static loaders.

  • They end at the file; tebako starts there. Per-invocation version dispatch, per-run declarative jails, opt-in signatures, recursive payload composition — the ecosystem around the artifact is the product, not an afterthought.

Note

Status: planned — non-ruby runtimes.

Honest limit. Today, tebako’s published runtimes are ruby-only. If you ship a python app this quarter, PyInstaller or PEX is the working answer — mature tooling that knows the language’s quirks, with no ecosystem to buy into. Tebako’s claim here is architectural: the per-language packagers solve packaging once per language; tebako solves packaging and loading once, for any runtime — and ruby is the proof of existence, not the boundary.

Gatekeeper and Authenticode: the OS trust gates.

Gatekeeper (macOS) and Authenticode (Windows) are the two OS-level trust systems users actually rely on. They answer one question — "may this binary run?" — and tebako answers a different one — "is every part of what runs what it claims to be?" The systems are complementary layers, not competitors.

Gatekeeper (macOS) Authenticode (Windows) Tebako

object verified

the app bundle / binary

the binary / driver

every slice + every manifest + every runtime + registry items + the sums manifest — and partial re-stacking stays verifiable (per-slot digests)

when verified

at every execution, by the OS

at install/UAC + SmartScreen heuristics

at fetch, at install, at mount (per-slot digest before attach), at publish (author side)

anchor of trust

Apple-issued Developer ID cert (vendor CA)

CA-issued cert (EV for reputation)

embedded tamatebako root key (self-sovereign) + user trust store + successor-chain rotation

unsigned path

block or warn (policy)

warn via SmartScreen reputation

unverified-first with a loud warning + audit journal; TEBAKO_REQUIRE_SIGNED=1 fails closed

revocation

CRL/OCSP (online, coarse)

CRL/OCSP (online, coarse)

signed successor statements — offline, forward-verifiable — plus per-key registration/removal

offline / air-gap

verifies offline; notarizing needs online at build time

mostly offline; timestamping needs online at sign time

full offline first-class: embedded root + pinned keys + signed sums + air-gap bundles

scope beyond executables

none (binaries only)

none (binaries only)

runtimes, data slices, toolkits, suites, registries, manifests, indexes — the whole object graph

cross-platform

macOS only

Windows only

one model across linux gnu/musl, macOS, and Windows

key rotation

cert renewal via the CA

cert renewal via the CA

self-describing: the old root signs its successor; clients forward-verify offline

enforcement level

OS syscall gate — the app cannot bypass it

OS gate + kernel driver signing

loader-level — honestly: a patched loader is beyond any loader, which is exactly where the OS gates protect us

ubiquity

every Mac, nothing to install

every Windows box, nothing to install

anywhere a single binary lands — zero host prerequisites

provenance

developer identity only

publisher identity only

identity + builder + source ref + payload tree hash + contract_version

Where tebako defers — and integrates. Continuous execution-time enforcement. Gatekeeper’s syscall-level gate and SmartScreen’s reputation network cannot be replicated by an application-level loader, and a patched loader is outside any loader’s reach. Tebako’s answer is to sign tebako-produced binaries for both gates, so the OS protects the root anchor continuously: their enforcement guards our bootstrap, our verification guards everything below it. Apple and Microsoft are also already on the machine, and notarization includes actual scanning — tebako does not imitate this; it slots into it.

Where tebako goes further. Granularity: they see a binary; tebako sees every slice inside it — swap one slice and only its slot needs re-verifying. Scope: runtimes, data slices, toolkits, suites, registries, and indexes all use the same signed-image machinery. Sovereignty: no corporate CA in the loop — root rotation is a self-describing signed chain, verifiable offline. Provenance: builder, source ref, payload tree hash, and contract version — compared to "Developer ID: Company X".

Where each alternative genuinely wins.

Tebako is a distribution format for applications, not a bid to replace your package manager, your container engine, your language tooling, or your hypervisor.

Tool Where it genuinely wins

RubyGems

sharing code between developers. Tebako packages applications; gems remain how developers share libraries.

rbenv / rvm

hacking on ruby itself. If your job is building interpreters, you want a version manager, not a package.

Homebrew / apt / dnf

OS-integrated components with a security-update channel. Tebako deliberately never manages the OS.

Installers (.dmg/.msi)

deep OS integration: drivers, system extensions, things that must be on the host — plus enterprise deployment and notarization review.

snap / flatpak

store discoverability and daemon-managed automatic updates — a real distribution channel, paid for with the daemon and platform coverage.

AppImage

zero-machinery single-file desktop linux, with mature tooling and a large catalog. The closest neighbor of a fat tebako package.

Language packagers

a single-language app, today, in that language: PyInstaller/PyOxidizer/PEX know python’s quirks, ocra/traveling-ruby know ruby’s, and none of them ask you to buy into an ecosystem.

Docker / OCI

server-side multi-process orchestration and its ecosystem. Tebako distributes tools to people; Docker runs services on fleets.

Virtual machines

hard boundaries for hostile native code. Jails are filesystem policy, not a hardware security boundary.

What exists nowhere else.

  • A single file that is simultaneously the app, its verifier, and its runtime resolver — self-authenticating, offline, over any transport including a USB stick.

  • Universal payloads over digest-pinned shared runtimes — a pure-language app ships one image for every platform, and the runtime underneath it downloads once per machine. Electron’s fidelity with a shared runtime’s footprint, and neither’s lock-in.

  • Per-entrypoint runtime pins inside one package — suites: multiple commands in one artifact, each with its own runtime requirement.

  • Recursive payload composition — an app declares toolkits and data at mount points, each independently published and versioned, resolved from a provides/requires graph.

  • Per-run declarative jails for the end user — no daemon, no root, no kernel module; tighten any command’s filesystem view at invocation time.

  • Object-level encryption with per-subtree audiences — hierarchical keys, crypto-shredding revocation, in-memory-only plaintext — in an application distribution format.

  • One signed-image algebra at every level — sources, runtimes, payloads, packages, registries — verified the same way, with verification that survives redistribution through untrusted mirrors.

Where tebako loses, stated plainly.

  • It is ruby-shaped today. The contracts are language-neutral; the published artifacts are not — yet. Until the python and julia factories follow the ruby one, the language packagers keep their home field.

  • No orchestration, no dev loop, no store. There is no compose, no kubernetes, no browsable catalog. Registries are plain YAML files on the developer’s own git host — the reference syntax (tfs:github: / tfs:gitlab: / tfs+https:// / file://, ?sha256= pins) deliberately has no default service. And tebako doesn’t develop with you: it presses a finished, resolved app; it is not where you hack.

  • Per-platform artifacts for native code. A standalone package is native — one artifact per platform triplet, built in a matrix. Only pure-language payloads collapse to one universal image; native extensions stay per-triplet, and a wrong ABI line is a named error, never a segfault.

  • Jails are VFS policy, not a security boundary. The declarative ro/rw jail controls what the process can see and touch through the filesystem — it does not contain hostile native code. That is what VMs are for, and this page won’t pretend otherwise.

  • The integrity story is opt-in. Signing and encryption are the publisher’s choice; an unsigned package runs with a loud warning, recorded in the audit journal. The distro channel’s always-on signed metadata is the stronger default — tebako’s answer is that verification, when a signature exists, is strict and fails closed with named exit codes, and TEBAKO_REQUIRE_SIGNED=1 makes it mandatory where the environment demands it.

Keep reading.

Then the platform story: the overview for what tebako is · runtime as image for the shared cache · dispatch for per-invocation version management · the chain of trust for the integrity machinery · the store for where everything lives.