Skip to main content
This page covers env zero CLI version 2.7.0. Run env0 <command> --help to print the flags of the version you have installed. To install the CLI and run a first deployment, see env zero CLI.

Global flags and environment variables

Every command accepts these flags: The CLI also reads these environment variables: Each value resolves in this order: flag, then environment variable, then stored context, then default. In CI and agents, set the environment variables so each run is explicit and does not depend on stored state. Anywhere a command takes an organization (--organization, ENV0_ORGANIZATION_ID, or the argument of organization use and organization get), you can pass the organization’s name instead of its id. The name must match exactly one organization your credentials can see, ignoring case and extra spaces. Several matches exit 2 and list their ids, no match exits 4, and a name made only of spaces exits 64. A value shaped like a UUID is always treated as an id. A name costs one extra request to list your organizations, so scripts should pass ids.

Sign-in and stored context

env0 login stores credentials and a default host, organization, and project, so later commands need no flags.
  • Browser sign-in. With no API key set and a terminal attached, login opens a browser sign-in and asks you to confirm a short code. --no-browser prints the URL instead.
  • API key. When --api-key-id and --api-key-secret (or ENV0_API_KEY_ID and ENV0_API_KEY_SECRET) are set, login validates and stores that key.
  • No key and no terminal. login exits 3. CI and agents should set the API key environment variables and skip login.
On a terminal, login then asks you to pick an organization and project, and the --organization and --project flags skip the matching picker. Without a terminal, it stores the --organization and --project you pass. Credentials are stored in the OS keychain. If the keychain write fails, the CLI writes them to a plain-text file readable only by your user, prints a warning: line on stderr naming the file, and still exits 0. env0 logout removes that file. Credentials are scoped to the API host they were issued for. Signing in to a second host does not sign you out of the first, and a host you have not signed in to exits 3 even when another host is authenticated. The host, organization, and project are stored once, in config.json, so switching hosts means running login again. Browser sign-in works out which env zero tenant to use from the API host. For a host outside env0.com, it exits 64 unless you pass --tenant. API keys work against any host.

Output

Every command prints one JSON envelope, except env0 skill show (raw Markdown), env0 deployment logs --follow (a log stream), and env0 api (the raw response body). Data goes to stdout and errors go to stderr:
  • -o json prints indented JSON. -o compact prints the same JSON on one line, for jq. -o table prints aligned columns.
  • On error, stdout is empty and the exit code is non-zero, so env0 ... -o compact | jq .data is safe.
  • Arrays are always arrays. An empty result is [], never null or a missing key.
  • error.host names the host the CLI was pointed at. It is absent for usage errors and for failures before a host is chosen. Credentials and paths are removed from it.

Pagination

environment list, deployment list, and plan list return at most 100 rows by default and add a page object next to data:
"truncated": true means more rows exist. Pass --all to fetch every page. --limit accepts 1 to 100. organization list, project list, and template list are not paginated. They return the whole collection and take no --limit or --all.

Exit codes

With --wait or deployment logs --follow, the exit code reflects the deployment’s final status: The deployment is printed to stdout in both cases, including failedCommand and errorMessage when it failed. WAITING_FOR_USER means the deployment needs approval, not that it failed.

context

Returns an environment’s state, recent deployments, and a failure summary in one call. Read .data.summary first, then .data.deployments, then deployment logs for the full error. The output has these keys:
  • environment: the same object environment get returns.
  • deployments: recent deployments, newest first.
  • summary: drift status and the latest deployment’s status and type. When that deployment failed (FAILURE, INTERNAL_FAILURE, or TIMEOUT), it also carries failedCommand, a truncated errorMessage, and exitCode. It carries costEstimation when the environment has one.
  • subEnvironments: for a workflow environment, each sub-environment’s alias, plus the revision and status of its latest deployment.
  • inventory: only with --inventory. It holds resources (the count of cloud resources the environment manages), unmanaged (the count of resources Cloud Compass attributes to the environment that its state does not hold), drifted (the count of drifted resources), resourceTypes (a list with the managed and drifted counts for each resource type), and insights (CloudQuery insight counts by severity on those resources). resources is absent when Cloud Compass cannot read the environment’s managed state. drifted and the drift in resourceTypes are absent when drift detection is off for the environment or Cloud Compass cannot read drift. A Cloud Compass problem never fails context: with no connection the block is {"connected": false, "reason": "..."}, and on any other error it carries only error.

Organizations

organization list

Lists every organization you can access. org is an alias for organization.

organization get

organization use

Stores a default organization for later commands. Takes an organization name or id. Without one on a terminal, it shows a picker you can type to filter.

