Skip to main content
The Tinfoil CLI lets you manage projects and individual instances, along with model weights, secrets, SSH keys, registry credentials, and custom domains. This page walks through the full instance lifecycle from the terminal.

Prerequisites

Install or update to the latest published CLI by running the installer:
Or download a binary from the releases page. A Docker image is published at ghcr.io/tinfoilsh/tinfoil-cli. Check your installation with tinfoil --version. You also need a Tinfoil organization with Tinfoil Containers enabled, the same prerequisite as the quickstart.

Updating the CLI

Check for a newer stable release or install it without logging in:
This updates only the CLI executable, not your running workloads, configuration, or keys. See Updating the CLI for JSON output, permissions, and package-manager guidance.

Authenticating

Container management requires an organization admin API key. A personal admin key selects a personal context and cannot manage organization instances. Create an organization key by following Getting a Tinfoil admin key, then run:
Credentials are written to ~/.tinfoil/config.json with mode 0600. whoami checks the effective credential with the server and displays its organization or personal context, user ID, controlplane URL, and credential source. For an organization context, it shows the returned organization ID; do not infer an organization from a key prefix or the GitHub repository owner. These environment variables can override saved configuration:
Credential precedence is TINFOIL_ADMIN_KEY, then an admin key in TINFOIL_API_KEY, then the saved file. Logging in saves a key but does not unset environment overrides; logging out removes only the saved file. To switch organizations, clear or change active overrides, log in with the intended organization’s key, and check tinfoil whoami again.

Inspecting your organization

Pass -o json to get machine-readable output suitable for scripting:
Projects can be referenced by repository name (owner/repo) or project UUID. Instances can be referenced by name or instance UUID. If a debug-mode and a production-mode instance share a name, the CLI lists both and asks for the UUID.

Publishing a config release

Before a tag can be deployed, the config repo must publish a measured release. In the dashboard, open the repository’s config editor under Containers, edit tinfoil-config.yml, open and merge the generated pull request, then follow the Release steps. Alternatively, commit the config change in GitHub and run the Tinfoil Release workflow from the repository’s Actions tab. Wait for both release workflow phases to finish before deploying the tag. See Updating for both workflows. The same flow is available from the CLI with tinfoil repo, which acts through the Tinfoil GitHub App installed on the repository:
Merge the config PR on GitHub before repo build run; repo pr status must report Merged: true. repo build run only queues a release. repo build status checks its workflow without dispatching another run; also follow the GitHub Actions link and verify both tag creation and measurement/publication succeed. repo build info reports tags and published releases; confirm the same tag is published before creating the instance. It also suggests the next patch version. The build command accepts semver and adds the v prefix if missing. The repository must be public, so never put secret values in tinfoil-config.yml.

Creating an instance

Once v0.0.1 is measured and published, create an instance using that exact tag. For the template, create its GREETING_TOKEN secret first:
The command follows the deployment, printing observed boot stages until the instance is Running, and exits non-zero if it observes a failure. Pass --no-wait or -o json to return the initial response without following progress. Create also records the project and revision you typed in your connection policy, so the first connect from this machine does not prompt. Before updating anything, send the first verified request, using the reported domain and myorg/[email protected]. Optional flags above are for applications that need them; a custom domain must already be verified, and selected secrets must already exist. A named volume mount requires a disk only when it declares key-secret (JSON key_secret). Pass --volume for each required mount; optional mounts may remain empty. If required mounts exist and you omit --volume, create refuses before creating the instance and prints the volume create and container create --volume commands to run. See Create and attach a volume. Initial creates cannot be held for review. Marking the tag as the repository’s latest GitHub release defaults to true; pass --mark-latest=false to leave it unchanged. It happens only when the GitHub App is connected, the repository is active, and an initial or changed tag is running on a non-debug instance. Explicit --host selection requires host-selection access for the organization, and the selected host and GPU size must be available to it. --replace is destructive: the old instance is stopped and removed before the replacement is queued. If the replacement later fails, the old instance is not restored.

Managing projects

