Skip to main content

CLI

Every command Octo has, and every flag. The octo binary ships inside @quadnix/octo-build, and a scaffolded project has it at node_modules/.bin/octo.

octo <command> [options]
octo --help
octo --version

Every command reads octo.yaml from the working directory. To read another file, set OCTO_DEFINITION_FILE_PATH; there is no flag for it.

OCTO_DEFINITION_FILE_PATH=octo.prod.yaml node_modules/.bin/octo generate -o .octo-tf

The lifecycle​

Four commands, in this order. The Lifecycle covers what each one is for.

The last two write shared state, so they require the app lock. Take it with octo lock and pass the ID it prints. generate and validate write nothing shared and need no lock.

npm run octo:generate
npm run octo:validate

LOCK_ID=$(npm run --silent octo:lock)
npm run octo:apply -- --lockId $LOCK_ID
npm run octo:commit -- --lockId $LOCK_ID # releases the lock

generate​

Writes the Terragrunt module folders for the infrastructure your definition file describes, and saves the model state.

Flag
-o, --outputDirrequiredWhere to write the folders. Folders are overwritten in place, never wiped; a module deleted from intent leaves an emptied folder so a later apply destroys its resources.
octo generate -o .octo-tf

A successful generate prints only what the listeners report. Silence is success.

With RemoteStateProvider, generate first brings the state directory up to the newest state a finished run left in the backend.

validate​

Runs terraform plan on every folder and correlates the plans with Octo's own diff. Creates nothing.

Flag
--terragruntDirrequiredThe generated directory to plan.
--terraformBinaryOverride the binary Terragrunt invokes. Sets TG_TF_PATH.
--terragruntBinaryOverride the Terragrunt binary.
--timeoutInMsPer-command timeout. Default 60 minutes.

Exits non-zero only when a folder could not be planned. Drift and destructive changes are findings, not failures. See Reading a Validate Report.

Planning a folder that holds an external resource runs octo get-response for it, which reads Octo state and calls nothing. validate puts the binary on PATH for that, the same way apply does for run-action.

apply​

The only command that changes live infrastructure, and the only one that does not boot Octo: it loads none of your modules and reads no model or resource state. It does read the definition file, because the state provider named there is where Terraform's own state is checked out of and back into around the apply.

Flag
--terragruntDirrequiredThe generated directory to apply.
--lockIdrequiredThe app lock ID from octo lock.
--auto-approveSkip the destroy check. Required to destroy anything.
--terraformBinaryOverride the binary Terragrunt invokes.
--terragruntBinaryOverride the Terragrunt binary.
--timeoutInMsGive the apply a time limit. None by default.

Verifies the lock before planning, so a run without it fails having done nothing. It does not release the lock, because the apply is the middle of a run and commit is the end of it.

Plans first and refuses if anything would be destroyed, or if a folder could not be planned. Each destroy of a resource holding data, a table or a bucket, is marked - the data it holds is lost with it.

A resource generate moved into another module folder, because its moduleId was renamed, has its Terraform state moved into the new folder before the plan, so it is not listed. Only the apply checks that move into the state provider; a plan or a validate runs on it and puts the state back.

An apply that fails part way prints the octo commit --partial that records what it did build, with this run's directory and lock ID already in it. A run that fails before applying anything does not, because there is nothing to record.

It runs without a time limit unless --timeoutInMs gives it one. How long a stack legitimately takes is a fact about that stack, and a limit that fires part way through leaves infrastructure half changed, which is worse than an apply that took longer than you expected. The limits that belong here are your CI job's own, and the terminal you can interrupt. validate and commit only read, so both keep the sixty-minute default that ends a hang.

npm run octo:apply -- --lockId $LOCK_ID --auto-approve

octo apply --help prints the flag as --autoApprove, which is the same flag. Yargs accepts either spelling, and these pages use the hyphenated one because it matches Terraform's.

commit​

Reads every folder's Terraform outputs and records them into Octo's state. All or nothing: a missing or null output throws before any state is written.

Flag
--terragruntDirrequiredThe generated directory, after an apply.
--lockIdrequiredThe app lock ID from octo lock. Released when the commit succeeds.
--partialRecord what did apply, after an apply that failed.
--terraformBinaryOverride the binary Terragrunt invokes.
--terragruntBinaryOverride the Terragrunt binary.
--timeoutInMsPer-command timeout. Default 60 minutes.

This is the command that ends a run, so a commit that succeeds releases the lock. A commit that throws keeps it: fix what it reported and run commit again with the same --lockId. A normal run never leaves a lock behind.

--partial is for after an apply that failed. It plans every folder first, and records only the folders whose plan is now empty: a folder whose apply failed still holds the outputs of its last apply, which would otherwise record a change that never reached the cloud. Within those folders it records every resource whose outputs did all resolve. Every other resource, and every resource standing on one, keeps what the last commit recorded of it, or stays out of Octo's state if it was never committed. Those come back as warnings naming what was skipped. The next run plans them again, which Terraform reconciles against its own state: recording less than reality is recoverable, recording more is not.

The app lock​

Octo's state has one writer at a time, and the lock is how that is enforced. apply and commit both require it; generate and validate do not, because neither writes shared state.

lock​

Takes the lock and prints its ID, and nothing else on stdout, so a shell can capture it.

LOCK_ID=$(npm run --silent octo:lock)

Fails if a lock is already held, naming the ID that holds it. Nothing else runs, so a second runner stops before it reads state or plans anything. With RemoteStateProvider it also fails when another run finished after your generate, so there generate comes first. It also fails while the state directory holds journal files a released lock could not archive, naming them.