Projects

project list

Lists every project in the organization.

project get

project create

The new project does not become your stored project. Pass its id (.data.id) as --project to later commands, or run env0 project use <project-id>. Requires permission to create projects in the organization.

project archive

Archives the project. project get and project list still return it with isArchived: true. The CLI cannot unarchive a project, and archiving a project cannot be undone.

project use

Stores a default project, and its organization, for later commands. Without an argument on a terminal, it shows a picker of the stored organization’s projects that you can type to filter. A name, or a Parent / Child path, is looked up in the current organization and must match exactly, ignoring case. A full path wins over a nested project’s own name, so web picks a top-level Web before Platform / Web. Archived projects are skipped. Several matches exit 2 and list their ids. No match exits 4 and names similar projects.

Templates

template list

Lists templates in exactly one scope. Passing both flags, or neither with no stored context, exits 64.

template get

template create

Requires permission to manage templates in the organization.

template assign

Assigns a template to a project. environment create --template only deploys templates assigned to the target project.

template delete

Removes the template from template list. template get can still return the record.

Environments

env is an alias for environment. Actions take an environment id, not a name.

environment list

All filters are optional and combine. The list does not include workflow sub-environments. To see them, run context on the workflow environment.

environment get

The output always includes tags, as {} when the environment has none.

environment create

Creates an environment and starts its first deployment. The output is that deployment. With --wait, its environmentId is the new environment’s id. A --repository environment gets a single-use template that needs no cleanup. When no cloud credential is assigned to the project, pass provider credentials as --secret, for example --secret AWS_ACCESS_KEY_ID=.... An assigned credential keeps keys out of your shell history and process list.

environment deploy

Starts a deployment and prints it. A deployment started while another is running is queued, not rejected. --manifest, --node, and --run-from-here are covered in Deploy workflows with the CLI.

environment plan

Previews what a deployment would change without applying anything. It starts a dry run, which is a plan-only deployment, always waits for it, streams its logs to stderr, and prints the finished deployment to stdout. The deployment’s type is dryRun, and its planSummary counts the planned changes.
  • Pass an environment id or a name. A value shaped like a UUID is treated as an id. A name is looked up as environment list --name looks it up, except that archived environments are skipped.
  • Names are not unique within a project, so the CLI never guesses. No match exits 4. Several matches exit 2 and list each match’s id and status. An empty name exits 64.
  • An archived environment is refused with exit 2, because a dry run would unarchive it.
  • The exit code follows the dry run as --wait does: 0 when it finished, 1 when the plan failed (read failedCommand and errorMessage), and 75 when --timeout runs out while the dry run may still be running.
  • If the environment already has a deployment waiting for approval, the dry run queues behind it. The CLI says so on stderr, even with --quiet.
  • A dry run that stops at WAITING_FOR_USER while the command waits has finished its plan. The CLI cancels it so nobody can approve it, prints it with status CANCELLED, and exits 0.
  • For a workflow environment, the workflow’s own dry run has no steps and no planSummary. Each sub-environment runs its own dry run. When the workflow’s dry run ends, stderr lists each sub-environment by alias with its status, plan counts, and deployment id. Read a sub-environment’s full plan with deployment logs.

environment apply

Creates or deploys every environment a YAML or JSON file lists, in parallel. Use it to bring up a set of environments, such as a test workspace made of several workflow environments. Each entry is a deploy manifest, with the same revision, variables, and subEnvironments as --manifest, plus these fields:
  • An environment is found by its exact name in its project, and case counts. Archived environments are ignored. If none exists, apply creates it from template. If one exists, apply redeploys it with the entry’s revision, variables, and sub-environment overrides. Two live environments with the same name exit 2.
  • Most checks run before anything changes. An unknown field, an entry listed twice, a missing template for an environment that does not exist yet, a bad ttl or tag, or sub-environment overrides for an environment that is not a workflow exit 64, and nothing is created or deployed. A wrong sub-environment alias gets the same result only where apply can read the workflow’s aliases up front, which is an existing workflow environment that has already read its workflow file. For a new workflow environment, or an existing one that has never read its workflow file because its first deployment failed early or has not reached it, apply checks aliases only after it creates the environment or starts the deployment. A wrong alias there becomes that environment’s error with type usage, the command exits 1, and the other environments still deploy.
  • env zero rejects approval on workflow environments. In a project that requires approval by default, a workflow entry needs requiresApproval: false to be created.
  • Environments run in parallel, each as its own deployment. Inside a workflow, needs: still orders the sub-environments.
  • With --only-failed, an environment is skipped unless it does not exist, has never been deployed, or its latest deployment ended in FAILURE, INTERNAL_FAILURE, TIMEOUT, CANCELLED, or ABORTED. In a failed workflow environment, when every failed sub-environment is one sub-environment or downstream of it, only that part runs again, as a --run-from-here run. Otherwise the whole workflow is redeployed, for example when failures start in two or more sub-environments that do not depend on each other, or when the entry sets a top-level revision.
  • --wait does not stream logs, because the deployments run in parallel. Follow one with deployment logs <deployment-id> --follow.
