piquelctl
piquelctl is the small operator client for piqueld. It uses the
public piqueld-client contracts and talks to the daemon over a Unix socket by
default.
piquelctl commands lists every command with its arguments and options.
Installing
On the daemon’s host, the NixOS module installs piquelctl, and
the other installations
install it beside the daemon. On any other Linux or macOS machine, install
Rust with rustup, then install piquelctl from
crates.io:
cargo install --locked piquelctl
macOS support
On Apple Silicon, Nix users can run nix build .#cli or
nix run .#cli -- --help. On macOS the default flake package is also the CLI;
daemon and dashboard packages are Linux
only. nix develop provides the CLI development tools, and just validate-cli
lints and tests the CLI and its shared client/core crates. just build-cli
builds the release binary. CI runs just validate-cli on macos-latest when
the CLI or its shared crates change. nix-darwin users can import
darwinModules.piquelctl to install it with profiles; see the NixOS module.
The daemon runs on Linux. With its listen mode set to tailscale or both,
connect from a tailnet peer using its IP address or MagicDNS name:
piquelctl --allow-insecure-http --url http://linux-host:7845 login
piquelctl --allow-insecure-http --url http://linux-host:7845 status
For persistent configuration, set a named profile’s url to
http://linux-host:7845 and select it with --profile and --allow-insecure-http. See
daemon configuration.
Both platforms discover system and user profiles at runtime, including binaries built with Cargo. See connection profiles.
Commands
An application owns the saved manifest; its environments deploy it, each with its
own deployments, status, volumes, generated secrets, routes, and network. A
manifest that declares environments
gets exactly those when it is saved or fetched from the branch spec.manifest
names; otherwise applications have an environment named production from
creation. env commands select an environment by name
or stable ID; ENV may be omitted only when the application has exactly one.
app deploy, app reconcile, app stop, app start, and app logs act on
the application’s only environment and fail with an “environment required”
input error naming the environments when it has several; they never pick one. With several
environments, app delete requires --environments a,b naming every one.
env delete keeps the application and retains the environment’s named volumes.
Environment creation, renames and deletions are conditioned on the inspected
application revision like other mutations (--expected-generation, --force),
and advance it, so a command based on an earlier inspection fails instead of
acting on a renamed or deleted environment.
Declared environments exist as long as their [spec.environments.<name>]
block does: env create, env rename (to or from a declared name) and
env delete refuse them with environment_declared, naming the environment
(remove the block before deleting one, rename the block to rename one). When the block sets branch, commit, promote_from or
sync, env source, env branch and env sync refuse it too; change the
manifest instead. app plan lists, under Environments:, what saving a manifest would
create (+), change (~), or no longer declare (!); app show lists how the
environments differ from the saved manifest, including environments it no
longer declares, which keep running until env delete. app apply --deploy
deploys every declared environment that builds and waits for each; promoted
ones wait for env promote.
Each environment of a repository-backed application follows its own branch of
the application’s repository; the repository URL and manifest path stay on the
application (app repository url|path). env create --branch chooses the
branch, by default the one spec.manifest names, and --commit pins a commit.
env branch points an existing environment at another branch, or pins or
unpins (without --commit) a commit; nothing is redeployed until its next
deployment. Both fail with manifest_repository_required for applications
without a repository. env list and env show report the source as saved,
as the branch, e.g. main or main@<commit>, or as promoted from <name>.
Deploying on push is a repository
setting: app repository sync APP poll [--interval SECONDS] lists the branches
every interval (300 seconds by default), webhook when GitHub reports a push,
and off (the default) deploys only when asked; app repository connect takes
the same as --sync and --interval. Every preview, and every environment
that opted in with env sync APP ENV on and follows an unpinned branch, then
redeploys when its branch moves past the head of its last deployment; nothing
follows before its first deployment, and a one-off --branch/--commit
deployment stays until the branch moves. env sync … off opts it out again;
env sync needs no revision and asks no confirmation. Turning sync on, or
opting in while it is on, needs apps:deploy too.
notification manages notification destinations.
notification set NAME creates a destination, or changes one; options it
omits keep the current settings, and a new destination receives every category
but deployment_succeeded about every application, without previews. The URL is read from --url-file
or --url-stdin, never an argument, and is required to create one. --kind json|discord picks the payload, --enabled true|false pauses it,
--categories a,b replaces the categories it receives, and
--message CATEGORY=TEXT (repeatable) sends text with a category’s
notifications, such as a Discord role mention <@&ROLE_ID>, which also adds
the category; --no-message CATEGORY removes it. --apps a,b limits it to
those applications, by name or ID, --all-apps selects every one, and
--previews true|false chooses previews:
piquelctl notification set ops --kind discord --url-file discord-url
piquelctl notification set ops --message 'deployment_failures=<@&123456789012345678>'
piquelctl notification set ops --apps shop,notes --previews true
piquelctl notification test ops
notification test sends a test notification at once and fails unless the
receiver acknowledges it; list shows destinations without URLs, and delete
removes one (--yes skips confirmation). Changing destinations needs
notifications:manage, and listing them system:read or notifications:manage.
List the latest deliveries, newest first:
piquelctl notification deliveries --limit 20
Each line shows the delivery’s ID, destination, category, subject, state and
attempts, when it last changed, and its last error. A pending delivery shows
when it is next attempted instead. The page holds 50 deliveries unless you pass
--limit, up to 100. Pass the printed --cursor to read the next page.
Try a failed delivery again, with its destination’s current settings:
piquelctl notification retry <delivery-id>
Listing deliveries needs system:read, and retrying them system:operate.
The daemon refuses to retry a delivery that hasn’t failed, that its destination
no longer receives, or whose incident has since closed or opened again. It also
refuses a failure after its destination received the recovery that followed it.
The command then fails with delivery_not_retryable and exit code 3, and its
message says why. See notifications.
env stop APP [ENV] confirms, then stops an environment: the gateway
withdraws its routes at once and its services scale to zero replicas, keeping
their configuration, volumes and secrets. It waits until its runtime reports
stopped, unless --no-wait, fails if the deployment the stop runs again
fails, and ends early if the environment is started or deployed meanwhile. A stopped environment stays stopped until env start, env reconcile, a deployment, a restore, or a promotion into it; pushes don’t
deploy it meanwhile, and sync deploys the head it missed once it starts. env start APP [ENV] confirms, then starts it again, retrying its latest operation
like env reconcile and waiting for it. Stopping a deployment in progress lets
it finish with no replicas. Both need apps:deploy; --expected-generation
is optional, as for reconcile. env list shows a stopped environment’s state
as stopped, and env show says so. Previews are deleted instead.
env telemetry APP ENV on grants an environment the daemon’s
telemetry: its
receiving service, marked with app service telemetry APP SERVICE on, gets
the traces, metrics and logs of every application. It needs telemetry:grant,
which no preset includes. off revokes it, which stops export at once. Either
way the controller mounts or unmounts the socket within seconds, which
restarts the service; nothing is deployed, and env telemetry needs no revision
and asks no confirmation. Previews are never granted. env show reports whether
the environment is granted, which service receives it, and whether the daemon
is connected; status lists the receivers of the environments you can read
next to the [telemetry] endpoint.
app show reports the setting and the last check of the repository, with its
error; env show, preview show, and preview list report whether each one
follows pushes (following, off, off (pinned), off (not opted in),
off (stopped), off (waiting for a deployment)) and the branch head of its last deployment. app repository webhook show APP prints the payload URL to
configure in GitHub, once the daemon sets ingress.webhook_hostname, and
whether a secret exists; app repository webhook rotate APP confirms, then
generates a new secret, replacing the previous one, and prints it: it is never
shown again. events names sync deployments’ actor, sync:poll or
sync:webhook.
The manifest is shared; values that differ between environments come from
manifest variables. Environments that render
the same route hostname conflict with hostname_conflict, naming the sibling
environment that reserves it. env rename fails with environment_configured
while the manifest the environment deploys (for a repository-backed one, the one
last fetched from its branch) has a [spec.environments.<name>] block for the old
or the new name, since renaming would change which block applies: remove the
block, rename, then add it back under the new name. env show lists each
variable’s value in that manifest, or that it has none, and says when a
repository-backed environment has fetched nothing yet.
Each successful preparation of an environment’s deployment records an immutable release
(previews never record one):
the manifest it deployed (and its commit when repository-backed), each
service’s image (a registry digest, or the local image ID and commit of a Git
build), and the build inputs those images came from. Releases belong to the
application, so deleting an environment keeps them, and environments that
prepared the same content share one release. app releases lists them newest
first, twenty per page, with each service’s image and provenance, and whether
its images are still present: all present, missing registry images that
deploying it pulls again by digest, or missing builds, which mean it can’t be
deployed again (image cleanup keeps each environment’s last
images.keep_deployments successful deployments). --json adds the manifest
and build inputs. env show reports the release the
environment’s current target runs (none before its first prepared
deployment). Each release also lists the deployments that received it
by promotion or by release ID.
A promoted environment never
builds: it only receives releases from another environment of the
application. env create --promote-from ENV creates one, and
env source APP ENV --promote-from ENV converts an existing one (name or ID);
env source --tracking returns it to the saved manifest or the branch
spec.manifest names, and env branch to another branch. These deploy nothing,
take --yes, --force and --expected-generation like env branch, and fail
with promotion_cycle or promotion_source_invalid for an impossible source.
env delete of an environment others promote from fails with
promotion_source_in_use, naming them. env deploy and app deploy of a
promoted environment fail with environment_promoted.
env promote APP ENV deploys the release of the source environment’s current
deployment, after confirmation (--yes), and waits for it unless --no-wait.
--deployment ID refuses to promote anything but that deployment
(promotion_source_changed once the source moved on); --release ID deploys an
earlier release of the application with the environment’s current secrets.
--expected-generation N pins the application revision; by default the
inspected one is sent. It fails, before deploying anything, with
promotion_source_not_ready unless the source’s current deployment succeeded
and runs healthy now, release_incompatible, image_unavailable, or
secrets_unavailable listing every unusable secret. env source --promote-from
fails with application_busy while a deployment of the environment is in
progress.
environment_not_promoted means the environment builds its own source.
env promote --plan prints the release and its provenance and images, its
origin, the rendered changes, the runtime plan, new (empty) volumes, and each
unusable secret, without changing anything. It exits with the conflict code 3
when the plan is blocked, as it is by each missing image that can’t be pulled
again (image_unavailable), or lists secrets. --release ID --plan plans an
earlier release for any environment.
env history APP [ENV] lists an environment’s deployments, newest first, three
per page (--cursor for older ones): each one’s ID, outcome, where it came
from, its release, and which is the current target and the last successful
one. An automatic rollback shows as “rolled back from
deployment <failed> to deployment <restored>”.
env restore APP ENV DEPLOYMENT deploys an earlier deployment again exactly as
it ran: its rendered manifest, its images and the secret versions it pinned,
never newer ones. It is recorded as a new deployment restored from that one,
which becomes the maintained target, so drift repair keeps it. It works for any
environment, promoted ones included, and needs no application revision. It asks
for confirmation (--yes), then waits unless --no-wait, reporting like
env deploy. preview history and preview restore do the same for a preview.
A restore fetches and builds nothing, and never runs jobs: re-running an old deployment’s migrations against newer data is exactly what it must avoid. It does not revert data either. Volume contents and migrations that ran since stay as they are, so the restored code must cope with them; expand/contract migrations keep old code compatible.
env rollback APP ENV automatic|manual|default chooses whether a failed
rollout restores the environment’s last successful deployment on its own (see
rollback). default removes the setting: promoted
environments then roll back automatically, the others manually.
Before deploying anything, a restore fails with:
deployment_not_restorablefor a deployment that never succeeded.image_unavailable, naming the service and image, when an image is gone and can’t be pulled again by digest. A build is never rebuilt. Image cleanup keeps each environment’s lastimages.keep_deploymentssuccessful deployments, and only a preview’s current one.restore_secrets_unavailable, naming the secrets whose pinned versions were deleted or discarded by key recovery since.secret_access_deniedfor a stored secret whose access no longer allows the environment.
A preview is a disposable deployment of
one branch of a repository-backed application. preview commands address a
preview by stable ID, then slug, then branch with --slot for a slotted one;
env commands and env list never show or act on previews. preview create
creates and deploys the preview of --branch and --slot, or returns the
existing one and its latest deployment without redeploying it, and fails with
preview_requires_repository for other applications. preview deploy
redeploys the head of its branch, replacing a local snapshot the preview runs.
None of them needs the application revision.
preview create fails with preview_limit_reached when a new preview would
exceed the daemon’s [previews] limits, listing
the previews it counts with their branch, slot, slug and last deployment so you
can delete one; repeating an existing creation always succeeds.
preview list and preview show report each branch as exists, moved,
gone, local or unknown, read with git ls-remote; show also lists the
preview’s URLs, and each service its deployment runs with default limits or
fewer replicas than its manifest asks for. preview delete removes the preview and every volume it
created, after confirmation. preview prune --branch-gone lists the previews
whose branch is gone, confirms, and deletes them; the daemon checks each branch
again, keeps any it can no longer confirm gone, and deletes nothing while the
repository cannot be read. Previews whose branch state is unknown are kept,
with a warning. preview create, deploy, delete and prune wait for their
operations unless --no-wait; delete and prune confirm unless --yes.
Creating, deploying and logs need the apps:deploy and logs:read permissions
as for environments; deleting and pruning need apps:delete.
preview deploy <app> --local deploys the Git worktree of the current
directory without pushing it: its committed, uncommitted and untracked files,
except ignored ones. A tracked file that is ignored, such as a committed
.env template, is sent as committed, never with its local edits, even when
deleted. Before
uploading, it lists the uncommitted files it sends (Info, hidden with
--quiet), and the snapshot’s size and the commits it carries. It deploys to the preview of the current branch and
--slot, creating it first if needed exactly as preview create does, within
the same limits; on a detached HEAD, name the branch with --branch. The
snapshot is a commit on top of HEAD made with a temporary index and object
directory, so the branch, index, working tree, stash, refs and objects of the
repository never change. It is uploaded as a Git bundle, thin against the
nearest ancestor of HEAD that a remote-tracking branch has, which the daemon
fetches from the application’s repository: only unpushed commits are sent.
When there is no such ancestor, or the daemon cannot fetch it
(snapshot_base_unavailable), the command fails rather than send the whole
history; push the base, or pass --full-history to upload every commit of
HEAD. Snapshots whose files take more than the daemon’s
checkouts.max_bytes, or number more than
it accepts, are refused before anything is staged, naming the largest files,
and bundles over its snapshots.max_upload_bytes
before they are uploaded; the daemon refuses both with snapshot_too_large
too. Bundles Git cannot verify fail with snapshot_invalid. The daemon reads the
manifest from the snapshot at the application’s manifest path and builds
repository = "self" services from it, like any preview deployment, with the
same build priority and limits. Submodules, nested repositories, SHA-256
repositories, partial clones, and files staging would pass through a configured Git filter
driver, such as LFS’s, are refused, since those filters write into the
repository. A large first upload may need a longer --timeout; the daemon
gives up on a body that stalls for 30 seconds, or after 10 minutes
(snapshot_timed_out). Uploading needs previews:deploy, which apps:deploy includes.
A preview that runs or is deploying a local snapshot reports its branch as
local, with the branch’s head if it was pushed: preview prune never deletes
it, so delete it with preview delete. Deploy on push leaves the snapshot
running until the branch’s head moves, or, for a branch never pushed, until it
is pushed; then it deploys that head. preview show and the deployment history
show the snapshot commit and its base.
env url and preview url print the URL of every route the environment’s or
preview’s current deployment renders, with its route name and state: ready,
or pending with what it is waiting for, one line each, as env show,
deployments and wait list them:
URL: https://notes.example.com (web) ready
URL: https://admin.notes.example.com (admin) pending: HTTPS (No DNS-01 certificate yet)
--route NAME prints only the URL of the
named route, and warns on stderr while it is
pending. The daemon derives the state from what it already observes (see
URL readiness); the CLI never probes a URL itself, so a
URL that is known is not necessarily ready.
env wait and preview wait wait for one deployment: --deployment ID, or
the latest one when wait starts. They never follow a newer deployment. With
--ready runtime (the default), the deployment must succeed and its runtime
must be observed, with every service healthy (converged, with every desired
replica healthy); --ready routes also waits until every URL is ready. If a newer
deployment, or the environment’s deletion, supersedes it, wait exits 3 with
deployment_superseded; if it
fails, 5; at the command --timeout, 4, which ends only the local wait. Use a
longer --timeout than the default 30 seconds for builds. On success, wait
prints the deployment like env deploy does.
--socket PATH selects a Unix socket. --url URL selects an explicit
HTTP or HTTPS origin such as http://127.0.0.1:7845/; the two transport options are
mutually exclusive. The default socket is
/run/piqueld/piqueld.sock.
Remote IP addresses and DNS names are accepted. HTTPS is supported; embedded URL
credentials, non-root paths, queries, and fragments are rejected. DNS resolution
and connection setup share the request timeout; redirects are not followed.
Remote HTTP authentication, including login, requires --allow-insecure-http
when using separate transport encryption such as Tailscale. The client does not
verify tailnet membership or add encryption with this flag. Prefer HTTPS, such as
a daemon’s tailnet node at
https://piqueld.<tailnet>.ts.net. Loopback HTTP, including localhost and
its subdomains, and Unix sockets need no opt-in.
When the daemon runs a tailnet node, status also reports its login state,
certificate expiry, and whether auth.public_url matches the node. It also lists
each DNS provider with its zones, health and
whether it manages route records,
and each DNS-01 certificate with its hostnames, expiry and last error.
piquelctl dns refresh checks the providers’ credentials and zones now instead
of at the next hourly discovery, then prints the same DNS lines. status also
reports how many images this installation built that
image cleanup kept, and the total
size of those it removed since the daemon started.
status answers whether the daemon and its subsystems are up. Two more
read-only commands, both requiring system:read, show what the dashboard’s
Daemon status and Host settings pages do:
piquelctl resourcesshows the daemon’s resource measurements: uptime, resident memory, CPU usage, the database and its write-ahead log, free disk on the data directory’s filesystem, retained events, diagnostics and build output, running and queued operations, and notification deliveries awaiting a retry or failed. The daemon measures at most every 5 seconds and averages CPU usage since its previous measurement, so its first measurement has none. What the host cannot measure is unavailable, never zero.piquelctl settingsshows the daemon’s effective configuration, grouped as on the Host settings page. Credentials appear only as the file they are read from. Change settings in the daemon’s TOML and restart it. Each read is recorded in the audit trail.
Global --timeout DURATION defaults to 30s. Durations are positive integer
milliseconds (ms), seconds (s), minutes (m), hours (h), or days (d); a
bare integer is interpreted as seconds. The timeout bounds the complete command, except
login, which waits for browser approval, and an app exec or preview exec session; interactive
confirmation prompts do not consume it. Read requests can use the full remaining
command budget. Idempotent mutations retry one transport failure if time remains;
a request that exhausts the command budget cannot be retried.
Manifest files must be regular UTF-8 files no larger than 2 MiB, matching the
daemon’s API request body limit.
Use --json for machine-readable output. JSON is made only from public API
DTOs and the small CLI composition objects below; diagnostics and progress are
written to stderr, so stdout remains valid JSON.
| Command | JSON output |
|---|---|
profiles | { "profiles": [{ "name": string, "endpoint": string, "default": bool }] } |
status | SystemStatus |
resources | DaemonStats |
settings | HostConfiguration |
doctor | { "connection": { "endpoint": string, "endpoint_source": string, "credential": string }, "checks": [SystemCheck] or null }; checks is null when the daemon could not be asked |
dns refresh | DnsStatus |
login | User |
whoami | Session |
account list / account access / account update | { "accounts": [Account], "operator_sessions": [OperatorSessionView] } |
account invite / account enroll | { "url": string } |
account passkey list | [PasskeyView] |
account invitation list | [InvitationView] |
token create | { "token": string } |
token list | [CredentialView] |
logout / token revoke / account delete / account passkey rename|remove / account invitation revoke | None |
audit | { "items": [AuditEvent], "next_cursor": string or null } |
audit verify | AuditVerification |
setup-link | SetupLink |
sign-in-link / recover-admin | OperatorLink |
app list | { "items": [{ "application": ApplicationSummary, "environments": [EnvironmentRow] }], "next_cursor": null } |
app show | { "application": ApplicationView, "environments": [EnvironmentRow] } |
env list | [EnvironmentRow], where EnvironmentRow is { "environment": EnvironmentView, "status": EnvironmentStatusView or null } |
env show | EnvironmentDetailView (including its urls, links and telemetry), with "stored": [StoredSecret] |
env url / preview url | [RouteUrl] |
env url --route / preview url --route | RouteUrl |
env wait / preview wait | Deployment |
env create / env rename / env branch / env sync / env telemetry / env source / env stop | EnvironmentView |
app repository webhook show | WebhookView |
app repository webhook rotate | WebhookSecret |
env promote --no-wait | { "application_id": string, "environment_id": string, "deployment_id": string, "operation_id": string, "release_id": string, "origin": DeploymentOrigin }; the deployment and operation IDs are equal |
env promote | Same, with "outcome": OperationState and "operation": Operation |
env promote --plan | PlanView, with release |
app logs / env logs | ApplicationLogs |
app validate | { "application": string } |
app exec / preview exec | None; the command’s raw output |
app plan | PlanView |
app route list | [RouteStatus], each with "environment": string (its name) |
app file list | [ServiceFile], each with "service": string |
app service secret list | [SecretMount], each with "service": string |
app export list / app import list | [Export] / [Import], as saved |
app generated-secret list | [SecretDeclaration], as saved |
app secret list | [StoredSecret] |
app secret set / access | StoredSecret |
env secret list | [SecretMetadata] |
env secret regenerate | SecretMetadata |
app secret delete / env secret delete | { "deleted": string } |
secrets recover-key | SecretKeyRecovery |
notification list / set | [NotificationDestination] |
notification test | DestinationTest |
notification delete | { "deleted": string } |
notification deliveries | { "items": [NotificationDelivery], "next_cursor": string or null } |
notification retry | None |
builds list | { "items": [BuildRecord], "next_cursor": string or null } |
builds logs | BuildLogPage |
app create / app rename / field edits | SavedApplication; --deploy uses the same output as app apply --deploy |
app manifest | Saved TOML as a JSON string |
app releases | { "items": [ReleaseView], "next_cursor": string or null } |
app apply | SavedApplication with null operation_id and empty operation_ids |
app apply --deploy --no-wait | SavedApplication with the deployment operation IDs in operation_ids, the first also in operation_id |
app apply --deploy | { "saved": SavedApplication, "outcome": OperationState, "operation": Operation, "operations": [Operation] }; outcome and operation are the first deployment’s, operations every one |
app delete --no-wait | { "deleted": DeletedApplication, "volumes_retained": true } |
app delete | { "deleted": DeletedApplication, "outcome": "deleted", "volumes_retained": true } |
env delete --no-wait | { "accepted": AcceptedOperation, "volumes_retained": true } |
env delete | { "accepted": AcceptedOperation, "outcome": "deleted", "volumes_retained": true } |
preview create --no-wait | CreatedPreview and the fields of Deployment |
preview create | CreatedPreview and the fields of Deployment, with outcome and urls |
preview list | [PreviewView] |
preview show | PreviewView |
preview deploy | Same as env deploy |
preview deploy --local --no-wait | CreatedPreview and the fields of Deployment, with local true |
preview deploy --local | CreatedPreview and the fields of Deployment, with local true, outcome and urls |
preview logs | ApplicationLogs |
preview delete --no-wait | { "accepted": AcceptedOperation, "volumes_retained": false } |
preview delete | { "accepted": AcceptedOperation, "outcome": "deleted", "volumes_retained": false } |
preview prune | [DeletedPreview] |
operation --no-wait | Operation |
operation | Operation |
app/env reconcile / start / deploy | { "accepted": AcceptedOperation, "operation": Operation } and the fields of Deployment, with outcome and urls |
app/env reconcile --no-wait / stop --no-wait / start --no-wait / deploy --no-wait | AcceptedOperation (operation_id, environment_id, generation) and the fields of Deployment |
events | { "items": [Event], "next_cursor": string or null } |
analytics | DeploymentAnalytics |
Deployment is the CLI’s summary of one deployment of an environment or
preview, flattened into the results above:
| Field | Value |
|---|---|
application_id, environment_id | Stable IDs |
slug | The environment’s name, or the preview’s slug (env.slug) |
deployment_id, operation_id | The deployment, and the operation that runs it (the same ID) |
release_id | The release it runs; null until prepared, and for previews |
branch, commit | Where its manifest was read from; null until fetched, and for saved manifests. For a local snapshot, the snapshot commit |
local | Whether it deploys a snapshot of a local worktree (preview deploy --local) |
base | The commit of the application’s repository a local snapshot was uploaded against; null when it was uploaded whole, and for other deployments |
outcome | Its OperationState; omitted with --no-wait |
urls | [RouteUrl] of its routes once it runs; omitted with --no-wait and when it does not run (superseded) |
The DTO fields are defined by the versioned API and the piqueld-client crate.
With --json, stderr is one JSON object per line too, keyed by its kind:
{"info":{"message":…}}, {"warning":{"message":…}},
{"progress":{"task":…,"message":…,"outcome":…}} (outcome once the task
finished), {"prompt":{"message":…}} for a confirmation asked on a terminal,
and, when the command fails, exactly one
{"error":{"code":…,"message":…,"details":…,"request_id":…}}. An API error keeps the
daemon’s error envelope: its code, message, details and request_id.
Failures the CLI detects itself use the same shape with a stable CLI code and
no request_id:
| Code | Exit | Meaning |
|---|---|---|
usage | 2 | Invalid arguments |
invalid_input | 2 | Invalid input, an unknown name, or an unconfirmed action |
invalid_configuration | 2 | Invalid connection configuration; details.configuration_source |
invalid_endpoint | 2 | A rejected endpoint; details.endpoint_source |
connection_failed | 4 | The daemon could not be reached; details.endpoint and endpoint_source |
request_timeout | 4 | A request exceeded --timeout; also details.timeout and timeout_source |
timeout | 4 | The command exceeded --timeout; server-side work goes on |
unavailable | 4 | The daemon stopped answering, e.g. an app exec stream ended before the command exited |
invalid_response | 1 | The endpoint does not serve the piqueld API |
conflict | 3 | An ambiguous name or a blocked plan |
deployment_superseded | 3 | wait’s deployment was superseded; details.deployment_id and superseded_by |
operation_failed | 5 | An awaited operation failed; details.operation |
checks_failed | 6 | doctor found a failed installation check |
interrupted | 130 | Ctrl-C |
failed | 1 | Any other failure |
Without --json, errors stay human-readable, as below.
Agents
Agents drive environments and previews with --json: stdout is one result
document, stderr one JSON event per line, and the exit code says what
happened. A typical run creates, waits, reads the URL and logs, then deletes.
For a preview of a branch:
created=$(piquelctl --json preview create notes --branch feat/login --slot agent-2 --no-wait)
slug=$(jq -r .slug <<<"$created")
deployment=$(jq -r .deployment_id <<<"$created")
piquelctl --json --timeout 15m preview wait notes "$slug" --deployment "$deployment" --ready routes
url=$(piquelctl --json preview url notes "$slug" --route web | jq -r .url)
piquelctl --json preview logs notes "$slug" --service web --tail 100
piquelctl --json preview delete notes "$slug" --yes
To test uncommitted work without pushing it, from the worktree:
deployed=$(piquelctl --json preview deploy notes --local --slot agent-2 --no-wait)
slug=$(jq -r .slug <<<"$deployed")
piquelctl --json --timeout 15m preview wait notes "$slug" --deployment "$(jq -r .deployment_id <<<"$deployed")" --ready routes
Its result has local: true, the snapshot commit and its base. Run it
again after each change; each run deploys a new snapshot to the same preview.
For an environment, which env create adds without deploying:
piquelctl --json env create notes staging --branch main --yes
deployment=$(piquelctl --json env deploy notes staging --yes --no-wait | jq -r .deployment_id)
piquelctl --json --timeout 15m env wait notes staging --deployment "$deployment" --ready routes
url=$(piquelctl --json env url notes staging --route web | jq -r .url)
piquelctl --json env logs notes staging --service web --tail 100
piquelctl --json env delete notes staging --yes
Passing the deployment_id from the deploy or create result makes wait
follow exactly that deployment, even if someone deploys again in between.
Without --no-wait, deploy and create wait for the deployment
themselves and return its outcome and urls, which can still be pending:
wait --ready routes is what waits for them to be ready.
Branch on the exit code, then read the error event on stderr:
| Exit | Meaning | Typical reaction |
|---|---|---|
| 0 | Done; for wait, as ready as asked | Continue |
| 2 | Invalid command or input (usage, invalid_input, API validation codes, snapshot_too_large, snapshot_invalid) | Fix the command; retrying will not help |
| 3 | Conflict: deployment_superseded, preview_limit_reached (its details list the previews to delete), a revision precondition | Read the newer state; wait for details.superseded_by, or delete a preview |
| 4 | Unavailable or timed out (connection_failed, timeout) | Retry the read or wait; server-side work was not cancelled |
| 5 | The deployment failed (operation_failed; details.operation names the phase and error) | Read logs, fix, deploy again |
| 6 | doctor found a failed check (checks_failed) | Follow each failed check’s next_step |
Installation checks
piquelctl doctor shows how this CLI connects (the endpoint, where it came
from, and which credential it sends), then asks the daemon to check its
installation (GET /api/v1/system/checks, which needs system:read):
| Check | Passes when |
|---|---|
| Public URL | auth.public_url is HTTPS and its /health answers piqueld’s liveness response from the daemon’s host with a trusted certificate |
| Tailnet node | The daemon’s node is logged in, holds a valid certificate, and serves auth.public_url |
| Docker Engine | Docker answers; with ingress enabled, it is Engine 28+ with API 1.48+ |
| Ports 80/443 | With direct ingress, they are held by the running managed Caddy gateway, or nothing on the host listens on them |
| Secret key | secrets.key is a private file matching the stored verifier, or not needed yet |
| API socket | The runtime directory passes startup’s checks, and the socket is 0660 with the daemon’s group |
| Backups | Always skipped: piqueld does not take backups yet |
Each check is pass, warn, fail, or skip (not applicable, such as the
tailnet node while [tailscale] is disabled), with a next step when action is
needed. doctor exits 6 when any check fails; warnings keep exit 0. Checks
never change the installation. doctor makes a single request bounded by
--timeout; when it fails, the connection is still reported, with checks
null in JSON, before the error.
The checks run where the daemon runs. A reachable public URL means the
daemon’s host reaches it; browsers elsewhere also need DNS and network access,
and may reach it when the host cannot, such as without NAT hairpinning.
Likewise, doctor reaching the daemon proves this CLI’s connection, not the
browser’s.
Connection failures
Use piquelctl status to check whether the selected endpoint responds with the
piqueld API, or piquelctl doctor for the resolved connection and installation
checks. Connection diagnostics are available on failures from every command.
Failures identify the effective endpoint and its source (flag, environment, profile and file, or built-in default), preserve the observed cause, and suggest a check. Timeout failures also show the effective timeout and its independent source. For example:
Error: piqueld API request failed: opening connection: No such file or directory (os error 2)
Endpoint: Unix socket /tmp/piqueld.sock
Endpoint source: flag --socket
Hint: Check the socket path and whether the daemon has created its socket.
Configuration failures identify the source that needs attention; they do not claim an effective endpoint before resolution succeeds. Rejected URLs and TOML source excerpts are omitted because they can contain credentials. Malformed profile files report a location and a safe error category instead.
These diagnostics use the original request and resolved configuration. They do
not probe other targets, inspect permissions, or check database, Docker, or Swarm
readiness; doctor checks the installation. Unexpected HTTP responses and invalid API data include connection
context; ordinary application errors keep their existing reporting. With
--json, the same facts are the error’s details (see the JSON errors above);
with --quiet, diagnostics stay human-readable. Exit codes and successful
output are unchanged.
Field editing
Application commands live under piquelctl app. Manifest import (app apply)
and planning (app plan) remain available, but ordinary editing sends only the
selected setting.
piquelctl app create notes --yes
piquelctl app service add notes web nginx:stable --yes
piquelctl app service replicas notes web 3 --yes
piquelctl app service env set notes web RUST_LOG debug --yes
piquelctl app service env remove notes web RUST_LOG --yes
piquelctl app service command notes web --yes -- /usr/bin/server
piquelctl app service arguments notes web --yes -- --listen "0.0.0.0:8080"
piquelctl app service depends-on notes web --yes -- postgres
piquelctl app service rollout notes worker --order stop-first --monitor-seconds 60 --yes
piquelctl app service telemetry observability collector on --yes
piquelctl app service add notes worker --git https://example.com/app.git --branch main --yes
piquelctl app service source git notes web https://example.com/app.git --target runtime --build-arg ORIGIN=https://notes.example.com --yes
piquelctl app service source git notes web https://example.com/app.git --branch main --yes
piquelctl app service source git notes web self --dockerfile build/Dockerfile --yes
piquelctl app service source branch notes web release --yes
piquelctl app service source commit notes web --clear --yes
piquelctl app service source dockerfile notes web build/Dockerfile --yes
piquelctl app service source context notes web build --yes
piquelctl app service source image notes web nginx:stable --yes
piquelctl app service cpu notes web 500 --yes
piquelctl app service memory notes web 268435456 --yes
piquelctl app service cpu notes web --clear --yes
piquelctl app volume add notes data --yes
piquelctl app service mount set notes web data /var/lib/data --yes
piquelctl app service mount remove notes web /var/lib/data --yes
piquelctl app service secret add notes web database-password /run/secrets/database-password --yes
piquelctl app service secret add notes web '${{ vars.stripe_key }}' /run/secrets/stripe --yes
piquelctl app service secret list notes web
piquelctl app service secret remove notes web /run/secrets/stripe --yes
piquelctl app file add notes web /etc/app/motd --content hello --yes
piquelctl app file add notes web /etc/prometheus/prometheus.yml --from ./prometheus.yml --yes
piquelctl app file add notes web /etc/otelcol/config.yaml --path otel/config.yaml --mode 0440 --yes
piquelctl app file list notes web
piquelctl app file remove notes web /etc/app/motd --yes
piquelctl app volume remove notes data --yes
piquelctl app route add notes notes.example.com web 3000 --visibility public --yes
piquelctl app route add notes admin.notes.example.com admin 8080 --yes
piquelctl app route add notes '*.tenants.notes.example.com' web 3000 --yes
piquelctl app route redirect notes www.notes.example.com https://notes.example.com --visibility public --yes
piquelctl app route visibility notes admin.notes.example.com private --yes
piquelctl app route name notes notes.example.com web --yes
piquelctl app route list notes
piquelctl app route remove notes notes.example.com --yes
piquelctl env visibility notes staging private --yes
piquelctl app job set notes migrate web --timeout-seconds 600 --yes -- notes migrate
piquelctl app job set notes migrate web --volume-access exclusive --yes -- notes migrate
piquelctl app job set notes migrate web --stop-service on --yes -- notes migrate
piquelctl app job set notes migrate web --env OWNER_PASSWORD_FILE=/run/secrets/owner --secret owner-password=/run/secrets/owner --yes -- notes migrate
piquelctl app job move notes migrate 1 --yes
piquelctl app job remove notes migrate --yes
piquelctl app export set auth api --applications notes,billing --yes
piquelctl app export set auth api --applications notes --previews --yes
piquelctl app export list auth
piquelctl app export remove auth api --yes
piquelctl app import add notes auth api --yes
piquelctl app import add notes auth api --environment staging --yes
piquelctl app import list notes
piquelctl app import remove notes auth api --yes
piquelctl app variable set notes domain piquel.fr --yes
piquelctl app variable set notes domain staging.piquel.fr --env staging --yes
piquelctl app variable set notes web_replicas 3 --env production --yes
piquelctl app variable unset notes web_replicas --env production --yes
piquelctl app service replicas notes web '${{ vars.web_replicas }}' --yes
piquelctl app service health http notes web 8080 --path /live --check-timeout 3 --yes
piquelctl app service health interval notes web 20 --yes
piquelctl app service health start-period notes web 60 --yes
piquelctl app service health clear notes web --yes
piquelctl app service replicas notes web 2 --deploy --yes
piquelctl app manifest notes
Every edit saves to the daemon’s internal manifest by default. --deploy saves
and captures a deployment atomically, then waits for it; add --no-wait to return
its receipt immediately. No local manifest file is read or rewritten. The server
validates the complete result, so removing a mounted volume is rejected until its
mounts are removed. Removing declarations retains Docker volume data.
In a repository-backed application, pass self instead of a Git URL, as in
source git notes web self or service add notes worker --git self, to build
the manifest’s own repository. It builds the
commit the manifest was read from, so it takes no --branch or --commit.
app file add adds a read-only file, or
replaces the one at the same target: --content sets its text, --from reads
a local file now and saves its content inline, and --path names a file in the
manifest’s repository, read by each deployment. Repository-backed applications
are edited in Git instead. app file list shows each service’s saved files.
app service secret add mounts a secret as a file under /run/secrets, or
replaces the mount at the same target; its name may reference variables, so
each environment can mount its own stored secret. app service secret remove
unmounts it by target, and app service secret list shows each service’s
saved mounts. Values and who may mount them are managed with app secret.
Route edits preserve other routes and use the inspected generation to reject
concurrent changes. route add and route redirect take an exact hostname or a
wildcard such as *.tenants.notes.example.com,
which serves every name one label below it; quote it for the shell. route redirect defaults to status 308 and appends the
request path and query to the destination; use --status and
--no-preserve-path to change that. Routes are private (tailnet only) unless
added with --visibility public; route visibility changes an existing route,
route add --name and route redirect --name name a new route, route name
names an existing one (or removes its name without NAME),
and env visibility caps every route of one environment at private (or lifts
the cap with public) in the saved manifest. Deploy after saving to activate or
remove routing. route list shows each deployed route with its name, environment,
effective visibility, state, destination, whether piqueld
manages its DNS records (manual, managed,
pending or dns_conflict) and the records its hostname needs, followed by the
cause while it is not ready. piquelctl status also shows the
public and private listeners: the ingress mode (ports 80/443, or the Cloudflare
Tunnel’s ID and whether it is connected), and the apps node’s name, state and tailnet
addresses. It also counts the previews against their limits, installation-wide
and for each application you may read, flags a count left over a lowered limit,
and sums the CPU and memory limits of the previews’ deployed replicas.
job set adds a job after the existing ones, or replaces the job with that name
in place, keeping its timeout, volume access and whether it stops its service
unless --timeout-seconds, --volume-access or --stop-service is given
(300 seconds, read-only and off for a new job).
--env KEY=VALUE and --secret NAME=TARGET, repeated for several, set the
environment variables and secret files only the job’s container gets, added
to its service’s; given, they replace the job’s own, which are otherwise kept.
--clear-env and --clear-secrets remove them.
read-only mounts the service’s volumes read-only; exclusive keeps writable
volumes writable and stops every service mounting them until the rollout.
--stop-service on stops the job’s own service before it runs, until the
rollout. job move sets a job’s 1-based position in the run order. Jobs run in
their saved order before each rollout; like routes, job edits
preserve the other jobs and use the inspected generation.
Each job inherits its referenced service’s depends_on: those services and
their transitive dependencies start or update and pass health checks before
the job runs. Configure dependencies with app service depends-on. Other
services wait for all jobs to succeed; after a failed job, repair rolls its
dependencies back and restarts the services the job stopped. Jobs of dependency services must come earlier in the saved order.
export set lets the listed applications reach a service through a
link, replacing who may, and whether previews
may, if it was already exported; --previews lets their previews too.
import add reaches another application’s exported service as
<service>.<application>, in the environment named like each importing one
unless --environment names one (it accepts ${{ vars.<name> }}); adding an
import again replaces its environment. Like jobs, link edits preserve the others and
use the inspected generation. env show lists each import of the environment
and whether it connects, and the environments of readable applications
importing its services.
app variable set sets a variable’s default, or with --env NAME its value in
the environment named NAME, which must exist (an edit never declares a new
environment, so an unknown name fails with environment_unknown; one that adds
the first [spec.environments.<name>] block, like env visibility … private,
declares every existing
environment), or
with --previews its value in every preview ([spec.previews.variables]). An
environment’s or the previews’ value overrides the default, and a variable
needs no default.
true, false and integers keep their type and anything else is text; --string
keeps text such as 3 as text. app variable unset removes the default, the
environment’s value, or the previews’ value. Like routes, variable edits preserve the other variables and
use the inspected generation. Settings that accept variables, such as replicas,
limits, health check settings, environment values, commands and images, take a
${{ vars.<name> }} reference in place of a literal; quote it for the shell.
app show lists the defaults and per-environment and preview values.
Previews only exist for repository-backed applications, whose configuration is
edited in their repository, so [spec.previews] is set there.
app service rollout replaces the service’s rollout block: an omitted --order
derives the order from the mounts, and an omitted --monitor-seconds uses 30
seconds, so running it without flags restores the defaults. app plan lists each
service’s effective order and monitor window, and warns when start-first is set
on a service with a writable volume.
app plan --env NAME renders the manifest for that environment: its changes
show rendered values, and it lists the value of every variable in scope, such as
vars.domain and env.name. A reference without a value fails the plan with
variable_value_missing. Without --env, the application’s only environment is
used; with several, the plan compares the saved manifest as written and says
that runtime actions are not planned. Likewise, a plan that must first resolve or
build sources says that network, volume and service changes are left to the
deployment; it still lists source resolution and declared jobs. Plans list,
under Environments:, how saving the manifest would change the environments it
declares.
Command, argument, job command, and depends-on arrays preserve individual elements; place
command options before --. An empty array clears the setting. Optional limits and pinned
commits use --clear; health checks use health clear. Nested Git/health settings
require the corresponding source/check to be configured first. Use --help on
any command for its values and options.
Git-backed applications are edited like any other: field edits and app apply
change the saved manifest, which deployments of the connection’s branch deploy
until a push changes the manifest there (see
editing).
app show lists the edits,
and app repository reset discards them, saving the manifest last read from the
repository again. Only app rename fails with repository_managed, since the
fetched manifests must keep the name. Connecting points every environment at
--branch (default main), and each environment’s branch then changes with
env branch. Disconnecting retains the saved manifest, edits included, and also
drops declared environments’ branch, commit and sync, returning them to
the saved manifest:
piquelctl app repository connect notes https://example.com/infra.git infra/app.toml --yes
piquelctl app repository path notes corrected/app.toml --yes
piquelctl app repository sync notes poll --interval 120 --yes
piquelctl env branch notes production release --yes
piquelctl app repository reset notes --yes
piquelctl app repository disconnect notes --yes
Running commands
app exec runs a one-off command in a running task of a deployed service, for
administrative work such as creating an invitation or opening a console:
piquelctl app exec piquel-fr auth -- auth-service invite create
piquelctl app exec piquel-fr db -i -- psql < dump.sql
piquelctl app exec piquel-fr auth -it -- /bin/sh
piquelctl preview exec piquel-fr feat/login auth -it -- /bin/sh
It runs in the application’s only environment; with several, --env ENV names
one. preview exec runs it in a preview instead, addressed like preview logs. The command runs inside the task, so it shares the task’s image, environment,
secrets, mounts and networks. It works over every transport, uses account
authentication and needs no SSH access; the account (or token) needs
apps:exec on the application. Output streams to stdout and stderr, and
piquelctl exits with the command’s exit code. -i forwards standard input.
-t allocates a terminal, implies -i, requires a terminal on standard input
and puts it in raw mode, so Ctrl-C reaches the command. --timeout bounds the
WebSocket handshake, not the session.
The service needs a running task; otherwise the command fails with
service_not_running. The environment’s history records command_started and
command_finished events with the account, task and exit code, never the
command. Ending piquelctl early closes the stream, but the command may keep
running in the task. The Terminal tab of an environment or preview in the
dashboard runs commands the same way.
Mutation safety
app apply saves configuration only. app apply --deploy saves and deploys atomically.
Both inspect the application identity and saved revision before confirmation;
neither requires a runtime plan or Docker availability. Use app plan separately
to inspect redacted changes and image resolution requirements. Planning may fail
when Docker observation is unavailable.
Mutating commands require TTY confirmation unless --yes is supplied; env sync,
env telemetry,
app secret access, preview create and preview deploy do not ask. Apply,
delete, rename, field edits, and env create, rename, branch and source
accept --force independently of confirmation. Every explicit
deployment creates a new snapshot, refreshes image references and supersedes pending
work. Completed deployments retain their terminal state in history.
Reconciliation retries the deployment snapshot using prepared digests.
Unchanged healthy containers do not restart unnecessarily.
Deletion removes application configuration and all its history after runtime
resources are absent. Environments’ named Docker volumes remain, which output
reports as volumes_retained: true; previews’ volumes are removed with their
data. The CLI waits for application absence because deletion
also removes its operation record.
For apply, delete, rename, and field edits, the CLI automatically sends the revision it
inspected before confirmation. Apply also sends the inspected application ID,
or generation zero for create-only. --expected-generation N supplies an explicit
revision for scripts. Conflicts stop the command without adopting the newer
revision. --force skips these preconditions: forced apply targets whichever
application currently has the name, or creates it if absent. Validation, resource
ownership, and rename busy/name-collision checks still apply. app show reports intent
and active target generations and separate runtime health.
The CLI retains one automatic transport retry, using the same command UUID in
Idempotency-Key. SQLite receipts replay the original acceptance response for
24 hours across daemon restarts. Replays do not restart failed or superseded work;
a separately invoked command gets a new UUID. This includes forced requests:
a transport retry cannot overwrite changes accepted after the original command.
Rename checks the inspected generation and name availability. It rejects pending
or running operations and deletion intent. It preserves identity, services,
networks, and volumes. It saves without deployment unless --deploy is given. A changed name
advances generation and records an event. Update metadata.name in your manifest
file afterward; the CLI does not edit files automatically.
events reads one page, oldest first. --application ID optionally filters by
stable application ID: application-wide events such as edits and renames, and the
events of all its environments, including deleted ones. --environment ID filters
by stable environment ID. Deleting an application removes its history. Use --cursor CURSOR for subsequent
pages and --limit N (1–100, default 50). JSON includes the next cursor.
analytics summarizes deployment analytics,
as the dashboard’s Analytics page does: deployments by the outcome of their
latest attempt, the mean attempt duration, failed and retry attempts, action
retries, mean durations per action phase, and the most frequent failure codes.
It covers every application the caller holds events:read on, or, with
--app, that application’s only environment; --env names one when it has
several. --since DURATION starts the window that long ago (default: 30 days
before its end) and --until DURATION ends it that long ago (default: now),
so --since 14d --until 7d covers the week before last. A warning reports when
the window reaches before retained history.
By default, apply with --deploy, delete, reconcile, deploy, and operation poll every 250 ms
until a terminal state. Supersession returns immediately with exit code 0 and
outcome: "superseded" in mutation command results (state: "superseded" on an
operation record). It does not wait for the replacement to deploy. An observed
failed attempt returns failure even if the controller will retry automatically.
--no-wait returns immediately. For long image pulls,
use a longer --timeout or return immediately and inspect the operation later.
Pressing Ctrl-C ends the local command, including confirmation and network
requests. It does not cancel an accepted server-side operation, which can still
be inspected with piquelctl operation <id>.
The commonly useful exit codes are 0 for success or supersession, 1 for a general error, 2 for
usage or input errors, 3 for conflicts, 4 for unavailable or timed
out requests, 5 for a failed operation, and 130 when local operation waiting is
interrupted. wait uses the same codes, except that supersession is a
conflict (3): it waits for one deployment, so another one running is not success.
The dashboard provides application management forms and recent application logs. Remote registry management and advanced interactive CLI flows remain future work.
Use piquelctl setup-link on the daemon host to print the first-account setup
link (--open also opens it in the default browser); only the Unix socket serves
it. Use piquelctl login for passkey login through the browser, whoami to inspect
the current account and its grants, and logout to revoke it. On the daemon host,
root and the daemon’s own user need no login: without a token, they act over the
Unix socket as the host operator, with
admin on every application, and whoami prints host operator (uid 0). Run
that way, piquelctl sign-in-link prints a one-time link (valid for 10
minutes) that signs a browser in as the host operator for 12 hours; it raises a
security notification. piquelctl account
lists accounts (and live host operator browser sessions), replaces their grants
(access), edits (update, which also renames your saved login) and deletes
(delete) them, and creates invitation
(invite) and passkey enrollment (enroll) links; account passkey list|rename|remove manages passkeys, and account invitation list|revoke
pending links. piquelctl token create|list|revoke
manages API tokens for your account (--tailnet binds one to a tailnet user
or tag); token list --user and token revoke --all [--user] list and revoke
every session and token of an account. login accepts the same grant options
to ask for a limited session; see ask for less access.
piquelctl audit reads the audit trail, one line
per request with the application or environment it addressed, filtered
by --user (a username, or any account ID, including a deleted account’s),
--credential, and --outcome; piquelctl audit verify checks its
hash chain. When no administrator can sign
in, sudo piquelctl recover-admin on the daemon host prints a one-time link
that creates a new administrator; see
recovering administrator access. Saved credentials are separate
from profiles. PIQUELD_TOKEN supplies an automation token; --account selects
a saved account. See signing in from the CLI for details.
app deploy and env deploy fetch a repository-backed environment’s manifest
from its branch, then explicitly resolve image or Git build sources. They
supersede pending work for the selected environment. Use --yes to skip
interactive confirmation, --no-wait to return after acceptance, or a longer
global --timeout for builds. --branch NAME or --commit SHA fetches a
repository-backed manifest from another revision for this deployment only,
without changing the environment’s branch; for example, to test a feature
branch. Once the deployment finishes, they warn about anything it reported, such
as manifest_connection_ignored when the fetched file’s spec.manifest names
another repository URL or path. The
server continues deployment if the CLI wait times out. app reconcile and
env reconcile retry or repair the latest
deployment snapshot and prepared target without refreshing sources.
Human output uses bold labels and color on terminals, application lists,
and elapsed operation progress. Redirected output stays plain and NO_COLOR
disables color. --quiet suppresses human results, information, and progress while
preserving warnings, errors, and explicit JSON results. --noninteractive refuses prompts
even on a terminal; destructive commands still need --yes.
Output is routed through channels configured once at startup:
| Role | Stream | Quiet behavior |
|---|---|---|
| Result | stdout, human or one typed JSON document | Human hidden; JSON retained |
| Information | stderr, routine guidance | Hidden |
| Warning | stderr, incomplete results or actionable caveats | Retained |
| Progress | stderr, task transitions and outcomes | Hidden |
| Prompt | stderr, an authorized interactive question | Retained |
| Error | stderr, final failure with context and hints | Retained |
Every event is rendered and flushed as it occurs; output is not collected until
the command ends. Terminal progress uses independently tracked task rows and
permanent completion lines. Redirected progress prints meaningful transitions,
without repeated identical updates. JSON remains a single result document, not
a progress stream; with --json, stderr events are JSON lines. Paginated results
can collect typed records before that document is emitted. If an environment’s
status cannot be fetched during app list, app show or env list, its status
is null (unavailable in human output) and a contextual warning is emitted
immediately; other environments are still returned.
Dynamic human-readable values escape terminal control characters. Logs preserve newlines and tabs, but escape other controls. Build logs additionally use the shared ANSI/control cleanup before human rendering. JSON retains original values. Result, information, warning, and prompt write failures fail the command; progress and final error reporting are best effort.
Internally, command handlers emit typed Report values through one Console;
they do not branch on --json or --quiet. Reports define their JSON type and
stream human rendering through HumanWriter. Contextual errors and warnings
share a borrowed diagnostic renderer, whose --json form is one stderr event;
every error is one CliError with one JSON serialization. Task handles may be cloned across workers,
but ordinary console writes remain serialized. Only explicit task completion
prints an outcome; dropping the last unfinished handle clears its transient row
without claiming the server-side operation was cancelled.
piquelctl app logs NAME_OR_ID [--service NAME] [--tail 200] [--since-seconds 3600]
(or env logs APP [ENV] with the same options) reads a recent Docker snapshot with timestamps, service, task, and stream labels.
--json returns the structured records and a truncation indicator.
Connection profiles
Select a named connection with --profile NAME or PIQUELD_PROFILE.
On Linux and macOS, the CLI loads /etc/piqueld/profiles.toml, then
$XDG_CONFIG_HOME/piqueld/profiles.toml (otherwise $HOME/.config/piqueld/profiles.toml).
User profiles replace entire system profiles with the same name; fields are not
merged. Other system profiles remain available. Discovery happens at runtime,
so installed and development binaries use the same configuration.
--profiles-file PATH takes precedence over PIQUELD_PROFILES_FILE. Either
explicit selection loads only that file, bypassing system and user discovery.
Missing automatically discovered files are ignored. Explicitly selected missing
files and all unreadable or malformed files are errors, with the file path.
Surviving profiles are validated after replacement; invalid entries report their
name and source file, even when they are not selected.
When neither --profile nor PIQUELD_PROFILE selects a profile, the CLI uses
the one named by the optional top-level default, else an optional
[profiles.default]. A later file’s default replaces an earlier one, and it
may name a profile from either file. A default or an explicitly selected
profile that does not exist is an error.
default = "testing"
[profiles.testing]
socket = "/tmp/piqueld-dev-run/piqueld.sock"
timeout = "2m"
[profiles.local-http]
url = "http://127.0.0.1:7845"
Profiles contain exactly one socket or url, plus an optional timeout.
Precedence is explicit flags, then PIQUELD_SOCKET / PIQUELD_URL /
PIQUELD_TIMEOUT, then profile settings, then built-in defaults. A transport
override replaces the entire profile transport. Simultaneous environment socket
and URL values are rejected unless an explicit transport overrides them.
Authentication and credentials are not profile settings yet.
piquelctl profiles lists the effective profile names and endpoints in alphabetical
order after file selection and replacement, marking the default. It does not contact a daemon or check
endpoint reachability, and ignores --profile, PIQUELD_PROFILE, and connection
overrides. Only configured profiles appear; the built-in local connection is not
an additional profile.
NAME ENDPOINT
dev /tmp/piqueld-dev/piqueld.sock
prod http://127.0.0.1:7845 (default)
piquelctl profiles --json returns
{"profiles":[{"name":"dev","endpoint":"/tmp/piqueld-dev/piqueld.sock","default":false}]}.
An empty list prints No profiles configured. in human mode, or
{"profiles":[]} in JSON.
--quiet suppresses human results, information, and progress, but preserves JSON,
warnings, errors, and authorized prompts.
piquelctl builds list [--application ID] [--environment ID] [--cursor CURSOR] lists one
page of build attempts and job runs, optionally of one application’s environments or
of one environment. piquelctl builds logs ID [--before BYTE_OFFSET] reads the newest bounded
output page, or an older page before the supplied cursor. It prints the cursor
for loading older output when available. Both support --json.
Manually set secrets live in the application’s secret store and are write-only. Each lists the environments that may mount it, by default every environment, including ones created later, and no previews:
piquelctl app secret notes list
piquelctl app secret notes set database-password --file ./password --yes
printf '%s' 'new-value' | piquelctl app secret notes set stripe-live --stdin --environments production --yes
piquelctl app secret notes access stripe-live --environments production,staging --previews
piquelctl app secret notes access stripe-live --all-environments --no-previews
piquelctl app secret notes delete database-password --yes
Services mount these secrets with app service secret add.
list shows each secret’s version and who may mount it, naming environments.
--environments a,b (names or IDs; --environments '' for none) or
--all-environments sets the environments, and --previews or --no-previews
whether previews may mount it; --all-environments never includes previews.
Unset flags keep the current access. Environments
are stored by ID, so renaming one keeps its access and deleting one removes it.
Deploying, or app plan --env, for an environment or preview that mounts a
secret it may not use fails with secret_access_denied.
Prefer protected files or a secure stdin producer over literal shell values in
real use. --file stores the file exactly as it is. --stdin drops one
trailing newline, so echo value | stores value. From a terminal,
--stdin asks for one line and doesn’t echo it. --expected-generation pins a write to inspected metadata; otherwise
the CLI reads the current generation before confirming. --json returns only
metadata. Replacement creates a new version for a later Deploy and does not
change running deployments. Deletion refuses secrets that any environment’s
saved configuration or runnable deployment still uses.
Manifests can declare secrets that piqueld generates for each environment when
a deployment first mounts them instead; see
application manifests. Declare them with
app generated-secret, like other saved settings:
piquelctl app generated-secret set notes database-password --bytes 32 --yes
piquelctl app generated-secret set notes signing-key --rsa-bits 3072 --yes
piquelctl app generated-secret list notes
piquelctl app generated-secret remove notes signing-key --yes
Changing or removing a declaration never changes values already generated.
Generated values are listed, regenerated and deleted per environment. regenerate creates a new version for the next
deployment, to rotate a value or replace one discarded by key recovery; running
deployments keep theirs. delete removes an unused one:
piquelctl env secret notes --env staging list
piquelctl env secret notes --env staging regenerate database-password --yes
piquelctl env secret notes --env staging delete database-password --yes
env show lists each secret the environment mounts and where its value comes
from: generated for the environment, the application store (with its version),
missing, not allowed by the secret’s access list, or discarded by key recovery,
which would fail the next deploy until replaced.
If the daemon’s secrets.key is lost and no backup exists, recover by discarding
stored and generated values across ALL applications and environments:
piquelctl secrets recover-key --yes
The command requires confirmation (--yes for automation), refuses while the
current key still works, and reports affected environment, secret and version
counts. Running Docker services are left alone. app secret APP list and
env secret APP list mark discarded values as unavailable; set replacement
stored values using the same names, then Deploy explicitly, which also generates
new values for discarded generated secrets.