Skip to content

Shell Execution

OrchStep supports multiple shell types for executing commands in do: blocks. The default is POSIX sh for maximum portability across Linux, macOS, Docker containers, and CI/CD environments.

TypeEngineRequiresBest For
shell (default)/bin/shPresent on all Unix systemsMaximum portability — Alpine, distroless, all Linux/macOS
goshGo-native (mvdan/sh)Nothing — built into binaryTrue cross-platform — Windows without bash, single binary deployments
bash/bin/bashbash installedWhen you need bash-specific features (arrays, [[ ]], process substitution)
zsh/bin/zshzsh installedmacOS default shell features
pwshPowerShellpwsh installedPowerShell scripting
  • Alpine Linux (the #1 Docker base image) has no bash — only /bin/sh
  • Distroless containers have /bin/sh but not bash
  • All CI/CD runners (GitHub Actions, GitLab CI, Jenkins) have /bin/sh
  • 90%+ of OrchStep do: blocks use POSIX-compatible commands (echo, pipes, variables)
  • Zero friction — works everywhere without installing additional packages

gosh is OrchStep’s built-in Go shell interpreter powered by mvdan/sh. It executes shell commands in pure Go — no external shell binary needed.

  • Windows — run the same POSIX shell scripts without installing bash/WSL
  • Minimal containers — no shell binary needed at all
  • Single binary deployment — orchstep binary is completely self-contained
  • Sandboxed environments — no exec to external processes

gosh supports ~95% of bash syntax:

  • Variable expansion: $VAR, ${VAR:-default}, ${#VAR}
  • Command substitution: $(command)
  • Pipes and redirects: |, >, >>, 2>&1
  • Conditionals: if/then/else/fi, [ ], [[ ]]
  • Loops: for, while, until
  • Arithmetic: $(( ))
  • Functions, arrays, 74+ builtins
  • No real fork() — subshells use goroutines
  • Some bash edge cases with associative arrays
  • External commands must be on PATH

Set the shell type at any level — more specific overrides less specific.

.orchstep/orchstep_config.yml:

func:
shell:
type: "gosh" # Use Go-native shell everywhere
name: my-workflow
config:
func:
shell:
type: "gosh"
tasks:
deploy:
steps:
- name: build
func: shell
do: echo "This runs in gosh"
steps:
- name: portable-step
func: shell
args:
type: "gosh"
cmd: echo "This specific step uses gosh"
- name: bash-step
func: shell
args:
type: "bash"
cmd: echo "This step needs real bash"

Cross-Platform Workflow (Windows + Linux + macOS)

Section titled “Cross-Platform Workflow (Windows + Linux + macOS)”
name: cross-platform-build
desc: "Works on any OS without bash"
config:
func:
shell:
type: "gosh"
defaults:
version: "1.0.0"
tasks:
build:
steps:
- name: compile
func: shell
do: |
echo "Building v$version..."
echo "Platform: $(uname -s 2>/dev/null || echo Windows)"
echo "BUILD_STATUS=success"
outputs:
status: '{{ result.output | regexFind "BUILD_STATUS=(.+)" }}'
name: mixed-shells
desc: "Different shells for different steps"
tasks:
deploy:
steps:
- name: check-env
func: shell
do: echo "Using default shell (sh)"
- name: bash-specific
func: shell
args:
type: "bash"
cmd: |
declare -A config
config[env]="production"
echo "Bash arrays: ${config[env]}"
- name: portable
func: shell
args:
type: "gosh"
cmd: echo "Go-native shell — no external deps"