GibilGibil

Architecture

Design decisions and technical implementation

Gibil is a thin orchestration layer between you and your cloud provider. There's no custom infrastructure, no proprietary runtime, no vendor lock-in. It's a CLI that makes the right API calls in the right order. Today's supported providers are Hetzner Cloud and Vultr, both behind a single CloudProvider interface.

Design decisions

Raw fetch over provider SDKs

Zero extra dependencies, full control over the HTTP layer, easy to mock in tests at the fetch boundary. Each provider integration is one file with a private request<T>() method — no SDK churn, no transitive deps.

CloudProvider interface + ProviderRegistry

The CloudProvider interface is the abstraction boundary. Hetzner and Vultr both implement it. Every command and the MCP server route through ProviderRegistry, which resolves the right provider for each operation — including months later, because every instance metadata file carries its provider name. Nothing outside src/providers/ imports a specific provider class.

Adding a new cloud is one new file plus one row in the static catalog.

Static catalog

Provider metadata — sizes, default region, label — lives in plain TypeScript at src/providers/catalog.ts, separate from the provider class itself. That's how gibil providers describes a cloud without instantiating one (no token required to list options), and how the size abstraction (small / medium / large) stays consistent across clouds while mapping to the right native SKU per provider.

Cloud-init over SSH provisioning

The server configures itself on boot. Gibil generates a cloud-init script with your runtime, services, and tasks, then hands it to the provider. No SSH connection needed during setup, no fragile multi-step provisioning over the network. The script is provider-agnostic.

Auth is optional

The CLI works with just a provider token. Gibil's auth layer (Supabase + Stripe) adds metering and billing for the managed service, but it's entirely optional. BYOC users never touch it.

InstanceStore with dependency injection

Instance metadata is stored as JSON files in ~/.gibil/instances/. The store accepts a baseDir parameter — production uses ~/.gibil, tests use a temp directory. No global state, no singletons. Each metadata file carries its provider name so destroy/run/ssh resolve the correct cloud months after creation.

Tech stack

ComponentTechnologyWhy
CLI frameworkCommander.jsStandard, well-documented
BuildtsupFast, zero-config ESM bundling
LanguageTypeScript (strict)Type safety without ceremony
SSHssh2Native Node.js SSH, no shelling out
ConfigYAML (.gibil.yml)Human-readable, version-controllable
TestsVitestFast, ESM-native, good DX
Cloud (EU/US)Hetzner Cloud APICheapest full VMs, clean API
Cloud (APAC)Vultr APITokyo, Seoul, Singapore, Sydney, Mumbai
Auth/billingSupabase + StripeOptional, no extra hosting needed

Project structure

src/
  cli/
    commands/       # One file per command (create, ssh, run, destroy, providers, ...)
    index.ts        # Entry point — registers all commands with Commander
  providers/        # CloudProvider implementations + registry + static catalog
    registry.ts     # ProviderRegistry: resolves the right provider per command/instance
    catalog.ts      # Static metadata (sizes, regions) — no token needed
    hetzner.ts      # Hetzner provider
    vultr.ts        # Vultr provider
  config/           # YAML parser + cloud-init script generator
  ssh/              # Key generation + remote command execution
  types/            # Shared TypeScript types (index.ts)
  utils/            # Logger, store, auth, paths, validate, random

Each command is a single file with a registerXCommand(program) function. Providers are resolved through the registry, never imported directly. Utilities are small and single-purpose.

Next steps

On this page