orkastor
v0.2.0
Published
The terminal path to Orkastor Cloud. Deploy an image or a repository and get an HTTPS URL.
Readme
npx orkastor
The terminal path to Orkastor Cloud. No install, no global, no runtime dependencies.
npx orkastor login
cd my-project
npx orkastor deployWhat an environment is
An environment is one running copy of your app, with its own URL and its own clock — not a dev/stage/prod tier. Make one per branch, per pull request or per demo, and delete it when you are done.
That sentence is here because the word already means something else everywhere else. On Railway, Heroku, Render and Fly an "environment" is a variant of a project — production, staging, development — so people arrive expecting to pick one of three. Here it is the opposite granularity: the industry name for this shape is a preview or ephemeral environment.
An environment holds one or more apps — 1 on Trial, 5 on Dev, 20 on Team — path-routed behind a single hostname.
Environments are ephemeral by design. Each has a lifetime, and its storage does not survive reclamation. Keep anything you need in your repository or in a database you run.
Commands
orkastor login | logout [--all] | whoami | tokens
orkastor deploy # from the current directory
orkastor deploy --image X --port 8080
orkastor deploy --repo owner/name --ref main
orkastor ls
orkastor logs <env> [--app NAME] [--follow]
orkastor open <env>
orkastor rm <env>
orkastor apps <env>
orkastor apps add <env> --image X --port N [--path /api]
orkastor apps rm <env> <name>
orkastor vars <env> # or `vars ls <env>`
orkastor vars set <env> NAME=VALUE
orkastor vars set <env> NAME # value read from stdin
orkastor vars set <env> --from-file .env
orkastor vars rm <env> <name>
orkastor suspend <env>
orkastor resume <env>
orkastor extend <env> --hours N
orkastor ai "<question>"
orkastor dockerfile <env> [--latest]orkastor help <command> for one command's flags.
Environment variables on an environment
vars, not env. env VAR=x cmd is a shell idiom, and more to the point an
environment is this product's central noun — orkastor env ls would read as
"list my environments", which is orkastor ls and a different command.
Variables are per environment, not per app. Every app in the environment sees
the same set, and the control plane refuses two apps declaring the same name with
different values. There is no --app flag for that reason.
You cannot read a value back. Not with any flag, and not later
orkastor vars <env> lists names, versions and when each changed. There is
no value, no preview, no first four characters, no length and no digest — and no
other command, endpoint or flag anywhere that returns one.
That is deliberate. The value was typed in by you, who already has it; a platform
keeping a second readable copy buys you nothing and adds a way to lose it. Fly,
GitHub Codespaces and Vercel's sensitive mode all land in the same place.
So there is no orkastor vars pull, and there will not be one. If you are
looking for the shape vercel env pull has, it cannot exist against this control
plane. The traffic goes the other way: --from-file pushes a whole .env up in
one transaction, which is the half that does the work.
Three ways to give it a value, in the order we would pick them
orkastor vars set myenv --from-file .env # nothing sensitive on the line
printf %s "$DB_URL" | orkastor vars set myenv DATABASE_URL
orkastor vars set myenv DATABASE_URL=postgres://… # in your shell history, and in `ps`The third works and prints a warning saying where the value has just been left. A file is refused whole if any line in it is not an assignment — the control plane refuses a bad batch whole too, and a half-applied set of credentials is a state nobody can reason about. The refusal names the line NUMBER, never its text.
Setting an existing name rotates it: the version goes up and the environment's
revision advances, so the workload rolls and the new value is read at container
start. A running container keeps the old value until then. set does not ask for
confirmation, because it spends nothing and destroys nothing.
vars rm does ask, and in CI it needs --yes. Nothing can read the value back,
so removing one is not recoverable from anything of ours. If the environment's
spec still declares the variable, it then stops deploying rather than starting
without it — which is deliberate, because a pod missing its configuration comes
up healthy.
⚠ Delivery into a running container has never been exercised end to end. A
variable set today is stored, encrypted, listed and resolvable, and no reconciler
has yet polled the seam that would inject it. Every set and rm prints the
control plane's own sentence saying so.
Suspending, resuming and extending
orkastor suspend myenv # stop the microVM, release its memory
orkastor resume myenv --yes # start it again, before a request would
orkastor extend myenv --hours 8 --yessuspend and resume record a REQUEST. They answer 202 and the
environment's state does not move: a reconciler applies it later and reports
back. These commands print the control plane's own note verbatim and never
report the state that was asked for as though it had happened. Today that note
also says whether a reconciler has ever connected — and if none has, the request
will not be applied at all.
suspend does not ask for confirmation: it stops the meter and destroys nothing.
resume and extend both need --yes in CI, because a suspended environment is
not billed for compute and a running one is, and more lifetime is more billed
hours. Neither can be priced first — POST /quote prices an environment about to
be created and there is no route that prices more hours on an existing one — so
they say plainly that they spend rather than showing a figure that would be a
guess.
extend is also the undo for an environment inside its grace window: it goes
back to RUNNING and the reclamation deadline is cleared. The cap is measured from
when the environment was created, not from now, so an extension can be granted
short. When it is, the command says so.
The AI
Orkastor has an AI that can read your workspace. Two commands reach it, and neither of them changes anything.
orkastor ai "why did checkout stop responding?"
orkastor dockerfile checkout
orkastor dockerfile checkout --latest # read the last one; spends nothingBoth spend the workspace's monthly AI budget. Neither spends prepaid
credit, which is what --yes guards, so neither asks for it — and both work in
CI unchanged. A workspace over its AI budget is refused before anything runs,
in the control plane's own words.
ai answers, and sometimes PROPOSES. It never acts
The AI can read your environments, builds, build logs, existing AI proposals and the repositories reachable through your GitHub connection. It cannot create an environment, start a build, deploy, or write to a repository. What it can do is propose one of those, and a proposal is a description of something that has not happened.
So when a proposal comes back, this CLI prints it and stops:
! Proposed: deploy ghcr.io/acme/web:2
environment: env-8f21
image: ghcr.io/acme/web:2
why: the tag has moved since the last deploy
Nothing has been created, started, deployed or written. This is a proposal.
To do it yourself:
orkastor ls
orkastor deploy --name <the name of env-8f21> --image ghcr.io/acme/web:2It will not turn that into a deploy, and there is no flag that makes it. That is the point of the design: propose-then-confirm exists so a person gets to say no, and a client that quietly closes the loop has deleted the only place they could.
Two of the four proposals have no exact equivalent here — starting a build, and patching a repository. Those print with no command, and say so, rather than offering something close.
A patch is yours to apply. If the AI proposes a code fix, it is a diff against your repository. The diff is printed in full so you can read it. Orkastor does not commit to your repository and this CLI offers no command that would. It has not been built and it has not been run.
The answer is a model's, not a measurement
Every ai run prints the reads it actually made before it prints the answer:
· read list_environments — 3 environments
· read read_build_log — 200 lines
The answer below is a model's, not a measurement. …The answer goes to stdout, so orkastor ai "…" | pbcopy is the answer and
nothing else. Everything else — the reads, the caveat, any proposal — is
stderr. --json puts the whole run, proposal included, on stdout instead.
When a run produces nothing, the control plane's own sentence is printed unchanged and the exit code is 4. That includes "no AI provider is configured", which is a deployment problem rather than a refusal of your question — the sentence says so, which is why it is quoted rather than replaced. A deployment whose API has no AI route at all exits 6, not 4: a 404 is not a refusal.
dockerfile proposes a Dockerfile you commit yourself
For an environment built from a GitHub repository that has no Dockerfile of its own. It reads the repository's manifests and writes one.
orkastor dockerfile checkout > DockerfileThe Dockerfile goes to stdout for exactly that reason; the rationale, the base image, every warning and the server's own note go to stderr. Nothing is written to your repository, no branch is pushed and no pull request is opened. The API has a route that opens one, admin-only and off by default — it is deliberately not reachable from here, because a write into your repository should not be one flag away from a read.
It has never been built. Treat it as a starting point, not a result.
--latest reads the most recent proposal back instead of generating a new one,
and spends nothing.
There is no orkastor diagnose
Build diagnosis is a real AI feature and it is not exposed here on purpose. It
needs a failed build, builds run in Kata microVMs on bare metal, and that
metal is not provisioned — kata-deploy sits at DESIRED 0. A command that can
never fire is worse than one that does not exist, because it fails in a way that
reads as your fault.
What deploy reads, and what it asks
From the current directory it reads, and tells you what it found and where:
| It looks for | It uses it for |
|---|---|
| Dockerfile → first EXPOSE | the port, when you did not pass --port |
| git config remote.origin.url | the repository, when it is a GitHub one |
| the checked-out branch | the ref, and part of the name |
| the directory name | the environment name |
| an environment already called that | it becomes an update, not a second environment |
The port order is --port, then EXPOSE, then 8080, mirroring the control
plane's own fallback. If it cannot tell what to run, it asks — and in CI, where
there is nobody to ask, it refuses and names the flag rather than guessing.
In CI
Non-TTY is a first-class mode: no spinners, no colour, no prompts.
env:
ORKASTOR_TOKEN: ${{ secrets.ORKASTOR_TOKEN }}
run: npx orkastor deploy --image ghcr.io/acme/web:${{ github.sha }} --yesAnything that spends prepaid credit needs --yes; without it the run refuses and
exits 7 rather than blocking on a prompt nobody can answer.
Exit codes
| | | |---|---| | 0 | it worked | | 1 | unexpected — a bug in the CLI | | 2 | the command line could not be acted on; nothing was sent | | 3 | no usable credential | | 4 | the control plane received it and refused it | | 5 | the control plane could not be read at all | | 6 | no such route in this deployment | | 7 | a confirmation was needed and could not be obtained; nothing was done |
deploy prints the environment's URL to stdout, alone on its line, at the
202 — before anything is running. Everything else goes to stderr, so
npx orkastor deploy --yes | tail -1 is the URL and nothing else.
Environment variables
| | |
|---|---|
| ORKASTOR_TOKEN | A bearer token. Overrides the stored credential entirely; nothing is read from or written to disk. |
| ORKASTOR_WORKSPACE | Which workspace to act on, by id or slug. |
| ORKASTOR_API_URL | The control plane. Defaults to https://api.orkastor.cloud. |
| ORKASTOR_LOGIN_URL | The browser sign-in page, for self-hosted installs. |
| ORKASTOR_AUTH_FILE | Where the credential is stored. |
| ORKASTOR_DEBUG | Print a stack trace on an unexpected error. |
| NO_COLOR, CI | Both honoured. |
The credential
orkastor login opens a browser, the page signs you in, and it posts a token to
a listener the CLI holds on 127.0.0.1 — the same hand-off kubegraf auth login
uses, against the same page.
That token is a Firebase ID token and lasts about an hour, so login does one
more thing with it before it lapses: it exchanges it, once, for an Orkastor CLI
token.
orkcli_<64 hex characters>It is Orkastor's own credential. api.orkastor.cloud issues it, verifies it
against Orkastor's own database, and it authorises nothing anywhere else — not
on app.kubegraf.io, not in any cloud account. The prefix is there so that
anyone who finds one in a CI log knows whose it is and where to revoke it.
| | |
|---|---|
| Expires | 90 days after you last use it. Every command pushes that forward. |
| Ceiling | 180 days from the day it was issued. This one never moves. |
| Stored | Only its SHA-256 reaches our database. It is shown once and cannot be recovered. |
| Revoked | orkastor logout, effective on the next request. |
The token is stored in your OS config directory (~/.config/orkastor/auth.json
on Linux and macOS, %APPDATA%\orkastor on Windows) with mode 0600. It is
never written into your project, never printed, and scrubbed out of any error
message that might otherwise carry it.
orkastor tokens lists every CLI token on your account — its label (the
machine's hostname), when it was last used, and both deadlines. No part of any
secret is shown, because none is kept. Last used is the column that matters:
it is what makes revoking the right one safe.
orkastor logout revokes this machine's token at the control plane and then
removes the local file, in that order. Deleting the file alone would forget a
credential that still works. orkastor logout --all revokes every token on the
account, which is what to run when the machine holding one is gone.
Against a control plane that does not have /api/orkastor/cli/tokens yet,
login keeps the browser token and says out loud that the sign-in lasts an hour.
Nothing here has to change when that deployment catches up.
Pricing
Creating an environment and adding an app both debit prepaid credit. Both commands ask the control plane what the specific thing you are about to create costs, show that total and the balance you will be left with, and only then make the call. If the workspace cannot afford it, they say so before spending rather than after a 402.
There is no rate card here, and there is not one anywhere else either.
