/ Docs Guides / CLI reference MCP ← All guides
CLI

ig1 — one static binary for humans AND agents.

Go/Cobra over the generated Go SDK, thin over the API's OpenStack + Kubernetes proxies, the KaaS factory, and billing. Same auth, tiers, and project scoping as every other surface; one output contract that a shell script or an agent can rely on verbatim.

Install

One static binary, no runtime and no toolchain. Download the one for your machine, make it executable, put it on your PATH. Current release: 0.3.1.

PlatformDownloadNotes
Linux · x86-64ig1-linux-amd64the usual server build
Linux · arm64ig1-linux-arm64Graviton, Ampere, Raspberry Pi
macOS · Apple siliconig1-darwin-arm64M1 and later
macOS · Intelig1-darwin-amd64
Windows · x86-64ig1-windows-amd64.exePowerShell or cmd; no installer
checksumsSHA256SUMSverify before you run it
signatures<binary>.minisig beside each downloadminisign, signed at build time
public keyig1-minisign.pubid 8C6142C3D56C052E

Linux and macOS:

curl -fLO https://docs.cloud.ig1.com/downloads/ig1-linux-amd64
curl -fLO https://docs.cloud.ig1.com/downloads/ig1-linux-amd64.minisig
curl -fLO https://docs.cloud.ig1.com/ig1-minisign.pub
minisign -Vm ig1-linux-amd64 -P $(sed -n 2p ig1-minisign.pub)   # trusted comment: ig1 cli release <version>
curl -fLO https://docs.cloud.ig1.com/downloads/SHA256SUMS
shasum -a 256 --ignore-missing --check SHA256SUMS      # sha256sum -c on Linux
chmod +x ig1-linux-amd64
sudo mv ig1-linux-amd64 /usr/local/bin/ig1
ig1 version --client

Windows (PowerShell):

Invoke-WebRequest https://docs.cloud.ig1.com/downloads/ig1-windows-amd64.exe -OutFile ig1.exe
(Get-FileHash .\ig1.exe -Algorithm SHA256).Hash.ToLower()   # compare against SHA256SUMS
.\ig1.exe version --client
macOS Gatekeeper. The binaries are not notarized yet, so the first run is refused with « cannot be opened because the developer cannot be verified ». Clear the quarantine attribute after checking the checksum: xattr -d com.apple.quarantine ig1-darwin-arm64. Notarization and cosign signing are the remaining half of the release story — verify against SHA256SUMS until they land.

Build from source instead

Only needed to modify it — the downloads above are built from this same tree, by the docs image itself, so they are never behind it. Go 1.26+, CGO off:

cd cli
make build        # → dist/ig1 (host; -trimpath, version read from config/build.yml)
make build-all    # → the same five targets as above + SHA256SUMS
make test lint    # go test ./... ; gofmt + go vet

Configure — contexts

State lives in ~/.ig1/config.json (0600) as named contexts; every service base URL defaults to the public cloud.ig1.com endpoints, so a context with no overrides already works from the internet with no TLS configuration — the flags below exist for in-lab endpoints and for pinning:

ig1 config set-context lab \
  --api https://api.cloud.ig1.com \
  --issuer https://zitadel.cloud.ig1.com
# --cacert-file is only for in-lab *.nip.io endpoints; the public
# cloud.ig1.com surfaces are Let's Encrypt-trusted and need no flag.
ig1 config use-context lab
ig1 config list-contexts

Defaults per context: API https://api.cloud.ig1.com, issuer https://zitadel.cloud.ig1.com, plus factory, billing, and the MCP endpoint on the same *.cloud.ig1.com pattern (in-lab: *.10.57.8.64.nip.io). Precedence: flag > env > context > VIP default. Every persistent flag has an IG1_* env mirror:

FlagEnv mirrorEffect
-o, --outputIG1_OUTPUTjson | yaml | table (table on a TTY, json on pipes)
--queryIG1_QUERYJMESPath expression applied to the result
--watchIG1_WATCHre-run a list/get every N seconds
--dry-runIG1_DRY_RUNprint the mutation request without sending it
-y, --yesIG1_YESskip confirmations (required non-interactively)
--cacertIG1_CACERTextra CA bundle — the lab's internal CA
--api-url / --issuerIG1_API_URL / IG1_ISSUERoverride the context's endpoints
--contextIG1_CONTEXTpick a context for one invocation
--tokenIG1_API_TOKENraw bearer — highest-priority auth (CI)