One project contains all instances in your organization that use the same GitHub repository. Inspect its aggregate status and instance counts with:
Set whether eligible project updates use hold for review by default:
Individual container update operations, including config-only updates, and batch project update operations inherit this setting when --hold is omitted. An explicit value applies only to that operation; it does not change the project default. Creates and deploys are never held. Update all running instances to one release:
To update only selected instances, repeat --instance with each instance UUID:
Pass --hold=true or --hold=false to override the project’s default for one update. Use =: a space-separated boolean is parsed as another positional argument. Holding applies only to blue/green updates (CPU-only and single-GPU instances without persistent volumes). If preflight finds an eligible instance whose target release requires replacement, a held project update is rejected before any instance is updated. Only running instances are updated; stopped and failed instances are skipped (bring them up with container deploy), as are instances with an update already in progress. Without a hold, replacement updates require downtime confirmation; pass --yes in scripts. After preflight, results are reported per instance, and the command exits non-zero if any instance is skipped or fails. Marking the tag as the latest release defaults to true and happens for each changed tag once it is running and serving production, subject to the repository and GitHub App conditions described above. For held updates it happens after promotion. Pass --mark-latest=false to leave the latest release unchanged.

Lifecycle

An instance is either running or it is not. deploy boots an enclave for an instance that has none; update replaces the version a running instance serves. To run the same version on a fresh enclave, update a blue/green instance to its current tag (no downtime), or stop and then deploy any instance (downtime).
deploy and update support --tag, --variable, --secret, --ssh-key, --debug, --mark-latest, and --custom-domain. deploy also supports --volume and --host; update also supports --hold. Supplied variables replace the full saved per-instance variable map; they do not replace the measured env values in tinfoil-config.yml. In managed delivery, a supplied secret list replaces the saved manual selection, then merges with declared secrets that exist in repository or organization scope. Supplied SSH-key lists replace their saved selections. --secret selects managed secrets, never private-keyserver names. A non-debug private target requires an empty managed selection; omitting --secret does not clear saved bindings. Pass a single --secret="" to send "secrets": [] on an individual create, deploy, or update; do not combine it with named selections. When switching to private delivery, also remove colliding saved variables with a complete replacement map. See the CLI and API transition example. Never upload private values as a workaround. In managed delivery, declared secrets are still merged after clearing the manual selection. A running instance cannot change hosts. Stop it, then deploy it with --host. Host access and GPU entitlement are validated when you select a host. A volume cannot automatically move to another host. When deploying after a stop, wait until the instance is Stopped before changing its tag, host, or disk attachments. A deploy queued while it is Stopping retains the current tag, host, and disk attachments. deploy --volume must attach the disk first, so it fails rather than queueing if the instance is still Stopping. deploy, update, and promote follow progress, printing observed boot stages and exiting non-zero if they observe a failure. A held update returns when the candidate is ready for review, without switching production traffic; other successful operations finish when the instance is Running and the update has completed. Pass --no-wait or -o json to return the initial response without following progress.
tinfoil container delete permanently removes the instance, its secret bindings, and its DNS records. There is no undo. Managed volumes are retained as unattached disks; delete them separately to erase their data.

Updates and downtime

Before submitting an update, the CLI prints a read-only current-to-target plan: tags, CPU/RAM/GPU resources, strategy and downtime, effective hold and its source, changed setting names, retained volume data, and whether to mark GitHub latest. No secret values are printed. The plan is not a historical snapshot or a reservation; the server validates the update again when it is submitted. When the plan includes secret_delivery, the CLI distinguishes managed and external secret names and states that private-keyserver authorization and unlock are not verified. JSON output preserves this optional object and its external_secrets_verified: false field; see the response schema. CPU-only and single-GPU instances without persistent volumes update blue/green: the new version boots alongside the current one and traffic switches once it is running. Multi-GPU instances and instances with persistent volumes cannot run two copies, so update stops the current version first and deploys the new one in its place. The CLI describes the downtime and asks you to type yes before proceeding; pass --yes in scripts. tinfoil container get shows Updates: replace for instances whose updates cause downtime.
When held for review, a blue/green update that is ready waits for container promote while the current enclave serves production. Otherwise traffic switches automatically. container cancel discards the new version without changing the production-serving instance; it also clears a failed update. Updates that replace the running instance cannot be held or canceled once the current version has stopped.

