> ## Documentation Index
> Fetch the complete documentation index at: https://docs.tinfoil.sh/llms.txt
> Use this file to discover all available pages before exploring further.

# Managing containers from the CLI

> Use the Tinfoil CLI to manage the full container lifecycle without opening the dashboard.

The [Tinfoil CLI](https://github.com/tinfoilsh/tinfoil-cli) lets you manage deployments and individual containers, along with secrets, SSH keys, registry credentials, and custom domains.

This page walks through the full container lifecycle from the terminal.

## Prerequisites

Install the CLI:

```bash theme={"dark"}
curl -fsSL https://github.com/tinfoilsh/tinfoil-cli/raw/main/install.sh | sh
```

Or download a binary from the [releases page](https://github.com/tinfoilsh/tinfoil-cli/releases). A Docker image is published at `ghcr.io/tinfoilsh/tinfoil-cli`.

You also need a Tinfoil organization with Containers enabled, the same prerequisite as the [quickstart](/containers/quickstart).

## Authenticating

Container management endpoints accept an **admin API key** scoped to a single organization. Create one from the dashboard's **Admin** tab by following [Getting a Tinfoil admin key](/admin/get-admin-key), then run:

```bash theme={"dark"}
tinfoil login                        # prompts for the key (no shell history)
tinfoil login --api-key admin_xxx    # non-interactive, e.g. for CI
tinfoil whoami                       # confirm the saved credential
tinfoil logout                       # delete the saved credential
```

Credentials are written to `~/.tinfoil/config.json` with mode `0600`. Two environment variables override the saved values for one-off invocations:

| Variable                   | Purpose                                                 |
| -------------------------- | ------------------------------------------------------- |
| `TINFOIL_API_KEY`          | Admin key (`admin_...`)                                 |
| `TINFOIL_CONTROLPLANE_URL` | Controlplane URL (defaults to `https://api.tinfoil.sh`) |

<Note>
  Admin keys carry the organization ID, so the CLI never asks for an org. To act on a different org, log out and log in with that org's admin key.
</Note>

## Inspecting your organization

```bash theme={"dark"}
tinfoil deployment list          # repositories and aggregate instance counts
tinfoil deployment get myorg/my-app-config
tinfoil container list           # all containers in the org
tinfoil container get my-app     # full detail for one container
tinfoil container hosts          # which container hosts your org may target
```

Pass `-o json` to get machine-readable output suitable for scripting:

```bash theme={"dark"}
tinfoil container list -o json | jq '.[] | select(.status == "failed")'
```

Deployments can be referenced by **repository name** (`owner/repo`) or **deployment UUID**. Containers can be referenced by **name** or **container UUID**. If you have a debug-mode and a production-mode container with the same name, pass `--debug-mode` to disambiguate.

## Publishing a config release

Before a tag can be deployed, the config repo must publish a measured release. The CLI uses the GitHub App connected to your Tinfoil organization, so you do not need a GitHub token or the `gh` CLI.

Fetch the current config, then open a pull request with your local version:

```bash theme={"dark"}
tinfoil repo config get myorg/my-app-config --raw > ./tinfoil-config.yml
tinfoil repo config pr myorg/my-app-config --file ./tinfoil-config.yml
```

Use `--file -` to read the config from stdin, and `--body` to add a pull request description. Repository commands also support `-o json`.

Check whether the pull request has been merged:

```bash theme={"dark"}
tinfoil repo pr status myorg/my-app-config 42
```

After it is merged, inspect the suggested version and trigger the **Tinfoil Release** workflow:

```bash theme={"dark"}
tinfoil repo build info myorg/my-app-config
tinfoil repo build run myorg/my-app-config --version v1.0.0
```

The command prints the GitHub Actions URL. Follow it and wait for both release workflow phases to finish before deploying the tag. See [Updating & lifecycle](/containers/updates#starting-an-update) for the dashboard and direct GitHub alternatives.

## Deploying a container

Once you have a measured release in your config repo (see [quickstart](/containers/quickstart) for the GitHub setup), deploy it:

```bash theme={"dark"}
tinfoil container create my-app \
  --repo myorg/my-app-config \
  --tag v1.0.0 \
  --variable LOG_LEVEL=info \
  --variable PORT=8080 \
  --secret DATABASE_URL \
  --custom-domain api.example.com
```

| Flag                   | What it does                                                    |
| ---------------------- | --------------------------------------------------------------- |
| `--repo`               | GitHub `owner/repo` containing `tinfoil-config.yml` (required)  |
| `--tag`                | Release tag to deploy (required)                                |
| `--variable KEY=VALUE` | Environment variable; repeatable                                |
| `--secret NAME`        | Organization or repository secret to mount; repeatable          |
| `--ssh-key NAME`       | Org SSH key (debug containers only); repeatable                 |
| `--debug`              | Deploy in [debug mode](/containers/debug-mode)                  |
| `--staging`            | Deploy in [staging mode](/containers/staging-mode)              |
| `--custom-domain`      | Use a [verified custom domain](/containers/custom-domains)      |
| `--host`               | Pin to a specific host (see `tinfoil container hosts`)          |
| `--replace ID`         | Atomically replace an existing container (frees its GPUs first) |

The command returns once the deployment is queued. Poll for readiness with `tinfoil container get my-app`.

## Managing deployments

One deployment contains all container instances in your organization that use the same GitHub repository. Inspect its aggregate status and instance counts with:

```bash theme={"dark"}
tinfoil deployment list
tinfoil deployment get myorg/my-app-config
```

Set whether new instances and deployment-wide updates use [staging mode](/containers/staging-mode) by default:

```bash theme={"dark"}
tinfoil deployment settings myorg/my-app-config --default-staging true
```

Update all eligible instances to one release:

```bash theme={"dark"}
tinfoil deployment update myorg/my-app-config --tag v1.0.1
```

To update only selected instances, repeat `--instance` with each container UUID:

```bash theme={"dark"}
tinfoil deployment update myorg/my-app-config \
  --tag v1.0.1 \
  --instance 550e8400-e29b-41d4-a716-446655440000 \
  --instance 6ba7b810-9dad-11d1-80b4-00c04fd430c8
```

Pass `--staging true` or `--staging false` to override the deployment's default for one update. Only running or failed instances are eligible. An instance is skipped if it is in any other state or already has an update in progress. Results are reported per instance, and the command exits non-zero if any instance is skipped or fails.

## Lifecycle

```bash theme={"dark"}
# Stop a running container (DB row preserved, traffic stops, billing pauses)
tinfoil container stop my-app

# Start a stopped container (optionally with config overrides)
tinfoil container start my-app
tinfoil container start my-app --tag v1.0.1

# Redeploy a running container with new config (blue-green)
tinfoil container relaunch my-app --tag v1.0.1
tinfoil container relaunch my-app --variable LOG_LEVEL=debug
tinfoil container relaunch my-app --secret NEW_KEY --secret OTHER_KEY

# Delete (irreversible)
tinfoil container delete my-app
```

`start` and `relaunch` support `--tag`, `--variable`, `--secret`, `--ssh-key`, `--debug`, `--staging`, `--custom-domain`, and `--host`. Any override you pass replaces the stored value entirely. For example, `--secret A --secret B` sets the secrets list to `[A, B]`, not `[A, B, ...existing]`.

<Warning>
  `tinfoil container delete` permanently removes the container, its secret bindings, and its DNS records. There is no undo.
</Warning>

## Managing in-progress updates

Updates triggered by `relaunch` go through a blue-green window. While the new version is booting, you can inspect or cancel it:

```bash theme={"dark"}
tinfoil container update status my-app   # show update status (pending/started/ready/failed)
tinfoil container update accept my-app   # promote a "ready" candidate
tinfoil container update cancel my-app   # discard the candidate, keep current
```

[Staging](/containers/staging-mode) containers always wait for `update accept`; production containers promote automatically once the candidate is ready.

## Secrets

Manage organization secrets with `tinfoil secret`:

```bash theme={"dark"}
tinfoil secret list

# Create
tinfoil secret create DATABASE_URL --value-file ./db.url
echo -n "$STRIPE_KEY" | tinfoil secret create STRIPE_SECRET_KEY --value-file -

# Update value (containers using it are marked stale; redeploy to pick up the new value)
tinfoil secret set DATABASE_URL --value-file ./db.url

# Inspect (the value itself is never returned)
tinfoil secret get DATABASE_URL

# Delete (fails if any container references it)
tinfoil secret delete DATABASE_URL
```

Manage secrets available only to one repository with `tinfoil repo secret`:

```bash theme={"dark"}
tinfoil repo secret list myorg/my-app-config

# Create
tinfoil repo secret create myorg/my-app-config DATABASE_URL --value-file ./db.url

# Update, inspect, or delete
tinfoil repo secret set myorg/my-app-config DATABASE_URL --value-file ./db.url
tinfoil repo secret get myorg/my-app-config DATABASE_URL
tinfoil repo secret delete myorg/my-app-config DATABASE_URL
```

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 — it avoids leaking the value via shell history or process listings. See [secrets and env vars](/containers/secrets-and-env-vars) for the underlying model.

## SSH keys

[Debug-mode](/containers/debug-mode) containers authorize SSH access using public keys registered at the org level:

Choose one of the supported input forms:

```bash theme={"dark"}
tinfoil ssh-key list

# Read from a file
tinfoil ssh-key create laptop --public-key-file ~/.ssh/id_ed25519.pub

# Or read from stdin
cat ~/.ssh/id_ed25519.pub | tinfoil ssh-key create laptop --public-key-file -

# Or pass the public key inline
tinfoil ssh-key create laptop --public-key "ssh-ed25519 AAAA..."

tinfoil ssh-key delete laptop
```

Reference keys at deploy time with `--ssh-key NAME` on `tinfoil container create` (or `relaunch` / `start`).

## Registry credentials

For [private images](/containers/private-images), set credentials per registry. Tinfoil supports `ghcr`, `gcr`, and `dockerhub`:

```bash theme={"dark"}
tinfoil registry list

# GitHub Container Registry: classic PAT with read:packages
tinfoil registry set ghcr --username my-gh-user --token ghp_xxx

# Google Artifact Registry / Container Registry: service-account JSON
tinfoil registry set gcr --key-file ./gcp-sa.json

# Docker Hub: PAT with read permission
tinfoil registry set dockerhub --username my-docker-user --token dckr_xxx

# Remove credentials for a registry
tinfoil registry delete ghcr
```

## Custom domains

```bash theme={"dark"}
tinfoil domain list

# Register a domain — output includes the TXT/CNAME records to configure
tinfoil domain add api.example.com

# Re-check DNS after configuring records
tinfoil domain verify api.example.com

# Remove (fails if any container uses it)
tinfoil domain delete api.example.com
```

Once a domain is verified, deploy a container against it with `tinfoil container create ... --custom-domain api.example.com`. See [custom domains](/containers/custom-domains) for the DNS record details and troubleshooting.

## Connecting to a deployed container

`tinfoil container connect <name>` resolves a container's enclave domain and source repo, then runs a verified local proxy — equivalent to `tinfoil-proxy -e <domain> -r <repo>` (the [standalone proxy CLI](/local-proxy/cli)) but without copy-pasting either value:

```bash theme={"dark"}
tinfoil container connect my-app -p 3301
```

The proxy binds to `127.0.0.1` by default. Pass `--bind <ADDRESS>` to use another interface:

```bash theme={"dark"}
tinfoil container connect my-app --port 3301 --bind 0.0.0.0
```

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 container's domain. The proxy verifies attestation on startup and pins the TLS certificate for subsequent requests. See [connecting](/containers/connecting) for the full client story (SDKs, raw `tinfoil http`).

<Warning>
  Debug-mode containers do not pass attestation, so `connect` (and any other `SecureClient` flow) will refuse to use them. SSH into debug containers directly via `ssh -p <port> root@console.tinfoil.sh` — the dashboard shows the exact command on the container's card.
</Warning>

## Resource metrics

```bash theme={"dark"}
tinfoil container metrics my-app --time 24h
```

Returns CPU, GPU, and memory utilization buckets as JSON. 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 controlplane error message printed to stderr.
* All commands respect `TINFOIL_API_KEY` / `TINFOIL_CONTROLPLANE_URL`, 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.