NO_COLOR is honored trivially — the CLI never emits color.

Login

ig1 login                        # browser authorization-code + PKCE (loopback listener)
ig1 login --device               # device-code flow (headless hosts)
IG1_API_KEY="<id>:<secret>" ig1 login --api-key
ig1 login --token "<bearer>"   # raw bearer (CI)

Resolution order at call time: --token/IG1_API_TOKEN → IG1_API_KEY (client_credentials mint, 5-min cache in ~/.ig1/token-cache/) → the current context's stored login. The api-key pair rides the env, never argv (ps is not a secret store). Every login is proven with ig1 whoami before it is persisted. The flows themselves — PKCE S256, device polling, refresh — are detailed in the auth guide.

The output contract

ig1 compute instance list                       # table on a TTY
ig1 compute instance list -o json               # json on pipes (explicit -o wins)
ig1 volume list --query "[?name=='data-vol']"   # JMESPath filter/projection
ig1 cluster list --watch 5                      # re-run until interrupted
ig1 volume create --name tmp --size-gb 1 --dry-run   # prints the request, sends nothing
ig1 compute instance delete "<id>" --yes      # --yes: required non-interactively

Every list paginates (cli 0.2.0): the CLI follows the limit/marker loop to the end of the collection, so a tenant with more than a page of resources sees all of them — a full page keys the next request on its last id, a short page ends it. --max-items N bounds the total, --no-paginate restores the single-request behaviour for a caller that genuinely wants one page.

ig1 compute instance list --max-items 200     # bounded full walk
ig1 compute instance create --name web --flavor f1 --image <id> --wait   # 202, then poll to ACTIVE
ig1 volume delete "<id>" --yes --wait               # poll until it is really gone
ig1 wait vm "<id>" --for active                 # vm, volume, lb, cluster
ig1 wait volume "<id>" --for deleted --timeout 10m  # scripts branch on the exit code
ig1 metrics show "<id>"                          # live CPU / memory / disk / NIC counters
ig1 metrics history "<id>" --step 300           # what its load looked like, Prometheus-backed
ig1 volume backup create --volume "<id>" --wait       # the DR copy — off the block layer (phase 58)
ig1 volume backup restore "<backup-id>" --wait        # restore into a NEW volume, never over the source
ig1 database restore postgres/"<name>" --to "<new-name>"  # recover into a NEW database from its archive (phase 58)
ExitMeaning
0ok
1error (API/client — the tier-403 text reaches stderr verbatim)
2usage (flag parsing, unknown command, invalid arguments)
3approval-required (destructive action without --yes, non-interactive)
4wait timeout — the state never arrived (it may still arrive)
5wait failed — the resource entered an error state (it never will)

Under -o json, errors are a single JSON envelope on stderr: {"error":{"code","message","status"}}. JSON schemas per command are semver'd with the binary — ig1 version --client prints the version string agents can pin against.

Command groups

Compute — ig1 compute instance … (Nova via the API proxy)

ig1 compute instance list
ig1 compute instance get "<id>"
ig1 compute instance create --name web-1 --image "$IMG" --flavor "$FLV" \
  --key-name ops --network "$NET" --count 1 --yes
ig1 compute instance start "<id>"
ig1 compute instance stop "<id>"              # compute billing continues while stopped
ig1 compute instance reboot "<id>"            # SOFT default; --hard
ig1 compute instance resize "<id>" --flavor "$FLV2"
ig1 compute instance resize-confirm "<id>"    # frees the old host's copy
ig1 compute instance resize-revert "<id>"
ig1 compute instance shelve "<id>"            # frees the hypervisor slot
ig1 compute instance unshelve "<id>"
ig1 compute instance console-log "<id>" --lines 500
ig1 compute instance snapshot "<id>" --name web-golden
ig1 compute instance rename "<id>" --name web-2
ig1 compute instance delete "<id>" --yes      # tier 2

Create flags: --name (required), --image, --flavor, --key-name, --user-data, --network (repeatable), --count.

SSH keypairs — ig1 compute keypair … (Nova os-keypairs)

ig1 compute keypair list
ig1 compute keypair import --name ops --public-key-file ~/.ssh/id_ed25519.pub
ig1 compute keypair create --name ops -o json > ops.json   # private key prints ONCE
ig1 compute keypair delete ops --yes                        # tier 2