The output lists every entry in file order, with these fields: One environment failing does not stop the others. The exit code is 0 when every environment started and, with --wait, none ended in a failed state. It is 1 when any environment has an error or a failed deployment, and the full result is still printed. A --wait that times out is that environment’s error, with type transient.

environment destroy

Destroys the environment’s resources. This cannot be undone, so it requires --yes (or --force) and has no prompt. On an environment that requires approval, the destroy waits at WAITING_FOR_USER like any deployment. With --file, destroy takes the same file as apply, so a workspace comes down with the file that brought it up. Entries need no template.
  • Environments are found as apply finds them, and every lookup runs before anything is destroyed. destroy -f shares the parse-level file validation of apply, such as an unknown field, an entry listed twice, or a bad ttl or tag, and its environment lookups, so a file that fails those is rejected here too. It does not require template, and it does not verify workflow aliases or whether the target supports sub-environment overrides, so a file that apply rejects for those with exit 64 still destroys the environments it names. It still validates the variable names and types in sub-environment overrides, and an invalid type exits 64 before any request.
  • Nothing to destroy is a skip, not an error: an environment that does not exist, one never deployed, one already destroyed, or one that already has an unfinished destroy. A rerun never queues a second destroy.
  • The destroys run in parallel. A workflow environment is destroyed as a unit, with its sub-environments destroyed in reverse needs: order.
  • env zero archives each environment once its destroy succeeds. An environment whose destroy fails or waits for approval stays live. After you fix or approve it, run the same command again to destroy what is left.
  • --requires-approval applies to every destroy. Without it, an entry’s requiresApproval applies, then the environment’s own setting.
  • --node and --pretty work only with a single environment id, and --wait does not stream logs.
  • The output lists every entry in file order, with name, projectId, environmentId, action (destroy or skip), deployments, and error. The exit code is 1 when any environment has an error or a failed destroy. A destroy waiting for approval still exits 0.

environment archive

Marks the environment inactive without running a deployment, so its cloud resources stay. Run destroy first to remove them. See mark an environment as inactive.

environment tag list

With an id, prints that environment’s tags, for example {"team": ["eng", "payments"]}. Without one, prints every tag key and value used in the organization. tags is an alias for tag.

environment tag set

Replaces the whole value list of each key you name and leaves other keys alone. To keep an existing value, pass it again: team=eng team=payments. Prints the updated environment.

environment tag remove

Removes each key with all of its values. Passing KEY=VALUE exits 64. Tag limits and permissions are listed in Environment tags. Tags are visible to anyone who can view the environment, so do not put secrets in them.

environment ttl set

Replaces the environment’s lifespan. A duration counts from now, not from the current end, so ttl set <environment-id> 2h can bring the end forward. The minimum is 1h. Read the new end from lifespanEndAt in the output: env zero truncates it to the minute.

environment ttl extend

Adds the duration to the current end, or to now if the end has passed. On an environment with no TTL, exits 2. extend reads the current end and then writes a new one, so a TTL change someone makes in between is overwritten.

environment ttl clear

Removes the TTL, so env zero never destroys the environment automatically. lifespanEndAt disappears from the output. For default and maximum TTLs, see Configure policy TTL.

Waiting for a deployment

--wait polls the deployment until it reaches a final status, then exits with the code for that status. It retries 5xx and 429 responses. A deployment that reaches WAITING_FOR_USER stops the wait with exit 0. Approve it with deployment approve. While it waits, the deployment’s step logs stream to stderr. Stdout still receives only the final deployment, so --wait -o compact | jq works. Logs keep their color on a terminal and lose it when redirected. Secrets are redacted before the logs reach the CLI.

Deployments

deployment list

Lists deployments, newest first. Paginated, see Pagination.

deployment get

Includes a truncated errorMessage, failedCommand, and exitCode for a failed deployment.

deployment logs

