Working Directories
How OrchStep decides where commands run, the dir: field, and running a workflow from a different directory than its orchstep.yml.
Working Directories
When OrchStep runs a shell command, it runs it somewhere. This guide explains
exactly where, how to change it with dir:, and how to run a workflow whose
orchstep.yml lives somewhere other than your current shell directory.
The two directories that matter
- Your shell's current directory (cwd) — where you are when you type
orchstep …. - The workflow directory — the folder that contains the
orchstep.ymlbeing run. This is the one that matters for command execution.
The single rule to remember:
By default, commands run in the workflow directory — not your shell's cwd — and relative
dir:paths resolve against the workflow directory.
This is deliberate: it makes a workflow behave the same no matter where you launch it from, which is what you want in scripts, CI, and shared repos.
The default: the workflow directory
With no dir:, every shell command runs in the folder holding orchstep.yml:
# ~/myrepo/orchstep.yml
tasks:
build:
steps:
- name: where
func: shell
do: pwd # prints ~/myrepo (the workflow directory)dir: — choose the directory
dir: is valid at workflow, task, and step level.
# ~/myrepo/orchstep.yml
tasks:
test:
dir: backend # every step in this task runs in ~/myrepo/backend
steps:
- name: deps
func: shell
do: go mod download # ~/myrepo/backend
- name: api
func: shell
dir: backend/api # this step overrides the task -> ~/myrepo/backend/api
do: go test ./...Precedence (most specific wins):
step dir: > task dir: > workflow dir: > the workflow directory (default)A step's dir: replaces the task's dir: — it does not nest under it. Both
resolve from the workflow directory, so write the full sub-path on the step
(backend/api, not api).
Path rules:
| You write | Resolves to |
|---|---|
dir: backend (relative) | <workflow dir>/backend |
dir: ../sibling | one level up from the workflow dir |
dir: /opt/app (absolute) | /opt/app as-is |
dir: ~/work | your home directory + /work |
dir: "{{ vars.service }}" | template-resolved first, then the above rules |
If the directory doesn't exist, the step fails fast and tells you the resolved path:
task 'test', step 'api': working directory 'backend/api' not found (resolved to /…/backend/api)
Check the dir: value — relative paths resolve against the workflow file's directory.Running from a different directory than orchstep.yml
You have three ways to run a workflow. The first is simplest; the others let you run from anywhere.
1. cd into the project (simplest)
cd ~/myrepo
orchstep run testYour cwd equals the workflow directory, so everything is intuitive.
2. Point at the file with -f / --file (run from anywhere)
# from literally any directory:
orchstep run test -f ~/myrepo/orchstep.ymlThe workflow directory is taken from the file's location (~/myrepo), not
your cwd. So dir: backend still resolves to ~/myrepo/backend, even though you
ran the command from /tmp or your home directory. Your cwd does not leak into
where commands run.
Use an absolute
-fpath for full independence. A relative-f(e.g.-f sub/orchstep.yml) is resolved against your cwd, so the workflow directory becomes<cwd>/sub. That's fine — just know that relative-fis the one case where your cwd participates.
3. A shell alias or wrapper
For a workflow you run often from elsewhere, alias the file path:
alias deploy='orchstep run -f ~/myrepo/deploy.orchstep.yml'
deploy productionWhat about other path flags?
CLI path flags like --file and --vars-file are resolved relative to your
shell cwd (they're things you type at the prompt). dir: is different — it
governs where the workflow's commands execute, anchored to the workflow file.
Keep the distinction in mind: flags = cwd-relative, dir: = workflow-relative.
Best practices by use case
Monorepo — one workflow at the root
Put orchstep.yml at the repo root and give each task the service it drives:
# ~/repo/orchstep.yml
tasks:
web: { dir: services/web, steps: [ { name: build, func: shell, do: npm run build } ] }
api: { dir: services/api, steps: [ { name: test, func: shell, do: go test ./... } ] }
infra: { dir: infra/terraform, steps: [ { name: plan, func: shell, do: terraform plan } ] }Run any of them from anywhere with -f ~/repo/orchstep.yml, or cd ~/repo first.
No cd clutter inside do:.
Umbrella workflow over sibling repos
A workflow that orchestrates neighboring checkouts (like an ops dashboard)
uses ../ paths relative to where the umbrella orchstep.yml sits:
# ~/code/orchstep.yml (siblings: ~/code/frontend, ~/code/backend)
tasks:
build-all:
steps:
- { name: fe, func: shell, dir: ../code/frontend, do: npm run build }
- { name: be, func: shell, dir: ../code/backend, do: go build ./... }Prefer relative paths (../code/backend) over absolute (/Users/you/code/backend)
so the workflow is portable across machines and teammates.
CI pipelines
The workflow file is the anchor, so CI is predictable regardless of the runner's starting directory:
orchstep run deploy -f "$GITHUB_WORKSPACE/orchstep.yml"When you genuinely want the caller's directory
Relative dir: is workflow-relative by design. If a task should act on wherever
the user is, pass it in explicitly rather than relying on cwd:
orchstep run lint --var target="$PWD"tasks:
lint:
steps:
- { name: run, func: shell, dir: "{{ vars.target }}", do: golangci-lint run }Rules of thumb
- Keep
dir:relative for portability; reserve absolute paths for true system locations (/opt,/var). - Set
dir:at the task level when all of a task's steps share a directory; override on a single step only when it differs. - Don't
cdinsidedo:— usedir:. It's clearer, it's validated, and the inspector/plan show the real directory. - Run with an absolute
-ffrom scripts and CI for cwd-independence.
See also
- Working Directory reference — the precise field spec.
- Project Layout — where to put workflow and task files.