vesperctl reference
vesperctl reads and creates the preview environments that Vesper manages
in a Kubernetes cluster. It uses your kubeconfig, like kubectl. Reading needs
get and list on previewenvironments.vesper.eventide.dev, creating
needs create.
Build it with make build; the binary is bin/vesperctl.
Global flags
Section titled “Global flags”Every command accepts them, before or after the command name.
| Flag | Default | Meaning |
|---|---|---|
--kubeconfig <path> |
$KUBECONFIG, then ~/.kube/config |
kubeconfig file to read |
--context <name> |
the current context | kubeconfig context to use |
-n, --namespace <name> |
the namespace of the context, else default |
namespace to work in |
Commands
Section titled “Commands”vesperctl list
Section titled “vesperctl list”List the preview environments of the namespace, sorted by namespace then name.
| Flag | Meaning |
|---|---|
-A, --all-namespaces |
list every namespace (overrides --namespace) |
-o, --output <format> |
table (default), json or yaml |
$ vesperctl list -n team-aNAMESPACE NAME PHASE AGEteam-a pr-12 <none> 5mteam-a pr-42 Ready 90mWith no preview environment, the table output prints a message on stderr
and nothing on stdout. JSON and YAML always print a document:
{"items": []}.
vesperctl get <name>
Section titled “vesperctl get <name>”Show one preview environment: status, expiry, a summary of its spec, its conditions and its latest events.
| Flag | Meaning |
|---|---|
-o, --output <format> |
table (default), json or yaml |
$ vesperctl get pr-42 -n team-aName: pr-42Namespace: team-aRuns in: vesper-team-a-pr-42-3f9c2a1bCreated: 2026-11-05T10:00:00Z (120m ago)Phase: ReadyURL: https://pr-42.preview.example.comExpires: 2026-11-06T10:00:00Z (in 22h)Spec: Image: ghcr.io/eventide/app:pr-42 Port: 8080 Replicas: 1 TTL: 24h Env: 2 variablesConditions: TYPE STATUS REASON AGE MESSAGE Available True DeploymentReady 90m 1/1 replicas available Progressing False Complete 90m <none>Events: TYPE REASON AGE MESSAGE Normal Provisioning 92m creating the objects of the preview Normal Ready 90m 1/1 replicas available<none> marks a value that is not set yet. Environment variables may hold
secrets: only their number is shown, in every output format.
Events lists the last 10 events the operator recorded about the preview,
oldest first, as kubectl describe does; an event repeated by the API server
shows its count, such as 5m (x3). The API server keeps events for about an
hour. Only the events of this preview are shown, not those of an earlier
preview with the same name. If you may read previews but not events, get
says <cannot list events: forbidden> and succeeds. The JSON and YAML output
holds the preview only, not its events.
Runs in is the namespace the operator created for the preview, where its
application runs; it is set once the namespace exists. Its pods, logs and
events are there:
NS=$(kubectl get pe pr-42 -n team-a -o jsonpath='{.status.namespace}')kubectl get pods -n "$NS" # the pods of the previewkubectl logs -n "$NS" deploy/app # the logs of its application; -f follows themkubectl events -n "$NS" # what happened there, oldest firstvesperctl create <name> --image <image>
Section titled “vesperctl create <name> --image <image>”Create a preview environment in the namespace. The operator then runs its
image; follow it with vesperctl get.
| Flag | Meaning |
|---|---|
--image <image> |
container image to run, pinned with a tag other than latest or with a digest (required) |
--port <port> |
port the application listens on; default 8080 |
--replicas <n> |
number of pods; default 1 |
--ttl <duration> |
time to live from the creation, such as 2h or 90m; default 24h |
--env KEY=VALUE |
environment variable of the application; repeat it for several |
--dry-run |
print the preview environment as YAML instead of creating it |
$ vesperctl create pr-42 -n team-a --image ghcr.io/acme/app:pr-42 --ttl 48hpreview environment "pr-42" created in namespace "team-a"Follow it with: vesperctl get pr-42 -n team-aFlags that are not given are left out, and the API server applies its
defaults. vesperctl checks the form of the flags (a number, a duration,
KEY=VALUE); the API server checks the values, with the rules of the CRD
(port, replicas, ttl between 1m and 168h, image), and vesperctl prints its
answer:
$ vesperctl create pr-42 --image ghcr.io/acme/app:latest --ttl 30svesperctl: the API server rejected the preview environment: spec.image: Invalid value: "ghcr.io/acme/app:latest": image must be pinned with a tag other than latest, or with a digest; spec.ttl: Invalid value: "30s": ttl must be a duration such as 24h or 90m, between 1m and 168h--dry-run sends nothing to the cluster: the output can be saved and
applied later, for example with kubectl apply -f. The values are only
checked when it is applied.
vesperctl extend <name> --by <duration>
Section titled “vesperctl extend <name> --by <duration>”Extend a preview environment: raise its ttl by --by, such as 2h or
90m. The ttl counts from the creation of the preview, so the expiry moves by
the same amount; the operator writes it to status.expiresAt.
$ vesperctl extend pr-42 -n team-a --by 2hpreview environment "pr-42" extended by 2h0m0s: ttl 26h0m0s, expires at 2026-11-08T12:00:00ZA ttl cannot exceed 168h: a preview lives 7 days at most, extensions included (ADR-0004). vesperctl checks it before sending anything, and says by how much the preview can still be extended:
$ vesperctl extend pr-42 --by 145hvesperctl: cannot extend "pr-42" by 145h0m0s: its ttl would be 169h0m0s, over the maximum of 168h0m0s; it can be extended by 144h0m0s at mostA preview that is being deleted, after its expiry or a vesperctl delete,
cannot be extended: create it again. If someone else changes the preview at
the same time, vesperctl reads it again and extends the new ttl, so no
extension is lost.
vesperctl delete <name>
Section titled “vesperctl delete <name>”Delete a preview environment. vesperctl first asks for a confirmation on
stderr and reads the answer on stdin: y or yes deletes, anything else,
an empty line included, does not and exits with 1.
$ vesperctl delete pr-42 -n team-aDelete preview environment "pr-42" in namespace "team-a", with everything in its preview namespace? [y/N] ypreview environment "pr-42" is being deleted: the operator deletes its namespace, then the preview| Flag | Meaning |
|---|---|
-y, --yes |
delete without asking for confirmation |
In a script, pass --yes: without it and with nothing to read on stdin,
vesperctl does not delete, and says so. vesperctl returns once the deletion
is requested: the operator deletes the preview namespace and everything in
it, then the preview, within a few seconds. A preview that is already being
deleted is left as it is, with exit code 0. If the preview was deleted and
created again between the question and the answer, the new one is not
deleted.
vesperctl completion bash|zsh|fish
Section titled “vesperctl completion bash|zsh|fish”Print a shell completion script. It completes commands, flags, the values of
-o and the shell names.
# bash (needs the bash-completion package)echo 'source <(vesperctl completion bash)' >> ~/.bashrc
# zsh (the completion system must be loaded: `autoload -U compinit && compinit`,# which frameworks such as Oh My Zsh already do)echo 'source <(vesperctl completion zsh)' >> ~/.zshrc
# fishvesperctl completion fish > ~/.config/fish/completions/vesperctl.fishThen start a new shell.
vesperctl version
Section titled “vesperctl version”Print the version of vesperctl, the Go version and the platform.
| Flag | Meaning |
|---|---|
-o, --output <format> |
table (default), json or yaml |
$ vesperctl versionvesperctl version v0.1.0 (go1.27.3 linux/amd64)
$ vesperctl version -o json{ "version": "v0.1.0", "goVersion": "go1.27.3", "platform": "linux/amd64"}The three fields are what a script reads: version is the release, or dev
for a plain go build; goVersion is the toolchain that built the binary;
platform is the OS and architecture it runs on. The default line stays the
same, so a shell prompt or a log line does not change.
Output formats
Section titled “Output formats”-o json and -o yaml on get and list print a summary of each preview
environment, not the raw Kubernetes object. The summary is a stable contract
for scripts: fields may be added in later versions, and existing fields keep
their name and meaning. Use kubectl get previewenvironment <name> -o yaml
for the raw object.
get prints one summary; list prints {"items": [...]}.
| Field | Type | Meaning |
|---|---|---|
name, namespace |
string | always present |
runNamespace |
string | the namespace where the application of the preview runs; omitted until the operator creates it |
createdAt |
string | creation time, RFC 3339 in UTC |
phase |
string | Pending, Ready, Failed or Expired; omitted until the controller sets it |
url |
string | address of the preview; omitted until set |
expiresAt |
string | when the preview is deleted, RFC 3339 in UTC; omitted until set |
spec.image, spec.ttl |
string | as requested; omitted when unset |
spec.port, spec.replicas |
integer | as requested; omitted when unset |
spec.envCount |
integer | number of environment variables; names and values are never printed |
conditions[] |
array | type (the main one is Ready), status, reason, message, lastTransitionTime, in the order the controller set them; omitted when empty |
JSON fields keep this order. YAML keys are sorted alphabetically, as kubectl does.
vesperctl list -A -o json | jq -r '.items[] | select(.phase == "Ready") | .url'Exit codes
Section titled “Exit codes”| Code | Meaning | Examples |
|---|---|---|
0 |
success | the command did what was asked, including an empty list |
1 |
the command was valid but failed | preview not found, deletion not confirmed, permission denied, cluster unreachable, no kubeconfig, Vesper not installed |
2 |
the command line is wrong | unknown command or flag, missing or invalid name, unknown output format or shell, a flag that is not a number or a duration, a value the API server rejects, an extension over 168h |
A usage error (2) also prints the usage of the command, except when the API
server rejected a value or an extension goes over 168h.
Error messages
Section titled “Error messages”Errors are printed on stderr, on one line starting with vesperctl:, and say
what to do next when vesperctl can tell:
| Situation | Message |
|---|---|
| no kubeconfig | no kubeconfig found: set --kubeconfig or the KUBECONFIG environment variable |
| Vesper not installed | the PreviewEnvironment API (...) is not installed in this cluster: install Vesper, or check --context |
| preview not found | preview environment "pr-1" not found in namespace "team-a" |
preview already exists (create) |
preview environment "pr-1" already exists in namespace "team-a": delete it first, or choose another name |
namespace not found (create) |
namespace "team-a" not found: create it, or choose another with --namespace |
ttl over 168h (extend) |
cannot extend "pr-1" by 145h0m0s: its ttl would be 169h0m0s, over the maximum of 168h0m0s; it can be extended by 144h0m0s at most |
deletion not confirmed (delete) |
not deleted |
no answer on stdin (delete) |
no answer: not deleted; pass --yes to delete without confirmation |
preview being deleted (extend) |
preview environment "pr-1" is being deleted: it cannot be extended; create it again |
value rejected by the API server (create) |
the API server rejected the preview environment: <field>: <reason>; ... |
| 403 from the API server | you are not allowed to list preview environments in namespace "team-a": ask a cluster admin for access (RBAC) |
| 401 from the API server | the API server rejected your credentials: check your kubeconfig, or log in again |
| no response (refused, timeout, TLS) | cannot reach the API server at https://...:6443 (check --context and your network): <cause> |
Requests time out after 30 seconds. A Go stack trace is never printed: if
vesperctl hits a bug, it prints internal error: ... (this is a bug, please report it at https://github.com/eventide-oss/vesper/issues) and exits with
1.