Without --follow, returns the deployment’s status, failedCommand, exitCode, full errorMessage, and steps, with color codes removed. Use it for the root cause of a failure:
  • steps lists every step in order, each with its name, status, and events: the log lines the env zero console shows for that step. A step that has not started has no events. A step with available: false has no events because env zero no longer stores its logs, which happens for some older deployments.
  • output holds the deployment’s Terraform or OpenTofu outputs, not the console output, so it is often {}.
  • --step <name> returns only that step. Repeatable. Use a name from steps, not a displayName. An unknown name exits 64 and lists the valid names.
  • --tail <n> keeps the last n lines of each step. A step that lost earlier lines has truncated: true.
  • -o table prints the fields, then the log lines grouped by step.
With --follow (-f), streams the logs to stdout until the deployment finishes, then exits like --wait. --step and --tail do not combine with --follow. The stream is text grouped by step. With -o json or -o compact it is newline-delimited JSON, one {"step", "level", "message", "timestamp"} object per line, with no envelope. --poll-interval defaults to 3s.

deployment approve

Resumes a deployment at WAITING_FOR_USER. The status changes a few seconds later. Approving a deployment that has already moved on exits 2 and is harmless. Approval rights do not approve your own deployments automatically.

deployment cancel

Cancels a QUEUED deployment, or rejects one at WAITING_FOR_USER.

deployment abort

Aborts an IN_PROGRESS deployment. approve, cancel, and abort return right away and take no --wait. To follow a resumed deployment, poll deployment get until its status leaves WAITING_FOR_USER, then run deployment logs <deployment-id> --follow.

Plans

plan list

Lists plan summaries across an environment’s deployments. Paginated, see Pagination.

plan get

Returns the plan stored with a deployment. Check planAvailable first: most deployments, including those at WAITING_FOR_USER, store no plan. With no plan, resourceChanges and outputChanges are [], the same as a plan with no changes.
  • resourceChanges[].action shows delete and recreate only when a plan is stored.
  • The deployment’s planSummary counts can be higher than the entries in resourceChanges. Use resourceChanges for the actual resources.
  • To see what drifted, run plan get on the latest deployment with type driftDetection. See Drift detection.

Cost estimation

environment get and list, context (under summary and each deployment), plan get, and deployment get and list include costEstimation: totalMonthlyCost and the deployment’s monthlyCostDiff, as numbers with no currency. The table output assumes USD. The field is left out, which jq reads as null, when cost estimation is off for the organization, the deployment produced no estimate, or the CLI could not read the estimate.

Variables

variable list

Lists the variables configured on a scope, with sensitive values masked. var is an alias for variable.

VCS connections

vcs list

Lists the organization’s VCS connections. Pass a connection’s id as --vcs-connection-id to template create or environment create for a private repository. Its accessScope, for example Organization:acme, must cover the repository’s owner. --type filters by provider, for example GitHub or GitLab.

Cloud inventory

env0 inventory compares the cloud resources that exist with the ones env zero manages, through Cloud Compass. Cloud Compass reads the CloudQuery connection stored in the organization’s settings, so the CLI needs no CloudQuery credential. Every inventory command takes --organization, by name or id, and uses the stored organization when you omit it. A resource is managed when Cloud Compass matched it to a resource in the state of an environment that still exists, or when it carries that environment’s env0_environment_id tag. An unmanaged resource is one no environment manages. It may belong to other tooling, such as CloudFormation, CDK, or Terraform run outside env zero, or it may have been created by hand. Run inventory status first. Data is only as fresh as the last CloudQuery sync. When Cloud Compass cannot read its managed state, coverage returns managed, unmanaged, drifted, and managedPct as null for that group (? in tables), and list returns managed: null. Treat those values as unknown, not as zero.

inventory status

Shows whether Cloud Compass can read CloudQuery, how many resources env zero manages, and when each integration last synced. It exits 0 either way, so check .data.connected. When it is false, .data.reason says why, and every other inventory command fails with that reason. When it is true, .data.managedResources counts the managed resources. A count of 0 means no environment state has been matched yet, so every resource reads as unmanaged. An absent count means the count itself failed. Each integration’s lastRun.completedAt shows its last sync, and lastRun.statusReason: partial_success means some resources were not synced.

inventory coverage

Counts managed and unmanaged cloud resources, plus drifted resources and CloudQuery insights, grouped by account by default. coverage comes from a separate Cloud Compass count that includes nested records, so its totals can be higher than the page.totalCount of inventory list.

inventory list

Lists cloud resources, optionally only those env zero does or does not manage. The filters of coverage apply, and these flags are added: page.totalCount is the full match count. The organization-wide list reads ten resources per request, so a large --limit or --all takes a while. Use coverage for counts. A good order is to run coverage, then coverage --account <name> --by type, then list --unmanaged --account <name> --type <label>.

inventory query

