What Blueprint is showed what a Blueprint.roc says. This chapter makes one work on your machine. It installs blueprint, writes a first Blueprint.roc that needs nothing but the binary, locks its download, runs a task in the environment it describes, and then adds a tool from Nix.

Install blueprint§

Each release attaches a blueprint binary for x86_64 Linux, arm64 Linux and Apple Silicon macOS, named blueprint-x86_64-linux, blueprint-aarch64-linux and blueprint-aarch64-darwin, with their SHA-256 sums in blueprint.sha256. Download the one for your system from the latest release, check it and put it on your PATH:

sha256sum --check --ignore-missing blueprint.sha256
install -m 755 blueprint-x86_64-linux ~/.local/bin/blueprint

That is all the first project below needs. Nix with flakes enabled is needed for the tools a project takes from a Nix package source and for its sandboxed builds, and for nothing else: blueprint requirements says which of a project’s tasks need it. Where Nix is installed, the same version also runs from the flake:

nix run github:lukewilliamboswell/roc-blueprint/<version> -- --help
nix profile install github:lukewilliamboswell/roc-blueprint/<version>

In GitHub Actions, the setup-blueprint action installs Nix and a release’s binary and checks its sum.

Loading a configuration runs one exact Roc nightly. The binary uses a roc on PATH when it is that nightly, and otherwise downloads it from its release, checks it against a digest the binary records, and keeps it in its store of downloads. Set ROC to choose the executable yourself; it must be that same nightly.

Write a first Blueprint.roc§

Put this in Blueprint.roc at the root of your repository. The platform URL is the latest release’s:

app [config] { pf: platform "https://github.com/lukewilliamboswell/roc-blueprint/releases/download/0.11.0/EuSPs8vc71qJNFg29YzegvVRL1bR9pKqUYDHFiSCPTQ1.tar.zst" }

import pf.Recipes

config = [
	Name("my-project"),
	Environment("dev", [
		Programs([Fetch("roc-stable", Recipes.roc_nightly("nightly-2026-10-28-bad769b"))]),
		Expect(["git"]),
	]),
	Shell("default", [Use("dev")]),
	Task("version", [Use("dev"), Run(["roc-stable", "version"])]),
	Task("status", [Use("dev"), Run(["git", "status", "--short"])]),
]

Use each release’s blueprint binary with that release’s platform URL. The two are built and tested together.

The file declares one environment, dev. It has two commands:

Each task names its environment and an exact argument list.

Lock the downloads and run something§

blueprint check          # validate Blueprint.roc; needs no lock
blueprint update         # record every download's digest in Blueprint.lock
blueprint run version    # run the task's command in dev
blueprint shell          # enter dev: your own shell, with roc-stable on PATH
blueprint requirements   # what each task needs of the machine
blueprint --help         # list this project's shells and tasks

blueprint update is the only command that decides anything: it downloads the compiler for your system, hashes it, and writes the digest for every system to Blueprint.lock. Every other command uses that file and fails if it no longer matches Blueprint.roc. blueprint run then downloads nothing: the compiler is kept in a per-user store and checked against the lock on every use.

None of this starts Nix, and blueprint requirements says so:

system x86_64-linux
providers: none
task version [dev]: programs roc-stable; expects git
task status [dev]: programs roc-stable; expects git

Add a tool from Nix§

A tool that should be installed, rather than fetched as one program or expected of the machine, comes from a Nix package source. Declare the source, and write each tool with the source’s name. Inside config:

Packages("nix", From(NixPackages("github:NixOS/nixpkgs/nixos-unstable"))),
Environment("native", [Tools(["nix#zig", "nix#pkg-config"])]),
Task("zig-version", [Use("native"), Run(["zig", "version"])]),

To give native the fetched compiler and git as well, start its settings with Extend("dev").

The project now declares something Nix resolves, so blueprint update needs Nix: it pins the source’s revision in the same Blueprint.lock. After that, only what uses the source starts Nix. blueprint run zig-version does; blueprint run version still does not, on a machine with Nix or without. blueprint update nix moves that one source and keeps every other pin and digest.

Commit the lock, not the generated files§

Commit Blueprint.roc and Blueprint.lock. The lock is the authority: a versioned S-expression listing each download with its digest and each pinned source with its revision.

blueprint keeps what it generates under .blueprint/ and writes a .gitignore there, so that directory never reaches a commit. If your repository already tracks .blueprint/, untrack it once:

git rm -r --cached .blueprint

Where to go next§