Ephemeral environments that cut your cloud bill
Stop paying for infrastructure that isn't being used. Wayfinder makes a real cloud environment as easy to throw away as it was to spin up. One command brings the whole stack up, another takes it back down. App, databases, queues, buckets, DNS and IAM, all together. Like docker compose, but for the cloud.
wf up creates the environment, wf down deletes it
How it works
Every pull request gets its own stack instance, named for the PR number. When the PR closes, the environment goes with it. No tags. No cleanup scripts. No Slack reminders.
Wire it up yourself
The snippets below are the ones we use to ship Wayfinder itself. Copy them in, open a throwaway PR, and watch the preview URL appear in the comments. Close the PR and the namespace, database, queue, IAM role and DNS record all disappear with it.
Every cloud bill has a long tail of forgotten infrastructure. Four shapes it takes, and what Wayfinder does about each.
-
The six-month-old staging copy
ProblemSpun up for a demo, never deleted. Still billing for compute, storage and an idle managed database.
WayfinderThe instance's lifetime is tied to the PR, or to the developer's
.wfupsession. When the work ends, the instance ends, and every resource it created comes down with it. -
The database nobody owns
ProblemThe app is gone, but the database, the bucket, the DNS record and the IAM role around it are still live, untagged and billing.
WayfinderEvery resource Wayfinder provisions belongs to a stack instance. Delete the instance and the whole graph comes down in one ordered walk.
-
The line item you can't see
ProblemProvisioned out-of-band by a script or a console session. No tag, no owner, no record. You find it only on the bill.
WayfinderThe manifest is the only path to creating infrastructure, so Wayfinder's graph is always the source of truth for what exists and who owns it.
-
The PR you can't safely test
ProblemYou need a real environment to validate the change, but anything you spin up tends to outlive the review and bill for months.
Wayfinderwf upon PR open,wf downon PR close. The environment lives exactly as long as the review.
On pull request open, a GitHub Action runs wf up with the PR number baked into the instance name. Wayfinder reads the same Wayfinder.yaml you use in production, provisions every dependency the manifest declares, and writes the preview URL into a file. A second step posts the URL back to the PR as a comment.
What wf up actually does, step by step
Kubernetes-based app
The workflow we use to ship Wayfinder itself. The container app deploys into a per-PR namespace on a dedicated preview cluster.
deploy-ui-preview:
runs-on: ubuntu-latest
container:
image: ${{ vars.CI_WFTOOLBOX_IMAGE }}
steps:
- uses: actions/checkout@v4
- name: Deploy UI to PR preview environment
id: deploy
env:
WAYFINDER_SERVER: ${{ vars.CI_WAYFINDER_SERVER }}
WAYFINDER_TENANT: ${{ vars.CI_WAYFINDER_TENANT }}
WAYFINDER_WORKSPACE: ${{ vars.CI_WAYFINDER_WORKSPACE }}
WAYFINDER_ENVIRONMENT: ${{ vars.CI_WAYFINDER_ENV_DEVELOP }}
WAYFINDER_TOKEN: ${{ secrets.UI_PREVIEWS_WAYFINDER_TOKEN }}
run: |
export ENV="wfpreviews-pr${{ github.event.pull_request.number }}"
wf up -i ${ENV} \
--target k8s=${WAYFINDER_WORKSPACE}/${WAYFINDER_ENVIRONMENT}/${{ vars.DEVELOP_HOST_CLUSTER }}:${ENV} \
--dns-zone previews.dev.wayfinder.run \
--out-file ./preview.json
echo "PREVIEW_URL=$(jq -r '.componentOutputs.wfui.url.value' ./preview.json)" >> $GITHUB_OUTPUT
--target k8s=... picks the host cluster and the namespace to land in. --dns-zone says where to publish the preview URL. --out-file is how the URL gets back to the PR comment job; the last line plucks it out of the JSON.
Non-Kubernetes app (serverless on AWS)
The same wf up handles a fully serverless stack (Lambdas, API Gateway, DynamoDB, S3, CloudFront) when that's what the manifest declares. No --target k8s; just the manifest and a DNS zone. The PR number still drives the instance name, the close workflow still finds it the same way.
deploy-api-preview:
runs-on: ubuntu-latest
container:
image: ${{ vars.CI_WFTOOLBOX_IMAGE }}
steps:
- uses: actions/checkout@v4
- name: Deploy AWS preview environment
env:
WAYFINDER_SERVER: ${{ vars.CI_WAYFINDER_SERVER }}
WAYFINDER_TENANT: ${{ vars.CI_WAYFINDER_TENANT }}
WAYFINDER_WORKSPACE: ${{ vars.CI_WAYFINDER_WORKSPACE }}
WAYFINDER_ENVIRONMENT: ${{ vars.CI_WAYFINDER_ENV_DEVELOP }}
WAYFINDER_TOKEN: ${{ secrets.API_PREVIEWS_WAYFINDER_TOKEN }}
run: |
export ENV="apipreviews-pr${{ github.event.pull_request.number }}"
export VERSION="pr${{ github.event.pull_request.number }}-${{ github.sha }}"
wf up -f ./aws/Wayfinder.yaml \
-i ${ENV} \
--dns-zone previews.dev.example.com \
--env-var VERSION=$VERSION \
--out-file ./preview.json
The result, in either case, is a real working environment shaped like production (the same manifest, the same identity model, the same dependencies) that reviewers can click before merge. Different runtime, identical lifecycle.
The half of the story that keeps the bill flat. Once the PR closes (most often a clean merge after review, but also if it's declined or simply abandoned) a separate workflow runs wf down against the same instance name. Wayfinder walks the instance's resource graph and removes every node on it.
name: Pull Request closed
on:
pull_request:
types: [closed]
jobs:
teardown-ui-preview:
runs-on: ubuntu-latest
container:
image: ${{ vars.CI_WFTOOLBOX_IMAGE }}
steps:
- uses: actions/checkout@v4
- name: Clean up preview environment
env:
WAYFINDER_SERVER: ${{ vars.CI_WAYFINDER_SERVER }}
WAYFINDER_WORKSPACE: ${{ vars.CI_WAYFINDER_WORKSPACE }}
WAYFINDER_ENVIRONMENT: ${{ vars.CI_WAYFINDER_ENV_DEVELOP }}
WAYFINDER_TENANT: ${{ vars.CI_WAYFINDER_TENANT }}
WAYFINDER_TOKEN: ${{ secrets.UI_PREVIEWS_WAYFINDER_TOKEN }}
run: |
export ENV="wfpreviews-pr${{ github.event.pull_request.number }}"
wf down -e ${WAYFINDER_ENVIRONMENT} -i ${ENV} || true
Wayfinder walks the resource graph
Every wf up records what it created: the workload, every managed resource that backed it, every binding between them, every credential it minted. wf down traverses that graph in dependency order, deleting leaves first so nothing gets stranded with a parent still alive. No tag scans, no "best effort". Wayfinder removes the things it knows it provisioned, in the order they can safely come down.
What goes, in order
If any single step fails (a Kubernetes deployment that won't drain, a network that won't delete, a bucket with delete protection turned on) Wayfinder pauses on that node, surfaces it in the job log, and lets you re-run wf down to resume. A half-deleted instance never silently turns into an orphan.
The same primitives that drive PR previews drive your inner dev loop. wf up on your laptop creates a personal stack instance with the same Wayfinder.yaml, the same managed dependencies, and the same identity model as production.
From your AI editor
Wayfinder's MCP server plugs into Claude Code, Cursor, Continue or any MCP-capable AI editor. Your agent can spin a stack up or take it down with a single instruction, and read the deploy result and running workload's logs without you leaving the conversation.
From the terminal
The same flow, scriptable, for when you'd rather drive it from a shell, or wrap it in a Makefile, a justfile, or a pre-commit hook.
wf use workspace <your-workspace>
wf use environment dev
# No -i flag: Wayfinder generates a friendly
# name (e.g. "busybeaver") and remembers it.
wf up -f ./Wayfinder.yaml
# Iterate. Wayfinder updates the same instance.
wf up -f ./Wayfinder.yaml
# Done for the day.
wf down
Each developer (or each team) gets their own stack instance in the shared dev environment. Run wf up again later and Wayfinder updates that instance instead of spawning a new one. Multiple instances run side by side in the same environment, so teams can iterate and test in parallel without tripping over each other, and any one of them can be torn down without affecting the others.
Why “like production” matters
The PR close hook does the heavy lifting. A handful of patterns on top take preview spend to a rounding error, catch the rare straggler, and turn “what is this thing?” into a question you never have to ask.
Make orphans impossible to hide
Cap blast radius before it starts
The pull request's open/closed state is the signal. Everything else flows from owning that one workflow.
See it on your own repo
Book a 20 minute demo. We'll wire per-PR environments into one of your services. Open a throwaway PR, see the preview URL land in the comments, close the PR, watch the environment vanish.