Runs a SQL query against the CloudQuery asset warehouse, which is ClickHouse. Use it for questions the other commands do not cover. Rows come back keyed by column name. These tables are the most useful:
  • cloud_assets holds every synced resource, with cloud, account, region, resource_type, name, tags, _cq_platform_id, and _cq_parent_id.
  • resource_enrichment rows with enrichment_source = 'envzero' link a resource, by _cq_platform_id, to the environment that manages it.
  • insight_resources holds CloudQuery insights per resource, with severity and title.
Results are read 500 rows per request, and each request runs the query again. When a result needs more than one request and the query has no top-level ORDER BY, the CLI runs it as SELECT * FROM (<query>) ORDER BY ALL so the pages line up. A query with its own ORDER BY is sent as written, so end it with a unique column such as _cq_platform_id. Otherwise rows that tie can repeat or go missing between pages. A request that runs past the server timeout exits 75 and is not retried, so narrow the query before you run it again. Counting resource_enrichment rows undercounts what env zero manages, because it holds only state matches. Take managed and unmanaged counts from inventory status, coverage, or list.

Raw API requests

api

Sends one request to any env zero API endpoint and prints the response body unmodified. It is an unsupported escape hatch for endpoints no other command covers. It has no envelope, retries, pagination, or secret masking, so use a dedicated command whenever one exists. Endpoints are listed in the API reference.
Responses are not masked. GET /configuration returns sensitive variable values in clear text, and endpoints that create a token or key return its secret. Read variables with variable list, which masks them, and keep api output that may hold a secret out of logs.
  • Quote the path, because ? and & mean something to the shell. The path is relative to the API host and carries the query string. A full URL exits 64, so your credentials only go to the configured host.
  • The stored organization and project are not added. Put organizationId or projectId where the endpoint’s reference page puts it: the path, the query, or the body.
  • The body is sent as is, as application/json unless you pass your own Content-Type. It is read only from --input, never from a bare pipe.
  • stdout gets the response body byte for byte, with no added newline. -o shapes only the error envelope.
  • A non-2xx response is still printed to stdout, and the error envelope goes to stderr with (HTTP <status>) at the end of error.message. The exit code follows the status: 2 for other 4xx, 3 for 401 and 403, 4 for 404, 75 for 429 and 5xx, and 1 for anything unexpected, including a 3xx, because redirects are not followed.
  • A path or method the API does not have answers 403 with a message about an invalid Authorization header, whatever your credentials. If organization list works, check the method and path against the API reference.
  • api never retries and never asks for confirmation. A write that fails with a 5xx or exit 75 may still have taken effect, so check before you send it again.

API keys

The CLI manages two kinds of API key: Use an organization key for shared CI. Use a personal key on your own machine, or when actions must be attributed to you. Both create commands print apiKeySecret once. No command returns it again. The returned apiKeyId and apiKeySecret are the values for ENV0_API_KEY_ID and ENV0_API_KEY_SECRET. Keys do not expire.

api-key list

Lists the organization’s keys without secrets, including lastUsedAt. Custom roles show as their id.

api-key create

No command shows a key’s project roles after creation.

api-key revoke

Takes the id field from api-key list, which looks like auth0|abc123, not the apiKeyId field. Quote it, because | is a shell pipe. Revoking cannot be undone. The key leaves the list at once but can keep authenticating for a short time afterwards. --force is an alias for --yes.

personal-api-key list

personal-api-key create

Creates a key with your permissions. It stops working when your user account does.

personal-api-key revoke

Takes the id field, not apiKeyId. Another user’s key id exits 4.

Sign-in commands

login

See Sign-in and stored context.

status

Always exits 0. Check .data.authenticated, not the exit code.

logout

Removes the current host’s credentials. Keeps the stored host, organization, and project. Other hosts stay signed in.

Agent skill

skill install

Writes a guide to the CLI into a coding agent’s configuration. AGENTS.md and the Copilot file are edited only between managed markers, so the rest of the file is kept. Running install again is safe. --global installs in your home directory, for claude and codex. --path writes to a file or directory you choose.

skill show

Prints the guide as Markdown, not JSON. Ignores -o.

skill uninstall

Deletes the Claude Code or Cursor file, or removes the managed block from a shared file.

Other commands

Telemetry

The CLI sends anonymous usage events and request traces: the command, output format, detected agent or CI system, authentication method, error type, exit code, CLI version, and organization id. Events use a random per-install id. They never include credentials, arguments, output, resource names or ids, or file paths. Telemetry never blocks or fails a command. To turn off both telemetry and tracing, set ENV0_TELEMETRY_DISABLED=1 or DO_NOT_TRACK=1. env0 status shows whether each is on.