Your first coordinated workflow
This tutorial makes desired-state configuration concrete: you request a built package, declare what it needs, and let Terrabuild determine the work.
You will create a tiny workspace with two projects: one prepares a message, and the other packages it. The tools are deliberately simple so you can see what Terrabuild contributes: dependencies, execution order, and reusable results. The same configuration model works with compilers, package managers, and deployment tools.
You need Terrabuild, Git, and a POSIX shell (sh, available on
macOS and Linux). The commands below use Bash-compatible syntax. No container
engine or Insights account is needed.
1. Create a workspace
In a new directory, run:
mkdir terrabuild-tutorial
cd terrabuild-tutorial
git init
mkdir message package
Create these files using the names shown above each example.
workspace { }
target build {
depends_on = [ target.^build ]
artifacts = ~workspace
}
WORKSPACE holds shared rules. Here, every build target waits for build
in its dependency projects. artifacts = ~workspace tells Terrabuild to keep
outputs in its local cache.
**/dist/
Generated files belong in dist/; they are outputs, not source inputs.
2. Define the producer
Hello from Terrabuild!
set -eu
mkdir -p dist
cp message.txt dist/message.txt
project message {
outputs = [ "dist/**" ]
}
target build {
@shell sh { args = "build.sh" }
}
A PROJECT file describes one unit of work. The project block names it and
declares its outputs. The target block supplies the command for build.
@shell is an included extension. Here it invokes sh build.sh in the project
directory. Terrabuild supplies the coordination; the shell script produces the file.
3. Define the consumer
set -eu
mkdir -p dist
cat ../message/dist/message.txt > dist/package.txt
printf 'Packaged successfully.\n' >> dist/package.txt
project package {
depends_on = [ project.message ]
outputs = [ "dist/**" ]
}
target build {
@shell sh { args = "build.sh" }
}
The two dependency declarations work together:
project.messagesays that the package consumes the message project.target.^buildinWORKSPACEsays which work it needs from that project.
The arrow means “must finish before.” A project relationship alone does not specify which command should run first; the target relationship supplies that rule.
Your directory should now look like this:
terrabuild-tutorial/
├── .gitignore
├── WORKSPACE
├── message/
│ ├── PROJECT
│ ├── build.sh
│ └── message.txt
└── package/
├── PROJECT
└── build.sh
Record the starting files so the tutorial has a Git history:
git add .
git commit -m "Add tutorial workspace"
4. Inspect, then run
From the workspace root:
terrabuild explain build --project package
Look for package:build and its prerequisite message:build. Selecting a
project does not remove the prerequisites it needs. explain resolves the
configuration and extension operations without executing those operations.
Now run the same selection:
terrabuild run build --project package
cat package/dist/package.txt
Expected file contents:
Hello from Terrabuild!
Packaged successfully.
Terrabuild runs message:build before package:build. With several independent
projects, it can execute independent branches concurrently.
5. Reuse the result
Run the same command again:
terrabuild run build --project package
The inputs have not changed, so Terrabuild can reuse both successful results and report that everything is up to date.
To see file restoration, add an action that consumes the built package and runs
on every request. Declare the selectable target and its policy in WORKSPACE:
target inspect {
depends_on = [ target.build ]
build = ~always
artifacts = ~none
}
Append its command to package/PROJECT:
target inspect {
@shell cat { args = "dist/package.txt" }
}
Run it once to establish the result with the updated configuration:
terrabuild run inspect --project package
Then remove only the generated directories and request that consumer again:
rm -r message/dist package/dist
terrabuild run inspect --project package
cat package/dist/package.txt
The file contains the same package contents. Terrabuild restores the prerequisite
files before the consumer reads them. A fully cached build request can finish
without executing or restoring tasks; the always-running consumer makes the
need for those files explicit.
Restoration works because you declared both an artifact policy and output paths.
The generic @shell extension does not assume arbitrary commands are cacheable;
the workspace policy explicitly makes these repeatable builds cacheable. The
inspect target overrides that policy with an action that runs every time.
6. Change the producer
printf 'Hello from a changed input!\n' > message/message.txt
terrabuild explain build --project package
terrabuild run build --project package
cat package/dist/package.txt
The message input changes, so its result changes. The package depends on that
result and must run again. Its output now contains the new greeting followed by
Packaged successfully.
You have described a dependency once and used it for both ordering and change propagation. You did not need a separate script that manually runs the projects in sequence.
Take it further
- Adopt Terrabuild in an existing repository: use your native tools and add projects progressively.
- Configure environments: keep reusable work separate from context-specific work.
- Customize your tools: replace this shell invocation with a small FScript extension.
- Explore advanced scenarios: combine generated code, toolchains, deployments, and CI.