When AWS archived Rain, we decided to build something better suited to how we ship. jrcft is a fast, focused build tool for CloudFormation templates, and it's open for anyone to use.
We keep it lean on purpose. Rather than chase full feature parity with Rain, we implement exactly what our templates need and grow the feature set as the community asks for more.
Just as important, jrcft never talks to the AWS API. There are no credentials to wire up and no network round trips to wait on, only fast, local template transformation. That tight focus on transforming templates is what keeps it quick.
jrcft reads your templates in YAML or JSON, runs them through a set of transform passes, and writes clean output as YAML, JSON, or both. A small set of custom tags keeps everything DRY and configurable.
jrcft src/*.yml -o dist -f yml,json| Tag | Description |
|---|---|
!Craft::Env <NAME> |
Resolves to the value of environment variable NAME. Errors if it's unset. |
!Craft::Embed <path> |
Inlines the contents of the file at path, relative to the template. |
!Craft::Template <path> |
Writes the processed template into the output directory under its S3 key, and resolves to its https:// URL. Only allowed on a stack's TemplateURL. |
Environment variables come from your shell or any env file you load.
!Craft::Template only works on the TemplateURL of an AWS::CloudFormation::Stack resource.
It processes the template it points at first, so a nested template can use these tags too.
The S3 key is a hash of the processed template, so the URL only changes when the content changes.
And jrcft doesn't upload anything.
It writes the processed template into the output directory alongside everything else, and you sync that directory to your bucket.
jrcft <source>... [flags] [-- KEY=VALUE ...]| Flag | Description |
|---|---|
-o, --output <dir> |
Directory to write into, created if it doesn't exist (default ./dist) |
-n, --output-name <name> |
Output file name without the extension (default [name]). Supports placeholders. |
-f, --format <list> |
Output formats: yml, json, or both, comma-separated (default yml) |
--s3-bucket <bucket> |
S3 bucket used in the !Craft::Template URLs. Required when a template uses it. |
--s3-region <region> |
S3 region used in the !Craft::Template URLs. Required when a template uses it. |
--s3-prefix <prefix> |
S3 key prefix, and the subdirectory under --output. Supports placeholders. |
--skip <passes> |
Passes to skip: craft-env, craft-template, craft-embed, craft-spacing |
--env-file <files> |
Env file to load (default .env). Repeatable. |
--output-name and --s3-prefix both support [...] placeholders.
[name] is the input template's base name without the extension.
[<anything>] is any environment variable, lowercased, so [version] pulls in VERSION.
# app.yml -> dist/app-v2.yml
VERSION=v2 jrcft app.yml -o dist -n '[name]-[version]'By default jrcft loads a .env from the current directory if one is there.
Those values only fill in variables that aren't already set, so your shell always wins.
They feed both !Craft::Env lookups and placeholders.
Pass --env-file to load other files instead, and you can pass it more than once.
It's the one option you can't set with an env var.
Pointing it at a file that doesn't exist is an error.
jrcft src/app.yml --env-file base.env --env-file prod.envAnything you pass after -- is a KEY=VALUE pair that gets injected into the environment for that run.
Those show up in !Craft::Env lookups and in placeholders, same as anything from your shell or an env file.
This is handy in CI when you want to pass a build value straight in without exporting it first.
Inline vars win over everything.
If the same key is set in your shell or an env file, the inline value takes over.
The value can have = signs in it, since only the first one splits the key from the value.
# in.yml -> dist/app-abc123.yml, with Build resolving to abc123
jrcft in.yml -o dist -n 'app-[hash]' -- HASH=abc123Every flag except --env-file can also be set with a JRCFT_-prefixed env var.
The flag wins if you pass both.
| Environment Variable | Flag |
|---|---|
JRCFT_OUTPUT |
-o, --output |
JRCFT_OUTPUT_NAME |
-n, --output-name |
JRCFT_FORMAT |
-f, --format |
JRCFT_S3_BUCKET |
--s3-bucket |
JRCFT_S3_REGION |
--s3-region |
JRCFT_S3_PREFIX |
--s3-prefix |
JRCFT_SKIP |
--skip |
These can live in an env file too, so a project's .env can pin its whole config.
Grab a build from Releases.
Swap 0.0.2 for the version you want.
Debian
curl -LO https://github.com/jetrails/jrcft/releases/download/0.0.2/jrcft_0.0.2_amd64.deb
sudo dpkg -i ./jrcft_0.0.2_amd64.deb
rm ./jrcft_0.0.2_amd64.debcurl -LO https://github.com/jetrails/jrcft/releases/download/0.0.2/jrcft_0.0.2_arm64.deb
sudo dpkg -i ./jrcft_0.0.2_arm64.deb
rm ./jrcft_0.0.2_arm64.debRed Hat
curl -LO https://github.com/jetrails/jrcft/releases/download/0.0.2/jrcft-0.0.2-1.aarch64.rpm
sudo rpm -i ./jrcft-0.0.2-1.aarch64.rpm
rm ./jrcft-0.0.2-1.aarch64.rpmcurl -LO https://github.com/jetrails/jrcft/releases/download/0.0.2/jrcft-0.0.2-1.x86_64.rpm
sudo rpm -i ./jrcft-0.0.2-1.x86_64.rpm
rm ./jrcft-0.0.2-1.x86_64.rpmAlpine
curl -LO https://github.com/jetrails/jrcft/releases/download/0.0.2/jrcft_0.0.2_aarch64.apk
apk add --allow-untrusted ./jrcft_0.0.2_aarch64.apk
rm ./jrcft_0.0.2_aarch64.apkcurl -LO https://github.com/jetrails/jrcft/releases/download/0.0.2/jrcft_0.0.2_x86_64.apk
apk add --allow-untrusted ./jrcft_0.0.2_x86_64.apk
rm ./jrcft_0.0.2_x86_64.apkArch
curl -LO https://github.com/jetrails/jrcft/releases/download/0.0.2/jrcft-0.0.2-1-aarch64.pkg.tar.zst
sudo pacman -U ./jrcft-0.0.2-1-aarch64.pkg.tar.zst
rm ./jrcft-0.0.2-1-aarch64.pkg.tar.zstcurl -LO https://github.com/jetrails/jrcft/releases/download/0.0.2/jrcft-0.0.2-1-x86_64.pkg.tar.zst
sudo pacman -U ./jrcft-0.0.2-1-x86_64.pkg.tar.zst
rm ./jrcft-0.0.2-1-x86_64.pkg.tar.zstmake buildThe binary lands in the dist directory, under subdirectories for the OS and architecture.
Tests live in the test/ directory as .test fixtures and run through a golden-file runner.
Run the suite with:
make testIf you change behavior on purpose, regenerate the expected output with UPDATE=1 make test and review the diff before you commit it.