Storage — ig1 volume … (Cinder)

ig1 volume list
ig1 volume create --name data-vol --size-gb 10 --yes      # tier 1
ig1 volume attach "<volume-id>" --server "<server-id>"
ig1 volume detach "<volume-id>" --server "<server-id>" # a DELETE on the wire, but tier 1 —
                                                            # the one documented tier exception (auth guide)
ig1 volume extend "<id>" --size-gb 20                     # grow-only
ig1 volume delete "<id>" --yes                            # tier 2
ig1 volume snapshot list
ig1 volume snapshot create --volume "<volume-id>" --name pre-upgrade
ig1 volume snapshot delete "<id>" --yes

Create flags: --size-gb (required), --name, --image (bootable), --snapshot (restore), --type, --availability-zone. snapshot create --force snapshots an attached volume — the result is crash-consistent.

Network — ig1 network vpc|fip|sg|lb … (Neutron + Octavia)

ig1 network vpc list
ig1 network vpc create --name app-net --cidr 10.10.0.0/24   # + subnet when --cidr
ig1 network fip list
ig1 network fip allocate                      # default: first router:external net
ig1 network fip associate "<fip-id>" --server "<server-id>"   # or --port
ig1 network fip disassociate "<fip-id>"
ig1 network fip release "<id>" --yes
ig1 network sg list
ig1 network sg create --name web-sg --description "web tier"
ig1 network sg rule list "<sg-id>"
ig1 network sg rule add "<sg-id>" --protocol tcp --port-min 443 --port-max 443 \
  --remote-cidr 10.10.0.0/24                  # or --remote-group
ig1 network sg rule remove "<rule-id>" --yes
ig1 network sg delete "<id>" --yes

Routers — ig1 router … (Neutron L3, the guarded gateway path)

ig1 router list
ig1 router create --name app-rtr
ig1 router attach "<router-id>" --subnet "<subnet-id>"
ig1 router detach "<router-id>" --subnet "<subnet-id>"
ig1 router delete "<id>" --yes

create --external-gateway is the guarded path: setting an external gateway is PLATFORM ADMINS ONLY, and non-admins get the API's 403 verbatim — the CLI never pre-guesses the refusal.

Load balancing — ig1 network lb … (Octavia, OVN driver — L4, internal by default)

ig1 network lb list                          # per LB: the VIP's security group + hostname + pending/active, or "internal only"
ig1 network lb get "<id>"                    # includes per-member health, the public facet + the listener security group
ig1 network lb create --name web --subnet "<subnet-id>" --protocol tcp \
  --port 80 --member-port 8080 --member 10.20.0.11 --member 10.20.0.12
                                             # also creates the group "web-lb" opening :80, attached to the VIP port
ig1 network lb create --name shop --subnet "<subnet-id>" --protocol tcp \
  --port 443 --member-port 8443 --member 10.20.0.21 \
  --internet-facing                          # publishes shop-<tenant-slug>.10.57.8.75.nip.io (pending until the edge syncs)
ig1 network lb create --name legacy --subnet "<subnet-id>" --protocol tcp \
  --port 80 --member-port 8080 --member 10.20.0.31 \
  --manage-security-group=false              # you own the VIP's ingress: it refuses traffic until you attach a group
ig1 network lb delete "<id>" --yes           # cascade: listeners, pools, monitors, members — exposure + FIP first when internet-facing, "<name>-lb" last