Volumes

Manage encrypted persistent volumes (Beta) with tinfoil volume:
Attach and detach require the instance to be stopped. Pass --volume to container create or container deploy to attach and deploy in one step.

Secrets

Manage organization secrets with tinfoil secret:
Secrets scoped to a single repository use tinfoil repo secret with the same subcommands and flags:
Repository secrets are also available from the Project secrets section of the repository’s config editor under Containers in the dashboard. The same secret name can have different values in different repositories. An organization secret and a repository secret cannot share a name within the same organization. --value-file accepts - for stdin, which is the recommended way to set secrets because it keeps the value out of shell history and process listings. See secrets and env vars for the underlying model.

SSH keys

Debug-mode instances authorize SSH access using public keys registered at the org level: Choose one of the supported input forms:
Reference keys with --ssh-key NAME on tinfoil container create, deploy, or update.

Registry credentials

For private images, set credentials per registry. The CLI supports ghcr, gcr, and dockerhub; configure ECR and custom registries in the dashboard or through the Admin API.

Custom domains

Once a domain is verified, deploy an instance against it with tinfoil container create ... --custom-domain api.example.com. See custom domains for the DNS record details and troubleshooting.

Model weights

For GPU inference instances, tinfoil model prepares Hugging Face weights as verified artifacts that enclaves load with integrity checking. See Model weights for how the pieces fit together.
wrap pins the repo’s latest default-branch commit unless --commit is given. update requires an existing completed wrap of the repo on that host and resolves the newest commit when --commit is omitted; when an update run with --wait completes, it prints the new models: block along with the delete command for the previous wrap job. Run that delete only after the new config is released and deployed. delete is refused while a job is running or while any deployment on the host references the artifact.

Connecting to a deployed instance

Beta: Verified connections use V3 attestation, which is still being deployed. This designation applies to connection verification and V3-dependent features, rather than ordinary instance management.
tinfoil container connect <name> resolves the production connection’s domain and config reference, checks the resolved config against your local connection policy, then runs a verified local proxy pinned to that config. It saves you from looking up those values and passing them to tinfoil proxy -e <domain> -r <repo>@<tag> by hand:
For a blue/green candidate, use tinfoil container connect my-app --review -p 3301. It pins the candidate tag and refuses if no review connection is available; it never falls back to production. container get and deployment progress output print available URLs with verified HTTP request commands. See Hold for review. The proxy binds to 127.0.0.1 by default. Pass --bind <ADDRESS> to use another interface:
Only bind beyond localhost on a trusted network because other hosts may then send requests through the proxy. In another terminal, send requests to http://localhost:3301 exactly as you would to your instance’s domain. The proxy verifies attestation on startup and pins the TLS certificate for subsequent requests. See connecting for the full client story (SDKs, raw tinfoil http).
Debug-mode instances do not pass attestation, so connect (and any other SecureClient flow) will refuse to use them. SSH into debug instances directly via ssh -p <port> [email protected]. The dashboard shows the exact command on the instance’s card.

Connection policy

Inspect or configure the policy that gates these connections with the trust command — the file is ~/.tinfoil/known-configs.yml, yours to edit, and the CLI preserves your comments when it writes:
See the connection policy reference for the file format, matching rules, and script guidance.

Resource metrics

Returns a JSON object with data_points and interval. Each data point has a timestamp, average and maximum CPU/GPU utilization, average and maximum CPU/GPU memory utilization, and CPU/GPU total memory. interval is the width of each time bucket. This is useful for piping into jq or a chart tool.

Scripting tips

  • -o json is supported on every list/get command.
  • Exit codes are non-zero on failure, with the API error message printed to stderr.
  • All commands respect the environment overrides listed above, so the CLI is safe to use from CI. Store the admin key as a secret and pass it through the environment instead of running tinfoil login.
  • --verbose and --trace increase log verbosity for debugging connectivity issues.