Skip to main content
A playbook is made up of four block types. Each has a distinct role in defining how a run behaves.

errand block

The errand block sets playbook-level configuration. It is optional and can appear at most once across all .ern files.
errand {
  working_dir = "<path>"
}

Attributes

working_dir
string
The default working directory for all tasks in the playbook. Tasks with their own working_dir override this value. Relative paths are resolved from the directory where errand was invoked.

Example

errand {
  working_dir = "./service/api"
}

task "test" {
  commands = ["go test ./..."]
}

task "build" {
  depends_on = [task.test]
  commands   = ["go build -o bin/api ./cmd/api"]
}
Both test and build run from ./service/api without repeating working_dir in each task.

Notes

  • If no errand block is declared, tasks run from the directory where errand was invoked unless they specify their own working_dir.
  • Declaring errand more than once is an error.

variable block

Variables are named inputs. Declare them in the playbook and pass values at run time with --var. Reference them as var.<name>.
variable "<name>" {
  description = "<text>"
  type        = <type>
  default     = <value>
}
All attributes are optional. A variable with no default is required: Errand reports an error if no value is provided.

Attributes

description
string
A human-readable description shown in error messages.
type
type constraint
A type constraint. When set, Errand validates and converts the provided value. See types for all available types.
default
any
The value used when no --var flag is passed. Must be compatible with type when both are set.

Passing values

errand run deploy --var env=production --var replicas=3
Multiple --var flags are supported. Values are strings on the command line and converted to the declared type automatically.

Examples

Required variable, no default means it must be supplied:
variable "registry" {
  description = "Container registry host"
  type        = string
}

task "push" {
  commands = ["docker push ${var.registry}/myapp:latest"]
}
Variable with a default:
variable "port" {
  description = "Port the server listens on"
  type        = number
  default     = 8080
}

task "run" {
  commands = ["./bin/server --port ${var.port}"]
}
Object variable, structured inputs keep related values together:
variable "db" {
  description = "Database connection settings"
  type = object({
    host = string
    port = optional(number, 5432)
    name = string
  })
}

task "migrate" {
  commands = [
    "psql -h ${var.db.host} -p ${var.db.port} -d ${var.db.name} -f schema.sql",
  ]
}
Pass an object from the command line:
errand run migrate --var 'db={ host = "db.prod", name = "appdb" }'
The port field is omitted, so its default 5432 applies. Boolean variable used in a conditional command:
variable "verbose" {
  type    = bool
  default = false
}

task "test" {
  commands = [
    "go test ${var.verbose ? \"-v\" : \"\"} ./...",
  ]
}

Notes

  • Variable names must be unique across all .ern files in the playbook.
  • Access object fields with dot notation: var.db.host.
  • Access list elements by index: var.services[0].

computed block

A computed block evaluates an expression at runtime and makes the result available as computed.<name>. Use it to derive values from variables, the environment, or built-in functions without duplicating logic across tasks.
computed "<name>" {
  description = "<text>"
  expression  = <expression>
}
expression is required. description is optional.

Attributes

description
string
A human-readable description of the computed value.
expression
expression
required
The expression to evaluate. Can reference variables (var.<name>), call built-in functions, and combine values with string interpolation.

Evaluation order

Computed blocks are evaluated before tasks run, in dependency order. If a computed block references a variable, that variable is resolved first automatically. If two computed blocks reference each other, Errand reports a cycle error.

Examples

Read from the environment with a fallback:
computed "registry" {
  description = "Container registry from CI environment"
  expression  = env("CI_REGISTRY", "registry.example.com")
}

task "push" {
  commands = ["docker push ${computed.registry}/myapp:latest"]
}
Build a value from multiple inputs:
variable "image" {
  type    = string
  default = "myapp"
}

variable "tag" {
  type = string
}

computed "full_image" {
  description = "Fully qualified image reference"
  expression  = "${env("CI_REGISTRY", "registry.example.com")}/${var.image}:${var.tag}"
}

task "build" {
  commands = ["docker build -t ${computed.full_image} ."]
}

task "push" {
  depends_on = [task.build]
  commands   = ["docker push ${computed.full_image}"]
}
Chain computed values: image_tag depends on version, so Errand evaluates version first:
computed "version" {
  expression = trimspace(file("VERSION"))
}

computed "image_tag" {
  expression = "${var.app}-${computed.version}"
}

Notes

  • Computed names must be unique across all .ern files in the playbook.
  • Computed values are read-only. Tasks cannot modify them.

task block

A task is a named group of shell commands. Tasks can depend on other tasks, run conditionally, and be scoped to a specific directory.
task "<name>" {
  description = "<text>"
  working_dir = "<path>"
  condition   = <boolean expression>
  depends_on  = [<task references>]
  commands    = [<string expressions>]
}
Only commands is required.

Attributes

description
string
A human-readable description of what the task does.
working_dir
string
The directory to run commands in. Overrides the playbook-level working_dir from the errand block. Relative paths are resolved from the directory where errand was invoked.
condition
boolean expression
A boolean expression. When it evaluates to false, the task is skipped. Tasks that depend on a skipped task are also skipped.
depends_on
list of task references
An explicit list of tasks that must complete before this task runs. Write each as task.<name>. Errand also infers dependencies from expression references in commands and condition.
commands
list of string expressions
required
Shell commands to execute in order. Each command is a string expression and can use variables, computed values, and built-in functions. Commands stop at the first failure.

Examples

Task with dependencies, Errand runs test and lint before build:
task "lint" {
  commands = ["golangci-lint run ./..."]
}

task "test" {
  commands = ["go test ./..."]
}

task "build" {
  description = "Compile the binary"
  depends_on  = [task.lint, task.test]
  commands    = ["go build -o bin/app ./cmd/app"]
}
Conditional task, skip when a lock file is absent:
task "install" {
  description = "Install dependencies"
  condition   = fileexists("package-lock.json")
  commands    = ["npm ci"]
}
Aggregate task, runs dependencies only, no commands of its own:
task "ci" {
  description = "Full CI pipeline"
  depends_on  = [task.lint, task.test, task.build]
  commands    = []
}

Default task

When you call errand run with no task name, Errand runs the task named default:
task "default" {
  depends_on = [task.test, task.build]
  commands   = []
}

Notes

  • Task names must be unique across all .ern files in the playbook.
  • Commands run inside a built-in POSIX-compatible shell interpreter. No external shell is required, including on Windows.
  • If a command fails, remaining commands in the same task are skipped and the task is marked as failed.
  • Tasks that do not depend on a failed task continue to run normally.