Runners¶
Runners define how commands are executed. A runner wraps process execution so you can run jobs on the host, inside Docker containers, or in any custom environment.
Defining a runner¶
runner_type "docker" {
run {
path = "docker"
args = [
"run", "--rm",
"-v", "$WORKDIR:/workdir",
"-w", "/workdir",
"$env",
"$args",
"$image",
"/bin/sh", "-ec", "command -v git >/dev/null && git config --global --add safe.directory '*'; $cmd",
]
}
}
| Field | Required | Description |
|---|---|---|
name |
yes | Label on the block |
source |
no | URL to fetch the definition from (mutually exclusive with inline run) |
run |
yes* | Block with path and args |
path |
no | Executable path |
args |
no | List of arguments |
* Not required when source is set.
Variable expansion¶
Inside path and args, PikoCI expands:
| Variable | Description |
|---|---|
$WORKDIR |
Temporary working directory for the job |
$<param> |
Any parameter passed from a run block in a task |
$args |
Replaced with the user-provided args list from the run block, passed through without variable expansion |
$env |
Replaced with -e KEY=VALUE flags for every parameter and env var, including exported step metadata (GET_*, TASK_*) |
For example, when a task uses run "docker" { image = "golang:1.25" cmd = "make test" }, the runner receives $image and $cmd as expandable variables.
The $env placeholder is used by the Docker runner to forward all parameters (including step metadata exported by get and task steps) into the container as environment variables. Without it, env vars set on the host process would not be visible inside the container.
Sourcing from URL¶
Instead of defining the runner inline, you can point to an external HCL file:
Two URL formats are supported:
pikoci://<name>resolves to the PikoCI registry. For shipped built-ins (exec,docker,shell), the embedded definition is used directly (no network call).https://...orhttp://...fetches HCL from any URL.
When source is set, you must not define an inline run block. PikoCI will error if both are present.
Note: The source is resolved once when the pipeline is created or updated. If the remote HCL file changes, you must re-set the pipeline to pick up the new definition.
Overriding built-ins¶
All built-in runners (exec, docker, shell) can be overridden by defining a runner_type block with the same name in your pipeline. Inline definitions always take precedence over built-ins.
This is useful when you need different default behavior. For example, the built-in docker runner uses /bin/sh -ec to run commands. If you want to always run with --network=host or use a different shell:
runner_type "docker" {
run {
path = "docker"
args = [
"run", "--rm",
"--network=host",
"-v", "$WORKDIR:/workdir",
"-w", "/workdir",
"$env",
"$args",
"$image",
"/bin/bash", "-ec", "$cmd",
]
}
}
This replaces the built-in docker runner entirely for this pipeline.
Type-level runner overrides¶
By default, a type's commands run using the runner specified in their command blocks (e.g. check "exec" { ... }). You can override this for all commands of a type by adding a runner block to the type definition.
This is useful when a type uses exec to run commands directly on the host, but you want all its operations to run inside Docker, for example, to provide the right CLI tools without installing them on the host.
Resource type¶
All check, pull, and push commands for this resource type will run inside the specified Docker image instead of directly on the host.
Notification type¶
notification_type "slack" {
source = "pikoci://slack"
runner "docker" {
image = "curlimages/curl:latest"
}
}
Secret type¶
secret_type "vault" {
source = "pikoci://vault"
runner "docker" {
image = "hashicorp/vault:latest"
}
}
Service type¶
service_type "postgresql" {
source = "pikoci://postgresql"
runner "docker" {
image = "docker:latest"
}
}
Passing extra Docker flags¶
Override params are passed to the runner template as variables. The built-in docker runner supports image, cmd, and args. Use args to pass extra docker run flags like volumes, environment variables, or privileged mode:
resource_type "git" {
source = "pikoci://git"
runner "docker" {
image = "alpine/git:latest"
args = ["-v", "/var/run/docker.sock:/var/run/docker.sock", "-e", "GIT_SSH_COMMAND=ssh -o StrictHostKeyChecking=no"]
}
}
Restrictions¶
- Only exec commands can be overridden. If any command in the type already uses a non-exec runner, specifying a runner override is an error.
- The override applies to all commands of the type (check/pull/push for resource types, start/stop/ready_check for services, etc.).
- Override parameters (like
image,args) are merged with the command's parameters. - Works with sourced types: you can use
source = "pikoci://git"and add arunnerblock to change where the sourced commands execute.
Using a runner¶
Reference a runner by name in task, on_success, on_failure, ensure, and resource type check/pull/push blocks:
job "build" {
get "git" "repo" {
trigger = true
}
task "compile" {
run "docker" {
image = "golang:1.25"
cmd = "make build"
}
}
}
Parameters in the run block (like image and cmd above) are passed to the runner as environment variables.
Built-in: exec¶
The exec runner is built in. It runs commands directly on the host machine:
The exec runner expands $path and $args from the run block:
You do not need to declare the exec runner in your pipeline. It is always available.
Built-in: docker¶
The docker runner is built in. It runs commands inside Docker containers:
Params¶
| Param | Required | Description |
|---|---|---|
image |
yes | Docker image to run |
cmd |
yes | Shell command to execute inside the container |
args |
no | Extra docker flags (env, volumes, etc.) |
The docker runner mounts $WORKDIR as /workdir inside the container and runs the command with /bin/sh -ec. It also automatically configures git config --global --add safe.directory '*' if git is available, so cloned repositories work correctly inside containers.
Extra docker flags¶
Use the args parameter to pass additional flags to docker run:
task "test" {
run "docker" {
image = "golang:1.25"
cmd = "make test"
args = ["-e", "CI=true", "-e", "FOO=bar"]
}
}
With volumes:
task "test" {
run "docker" {
image = "golang:1.25"
cmd = "make test"
args = ["-v", "/data:/data"]
}
}
With privileged mode:
task "build-image" {
run "docker" {
image = "docker:latest"
cmd = "docker build -t myapp ."
args = ["--privileged"]
}
}
Using HCL functions to build args dynamically:
task "test" {
run "docker" {
image = "golang:1.25"
cmd = "make test"
args = concat(
["-e", "CI=true"],
["-e", "FOO=bar"],
["-v", "/cache:/cache"],
)
}
}
Built-in: shell¶
The shell runner simplifies running shell commands in tasks and hooks. It replaces the common run "exec" { path = "/bin/sh" args = ["-ec", "..."] } pattern. It has two mutually exclusive modes: inline (cmd) and file (file).
Inline mode (cmd)¶
Runs a shell command string via <shell> -ec "<cmd>":
File mode (file)¶
Runs a script file. Relative paths are resolved against the build working directory:
When no shell param is set, the file is executed directly (chmod +x is applied), so the OS uses the script's shebang line. When shell is set, the file is passed as an argument to the specified shell.
Params¶
| Param | Required | Description |
|---|---|---|
cmd |
no* | Shell command string to run inline |
file |
no* | Path to a script file to execute |
shell |
no | Shell binary to use (default /bin/sh) |
* Exactly one of cmd or file must be set.
Custom shell¶
Both modes accept an optional shell param to override the default /bin/sh:
task "test" {
run "shell" {
shell = "/bin/bash"
cmd = "set -o pipefail; make test 2>&1 | tee test.log"
}
}
When to use shell vs exec¶
Use shell for shell commands and scripts. Use exec when you need to run a binary directly with specific arguments.