Skip to content

Build Resources

Many builds and tests need a helper process that is not itself a build artifact — a Postgres test container, a local registry, an emulator. Declaring one as a resource lets Grog manage its lifecycle:

  • It is started at most once per invocation, no matter how many targets depend on it.
  • It starts lazily: a fully cached build never starts the resource at all.
  • It is torn down automatically when the build finishes, even when the build fails or is interrupted.
  • It is never cached and does not affect the change hash of its dependents: restarting a resource does not invalidate any target.
resources:
- name: postgres
up: |
docker run -d --rm --name "grog-pg-$GROG_RESOURCE_ID" -p 0:5432 postgres:16
PORT=$(docker port "grog-pg-$GROG_RESOURCE_ID" 5432 | head -1 | cut -d: -f2)
echo "PGPORT=$PORT" >> "$GROG_RESOURCE_EXPORTS_FILE"
ready: pg_isready -h 127.0.0.1 -p "$PGPORT"
down: docker rm -f "grog-pg-$GROG_RESOURCE_ID"
exports:
PGHOST: 127.0.0.1
targets:
- name: integration_test
dependencies: [":postgres"]
command: go test ./...

Targets depend on resources through the ordinary dependencies list. When integration_test needs to run, Grog starts the resource, waits until it is ready, and injects the exported variables (PGHOST, PGPORT) into the target’s environment.

FieldTypeDescription
namestringUnique identifier for the resource within its package
upstringCommand that starts the resource. Must return once the resource is running (daemonize it)
downstringOptional command that stops the resource
readystringOptional probe command, polled until it exits 0 before dependents run
timeoutstringBounds the start phase (up + ready polling) and, separately, down. Defaults to 5m
exportsRecord<string, string>Environment variables published to the commands of directly dependent targets
dependencieslabel[]Targets built, and resources started, before this resource starts — test targets are not allowed
  1. The first executing (i.e. not cached) target that depends on the resource triggers up. Concurrent targets wait for the same start.
  2. If ready is set, it is polled every 250ms until it succeeds or timeout is exceeded.
  3. Dependent target commands run with the resource’s exports in their environment.
  4. When the build finishes — successfully, with failures, or interrupted — down runs for every started resource in reverse start order.

If up fails or ready times out, every dependent target that needed the resource fails; down still runs at the end of the build so partial starts are cleaned up.

A resource may depend on another resource. Grog starts dependencies first and tears them down last, and the dependency’s exports are available to the dependent resource’s up, ready, and down commands:

resources:
- name: database
up: ...
exports:
DB_DSN: postgres://127.0.0.1:5432/app
- name: migrations
dependencies: [":database"]
up: migrate --database "$DB_DSN" up

Exports only flow one level: a target depending on :migrations sees the exports of :migrations, not those of :database. Declare both dependencies if the target needs both.

Static exports cover fixed values. For values only known after startup — such as a dynamically allocated host port — the up command can append KEY=VALUE lines to the file at $GROG_RESOURCE_EXPORTS_FILE. Dynamic exports override static ones with the same key and are also visible to the ready and down commands.

Grog injects the following variables into up, ready, and down:

Variable NameDescription
GROG_RESOURCEThe full label of the resource, e.g. //services/db:postgres
GROG_RESOURCE_IDA short identifier derived from the resource’s label and definition — stable across runs, changes when the definition changes. Use it to name containers so lifecycle commands stay idempotent
GROG_RESOURCE_EXPORTS_FILE(up only) File to append dynamic KEY=VALUE exports to
GROG_PACKAGEThe path to the package directory
GROG_WORKSPACE_ROOTThe workspace root directory
GROG_OS / GROG_ARCH / GROG_PLATFORMGrog’s target operating system, architecture, and platform

A resource is an ambient runtime dependency, not a build input:

  • Resources contribute nothing to the change hash of their dependents. Two runs with identical inputs are cache hits even if the resource restarted in between.
  • If a resource’s identity should invalidate a consumer — say the Postgres major version — declare it in the consumer’s fingerprint: fingerprint: { postgres: "16" }.