create builds the whole graph — VIP + listener + pool + health monitor + members — or rolls back what it made and names what survived. The health monitor is on by default (the AWS posture: without one, a dead member keeps its hash bucket's flows forever); tune it with --health-check-delay/timeout/retries, opt out with --health-check=false. No algorithm flag: the OVN driver implements exactly one (SOURCE_IP_PORT). The VIP is internal by default and TLS terminates on the members — the driver is L4 passthrough. --internet-facing (default off, TCP listeners only — the edge speaks HTTPS to the target, so UDP + --internet-facing is refused with the reason before the first call; and the members must serve TLS on --member-port, because the edge re-encrypts to the VIP and a plain-HTTP backend fails the handshake — a self-signed certificate is fine, the edge does not verify it) publishes the VIP through the .75 edge once the graph is ACTIVE: a floating IP on the VIP port, then an exposure targeting it, answered as {name}-{tenant-slug}.10.57.8.75.nip.io with its status — pending until the edge acknowledges the config, active after. If publishing fails the load balancer is kept (a working internal LB is not destruction-worthy) and the output names what exists and the ig1 network fip / ig1 edge expose steps that finish it by hand. The certificate on the generated hostname is the lab's internal-CA wildcard, exactly as for ig1 edge below — that name resolves to a private address, so no public CA can ever certify it. "Internet-facing" here means reachable from outside the tenant through the platform edge at 10.57.8.75, which under the generated .nip.io name is fabric/VPN reach. The edge's public face is 185.255.84.178; the way to use it is ig1 edge expose --domain with a name you own, which resolves publicly and gets a publicly-trusted certificate.

The listener port is open when the load balancer is. Octavia's OVN driver hands the VIP port your tenant's default security group — the one that denies all ingress — so a load balancer used to report ACTIVE with its members ONLINE and answer nothing on the port you asked for. Every create now also creates the group {name}-lb, adds one ingress rule per distinct listener port from 0.0.0.0/0, and attaches it to the VIP port beside whatever is already there; get prints its id. 0.0.0.0/0 is the honest default because the boundary is the network, not this rule: the VIP is reachable only where you already allowed it, and a narrower default would recreate the same silence the first time you pass --internet-facing — the group is yours, so narrow it (ig1 network sg rule remove / add) when your reachability model is narrower than your network's. --manage-security-group=false opts out: nothing is created, nothing is attached, and the output says the VIP refuses traffic on the listener port until you attach a group that allows it. delete removes the group after the cascade releases the VIP port, and only when it is the one the CLI made — named {name}-lb and attached to that VIP port; a group you attached yourself is never touched, and if the delete fails the output says the group may remain.

Catalog — images & flavors

ig1 image list
ig1 flavor list

Kubernetes — ig1 cluster … (the KaaS factory)

ig1 cluster list
ig1 cluster create demo --workers 2 --yes       # --version --flavor --image --cp-replicas
ig1 cluster scale demo --workers 3
ig1 cluster autoscaling demo --min 1 --max 5    # the cluster-autoscaler owns the count
ig1 cluster autoscaling demo --enabled=false    # …until you take it back
ig1 cluster kubeconfig demo > demo.kubeconfig   # raw YAML — handle as a secret (0600)
ig1 cluster delete demo --yes                   # tier 2

kubeconfig reads the kamaji-published admin Secret through the API's k8s READ proxy — redirect to a 0600 file, never into logs. scale answers 409 on a cluster with autoscaling enabled: the cluster-autoscaler owns the worker count there — retune autoscaling --min/--max or disable it first. --max is required when enabling (it is the number tenant-quota admission charges); --min 0 parks the pool (day-2 upgrade, protection and node recycle are portal / REST / MCP surfaces today; see the factory reference).

Autoscaling groups — ig1 asg … (the factory reconciler)

ig1 asg list                                    # DESIRED next to CURRENT — drift is visible
ig1 asg create web --flavor m1.small --image ubuntu-24.04 --min 1 --max 5 \
    --health-check tcp --health-check-port 8080 \
    --scale-out-alarm cpu-high --scale-in-alarm cpu-low
ig1 asg get web                                 # members, cooldown, degraded reasons
ig1 asg update web --desired 3                  # manual scale; admission 409s name the numbers
ig1 asg delete web --yes                        # tier 2 — a 202 PROCESS, drain-first

Plain Nova VMs kept between min and max by the factory's reconcile loop. Only alarm ok → alarm transitions scale (never levels, never insufficient_data); every scale-out passes tenant-quota admission first; scale-in drains members from the LB pool before anything is deleted. A group that cannot act says why in degraded — an empty list is the healthy answer.

Tenant edge — ig1 edge … (north-south, the .75 gateway)

ig1 edge list                                   # status: pending until the edge syncs
ig1 edge expose shop --target-ip 10.168.210.15 --target-port 8443
ig1 edge delete exp-1a2b3c4d5e6f --yes          # tier 2 — takes the hostname DOWN

The one public path for tenant workloads — driven by hand here, or on your behalf by ig1 network lb create --internet-facing, which is fip + expose composed: a derived hostname {name}-{tenant-slug}.10.57.8.75.nip.io, TLS terminated at the edge with the lab's internal-CA wildcard (--domain adds a name you own, served publicly with a Let's Encrypt certificate), forwarded to an address Neutron confirms your project owns. Fabric addresses are refused with the reason; a fresh exposure honestly reports pending until the edge acknowledges the config.