Flag
--lockIdResume the run holding this lock instead of taking a new one.

There is no timeout on a lock. An expiring one would let a second apply start while the first might still be running, and from outside you cannot tell whether a dead run applied nothing, everything, or half of it. A run that died is finished rather than released: stop its process if it is still there, resume it with the ID the error named, here in HELD_LOCK_ID, then generate, apply and commit as usual. Recovering a Failed Run covers what to check before the apply.

LOCK_ID=$(npm run --silent octo:lock -- --lockId "$HELD_LOCK_ID")
npm run octo:generate
npm run octo:apply -- --lockId $LOCK_ID
npm run octo:commit -- --lockId $LOCK_ID

With a remote state provider, the run can be resumed from any machine, by the account that took the lock or by an organization administrator, and resuming reads everything the dead run had written. Stop the dead run's process first: its lock is still good, so a process that wakes up keeps writing into the same state.

unlock​

Finishes a run as it stands and releases its lock.

Flag
--lockIdrequiredThe ID to release, as octo lock printed it.

A successful commit releases its own lock, so this is for a run that will not reach one: a run that never applied, or one somebody finished by hand. With a remote state provider, unlocking makes what the run wrote the newest state, which the next generate plans against. Like commit, it seals the run into archives/<commitId>/: a copy of the state, and the journal moved beside it.

Scaffolding​

create-app​

Creates a project from a template, and runs npm install in it.

Flag
-n, --namerequiredThe app name. A directory of this name is created.
-t, --templaterequiredaws-s3-website, aws-ecs-server, or aws-dynamodb-service.
-p, --pathWhere to create it. Defaults to ..
npx @quadnix/octo-build create-app -t aws-s3-website -n shop

create-module​

Writes a module and its input schema, and exports it from src/index.ts. The model and its action are a separate command, create-model.

Flag
-n, --namerequiredModule name, in kebab-case.
-t, --typerequiredThe model type: account, app, deployment, environment, execution, filesystem, image, pipeline, region, server, service, subnet.
--packagerequiredYour library's package name, e.g. @shop.
-p, --pathProject root. Defaults to ..

create-model​

Writes a model, its schema, and a model action, inside an existing module.

Flag
-n, --namerequiredThe module the model belongs to, in kebab-case.
-t, --typerequiredThe base model this extends. Same list as above.
--packagerequiredYour library's package name.
-p, --pathProject root.

create-terraform-resource​

Writes a resource whose lifecycle Terraform manages.

Flag
-n, --namerequiredResource name, in kebab-case.
--packagerequiredYour library's package name.
-p, --pathProject root.

create-external-resource​

Writes a resource whose lifecycle runs outside Terraform, plus its add, update, and delete actions.

Flag
-n, --namerequiredResource name, in kebab-case.
--packagerequiredYour library's package name.
-p, --pathProject root.

create-anchor​

Flag
-n, --namerequiredAnchor name, in kebab-case.
--familyrequiredThe directory under src/anchors/ to group it in.
-t, --typerequiredThe model type it attaches to, or overlay.
--packagerequiredYour library's package name.
-p, --pathProject root.

create-overlay​

Flag
-n, --namerequiredThe module the overlay lives inside, in kebab-case.
--overlayrequiredThe overlay's own name, in kebab-case.
-t, --typerequiredThe model type the overlay is for.
--packagerequiredYour library's package name.
-p, --pathProject root.

Others​

run-action​

Runs one resource action for an external resource. Terraform invokes this mid-apply through a generated provisioner; you do not call it.

Flag
--resourceIdrequiredThe resource whose action to run.
--inputA parent's response value, as <parentResourceId>.<responseKey>=<value>. Repeatable.

Its stdout carries only the action's JSON result, because Terraform reads it as a data "external" response. Octo's own listeners are routed to stderr for this command.

What the action produced is recorded against the resource as soon as it succeeds, and octo commit folds that record into the resource graph and clears it. The record is also what keeps the action to one run: Terraform never updates a null_resource in place, so a changed trigger destroys and recreates it and the provisioner is invoked twice for one change. The second invocation finds the work already done and returns what the first produced.

get-response​

Prints one resource's recorded response as JSON. Terraform reads it as a data "external" for an external resource, on every plan and every apply of the folder that resource sits in; you do not call it.

Flag
--resourceIdrequiredThe resource whose response to print.

It reads Octo state and nothing else: no modules are composed and nothing is called, which is what makes it safe during a plan. The response comes from what octo run-action recorded when the commit has not folded it in yet, and from the committed resource graph after that. A resource that has neither is an error, and the plan or apply that asked for it fails with that error.

build​

A small job runner, unrelated to the infrastructure lifecycle. It reads a build.yml describing jobs and their dependencies, and runs them with a concurrency limit.

Flag
-b, --buildFilePathThe build file, relative to sourcePath. Defaults to build.yml.
-s, --sourcePathThe project source. Defaults to ..
-c, --concurrencyHow many jobs at once. Defaults to 5.
--dryRunResolve the jobs without running them.
--logsDirWrite each job's log to this directory.
--timeoutOverall limit, in minutes. 0 for none.

Environment​

OCTO_DEFINITION_FILE_PATHThe definition file, octo.yaml when unset. Relative paths resolve against the working directory, and everything the definition names resolves against the definition's own directory.
OCTO_ENV_<key>, for an env keyOverrides that key's declared value. See The Definition File.