Managed databases — ig1 database … (day-2 on PostgreSQL / Kafka)

ig1 database resize postgres/orders --size-gb 40      # grow-only; refuses shrink AND no-op
ig1 database rotate-credentials orders --yes          # postgres only — breaks stale clients
                                                      # on their next reconnect; roll out first

The new password is deliberately not printed by the rotation — read it back from the credentials endpoint (GET /v1/databases/postgres/orders/credentials). Kafka has no credential to rotate: its listener is plaintext in-cluster, isolation comes from the tenant's network policies. Database create/list/delete stay REST / portal / Terraform surfaces (ig1_database).

Object storage — ig1 bucket … (RGW S3)

ig1 bucket list
ig1 bucket create ci-artifacts                   # the bucket name IS the identity
ig1 bucket objects backups --prefix 2026/ --limit 100   # the object browser, paged
ig1 bucket delete ci-artifacts --yes

objects pages with --continuation-token (the previous page's next_continuation_token). S3 access keys stay on the REST / portal surface.

Billing — usage

ig1 usage                          # current month, rated to EUR
ig1 usage -o json --query "rated.total_eur"

Audit — ig1 audit list (the write trail, tier 0)

ig1 audit list                                    # default window: the last 24h, newest first
ig1 audit list --since 2026-08-14T00:00:00Z --until 2026-08-15T00:00:00Z --limit 50
ig1 audit list --method DELETE --status 403       # who was refused what

Each answer names its source: ring when the serving pod’s recent buffer provably covers your window, durable when the read went to the 30-day api.audit topic (phase 54, 2026-08-30) — an api redeploy no longer resets the trail.

Budgets & webhooks — ig1 budget / ig1 webhook (billing events)

ig1 budget list
ig1 budget create --amount-eur 250               # or --amount-cents 25000 (exact)
ig1 budget delete "<id>" --yes
ig1 webhook list                                 # never carries secrets
ig1 webhook create --url https://ops.example/hook --topic billing.budget.breached
ig1 webhook deliveries "<id>"                    # recent attempts + response codes
ig1 webhook test "<id>"                          # fire a signed test event now
ig1 webhook delete "<id>" --yes

The signing secret prints ONCE at create (with a stderr warning) — verify X-IG1-Signature with key sha256_hex(secret) over the raw request body.

Quota & tiers — ig1 quota / ig1 tenant

ig1 quota show                     # this project's tier, limits and usage
ig1 tier list                      # the public tier catalogue — names + headline numbers
ig1 tenant set-tier "<project-id>" --tier standard   # platform admins; admission-checked

Credentials — ig1 credential … (phase-26 scoped keys)

ig1 credential list                          # never carries secrets
ig1 credential create --kind mcp --tier 0 --label claude-laptop
ig1 credential revoke "<id>" --yes

create flags: --kind api-key|mcp, --tier 0|1|2, --label (required), --expires-in (days, 1–365). The client_secret and pat print ONCE — see the auth guide.

Secrets — ig1 secret … (Barbican via the OpenStack proxy)

ig1 secret list
ig1 secret create --name app-db-password --payload-file pw.txt   # or piped stdin
ig1 secret get "<id>"                       # METADATA — the default answer
ig1 secret get "<id>" --reveal > pw.txt     # the payload, raw on stdout
ig1 secret delete "<id>" --yes

Prefer --payload-file or stdin over --payload (argv leaks into process listings). The default read is metadata; --reveal is the human payload path — its warning goes to stderr so stdout stays pipeable. The MCP agent surface deliberately has no payload read at all; Terraform's ig1_secret carries the state caveat (provider guide).

Org users — ig1 org user … (admin / customer-admin)

ig1 org user list
ig1 org user invite --email jane.doe@ig-1.net --role customer
ig1 org user remove "<user-id>" --yes

Agent surfaces

ig1 mcp config        # MCP client JSON (Claude Code / Cursor / Tidet) for this context
ig1 agent init        # install SKILL.md + AGENTS.md into the current project (--dir, --force)

Both are covered in the MCP guide.

Misc

ig1 whoami -o json                # resolved user, tenant, tier, credential id
ig1 version                        # client version + schema ("v1")
ig1 completion bash                # also: zsh | fish | powershell