See what is waiting.
Deploy it.
A deploy dashboard in one GitHub issue, for infrastructure as code. After a merge it previews the stacks that changed and lists what is waiting; you tick a stack's box and GitHub Actions deploys exactly that stack.
The app opens a pull request that sets up the action. Or run the action yourself: open source, Apache-2.0, released as v0.50.0, in beta.
🟡 4 pending · 🟠 2 drifted · 🔵 2 deploying · ⚪ 0 preview failed · 🟢 8 in sync · ⚠️ 2 pending stacks delete or replace resources
Scanned 34e410f on 2026-09-21 10:02 UTC · run · last full scan 2026-09-21 06:00 UTC
Deploying
apps/api:prod · deploying · ticked by alice · run
from #512 by alice · compareapps/worker:prod · queued behind apps/api:prod · ticked by alice · run
from #509 by bob · compare
Updates waiting to merge
Tick a box to merge that pull request. Its stack is then previewed again and deployed as that preview shows it.
- platform/ingress-nginx:prod · Update Helm release ingress-nginx to v4.13 · #519 by renovate[bot] · preview after the merge: 1 update
These wait on their own checks. Each gets a box here once its checks are green.
- apps/web:prod · Update dependency next to v15.5 · #521 by renovate[bot] · waits on its checks
Pending
Tick a box to deploy that stack exactly as its row shows it.
Caution
2 pending stacks delete or replace resources: apps/legacy-worker:prod, infra/network:prod
1 drifted stack has resources gone outside the code: platform/external-dns:prod
-
· 1 create, 1 update · preview
from #514 by erin, #511 by renovate[bot] · compare2 changes
updatekubernetes:apps/v1:Deploymentbilling ·spec.replicas2→3
createkubernetes:monitoring.coreos.com/v1:ServiceMonitorbilling
-
· 3 deletes, 1 tracking only · preview
from #498 by dave · compare
⚠️ DELETEaws:sqs/queue:Queuelegacy-jobs
⚠️ DELETEkubernetes:apps/v1:Deploymentlegacy-worker
⚠️ DELETEkubernetes:core/v1:Servicelegacy-worker1 other change
forgetaws:iam/role:Rolelegacy-worker
-
· 2 creates, 1 update · preview
from #516 by carol, #510 by bob, and 1 change outside this stack · compare3 changes
updatekubernetes:apps/v1:Deploymentweb ·metadata.labels["app.kubernetes.io/version"],spec.template.spec.containers[0].image
createkubernetes:autoscaling/v2:HorizontalPodAutoscalerweb
createkubernetes:core/v1:ConfigMapweb-feature-flags
changes outside this stack
#497 by frank
-
· 1 update, 1 replace · preview
from 11fa403 by gina · compare
⚠️ REPLACEaws:ec2/subnet:Subnetprivate-b · forced bycidrBlock1 other change
updateaws:ec2/routeTable:RouteTableprivate ·routes[1].natGatewayId
-
Deploy all 4 pending stacks
Drifted
Real infrastructure changed outside the code. Deploying a stack puts it back as its code says.
-
monitoring/grafana:prod · 1 changed outside the code · preview
1 change outside the code
changedkubernetes:apps/v1:Deploymentgrafana ·spec.replicas
-
platform/external-dns:prod · 1 gone outside the code · preview
1 change outside the code
goneaws:route53/record:Recordstatus-cname
-
Confirm: repair all 2 drifted stacks: monitoring/grafana:prod, platform/external-dns:prod · asked by carol
Ticking this deploys each stack as its row shows it, in dependency order. A change to these rows first takes it back.
In sync
8 stacks in sync
- apps/api:staging
- apps/auth:prod
- apps/auth:staging
- apps/billing:staging
- apps/web:prod
- data/postgres:prod
- data/postgres:staging
- platform/ingress-nginx:prod
1 stack left out by ignore
- sandbox/playground:dev · a scratch stack, deployed by hand
Recently deployed
Times are in UTC.
- 🟢 apps/auth:prod · alice · 09-21 09:41 · run
shipped #513 by alice · compare - 🟢 data/postgres:prod · deployed outside the dashboard, from
35bf0b7· 09-21 09:30 - ⚪ apps/auth:staging · no changes · alice · 09-21 09:12 · run
- 🟢 platform/ingress-nginx:prod · drift fixed · carol · 09-20 18:05 · run
- 🟢 apps/web:prod · bob · 09-20 16:52 · run
shipped #510 by bob · compare - 🔴 apps/web:prod · failed · bob · 09-20 16:40 · run
- Rescan all stacks
Sluiceway v0.50.0 · docs
This is the example dashboard. Ticking a box here only moves the example; nothing is deployed.
How it works
Nothing deploys until someone ticks a stack's box, unless your own sluiceway.yaml sets a stack to deploy on merge. A tick deploys only what its row shows.
A scan previews the stacks
After a merge to the default branch, the stacks that merge changed. Once a day, every stack.
The scan writes the dashboard issue
One row per stack, and a box on every stack that has changes waiting. A box under the pending rows deploys them all, and one under the drifted rows repairs them all. Neither does anything until you also tick its confirm box.
You tick a box
That asks for that stack to be deployed exactly as its row shows it.
It previews again, then deploys
Only if the fresh preview still matches the row. Otherwise the row comes back with the new diff.
The dashboard at the top has stacks waiting. This is its header once they deploy: nothing pending. The header picture changes with every scan to show what it found.
What is pending is always worked out again from a fresh preview.
- Previews and deploys run in your own runners, with your own credentials. Sluiceway never holds a cloud credential, in any tier.
- The issue is a view, never the source of truth. Before every deploy the stack is previewed again, and nothing happens unless the fresh preview still matches what was ticked.
- No property value reaches the dashboard unless the repo lists the path. No telemetry from the action.
These hold with the app and without it. Every deploy runs in your own GitHub Actions; the app holds no cloud credential and runs no infrastructure code.
The action, set up for you, with a console
Install the GitHub App and it opens a pull request on each repo you pick, adding the workflow and a starter sluiceway.yaml. It never replaces the action: every deploy still runs in your own GitHub Actions, with your own cloud credentials. How the app and the action work together.
What it adds
- The app picks a tick up within about a second. On a repo whose
sluiceway.yamlnames the app, it hears the edit of the issue, judges the tick with the action's own code, opens the deployment record and starts your workflow, which then waits for a runner as any run does. The bulk, merge and rescan boxes, a drift repair, and a stack with dependencies, a phase, deploy on merge or a deploy window stay with the action's ownresolve. It is the fallback too, so if the app is down a tick still deploys, and starts in about 20 seconds as it does without the app. - The console and the org view. Every stack of every repo, grouped by what needs a person: needs you, in flight, in sync.
- Tick from the console and the command line. A tick there is a tick by the signed-in person, judged with the action's own rule and their live permission, on a repo whose
sluiceway.yamlhasrecordWriters: ["sluiceway[bot]"]. The onboarding pull request adds that line. - The audit log and Insights. One line per deploy: who ticked or who merged, which stack, which commit, when, with a flag on a deploy made outside the dashboard. Insights shows lead time from merge to deploy, deploys per week and change failure rate.
- A required second approver, on Team and Team Plus. GitHub holds the deploy job until a second person, not the ticker, approves it in the console. It is a GitHub deployment protection rule, which GitHub offers on public repos and on GitHub Enterprise.
- Edit mode. The dashboard as an editor: every change redraws it with the real diff of
sluiceway.yaml, and Save opens one pull request. A person merges it; Sluiceway never writes the config itself.
- It never holds a cloud or infrastructure credential. Previews and deploys keep running in your own runners, with your own credentials.
- The hosted app keeps stack-level facts only: state, counts, who ticked, which commit, when. Never resource names, property paths, diffs or values.
- Sluiceway cannot cause a deploy by itself. A person ticks, the same rule judges every tick, the fresh preview and the hash check stand, and a destroy waits for a person.
Free on up to five adopted repos, with every feature but the required second approver. Only a repo with a dashboard counts, so installing the app on a whole org costs nothing.
The action runs on its own, and it is complete
The same deploys, with the issue as the only dashboard. Open source under Apache-2.0 and free forever, in as many repos as you add it to; nothing in the dashboard is held back for the app. It is the path for a team that grants no third-party app. Run the action yourself.
Shipped
v0.50.0- Pulumi, with stacks found from their files alone
- OpenTofu and Terraform, also behind Terragrunt or CDK for Terraform, declared in sluiceway.yaml
- Helm releases and Kubernetes manifests, declared in sluiceway.yaml
- One issue, one row per stack, tick to deploy
- Narrowed scans on push, full scans on a schedule, a rescan box
- A preview page per pending stack, with nested property paths
- Who may tick: write, maintain, admin, or a list of usernames
- Merge and deploy: one tick merges a green routine update and deploys its stack
- Drift, opt-in: a scheduled scan finds changes made outside the code, and a tick puts them back
- Stack dependencies with dependsOn or phases
- A kill switch that stops every deploy, a rehearsal that runs a tick without deploying, ignored stacks, read-only mode
- Notifications, opt-in: a short message to Slack, Telegram or your own webhook when stacks are pending, drift is found, a deploy fails or a tick is refused
- Step outputs and a result file for anything else
Next, still free
Planned- Planned: AWS CDK and CloudFormation, after 1.0
It lands in the open-source action, not behind a tier. No date is set.
One place to look
Every stack has one row, and a box when it has changes waiting. No second dashboard to run, and no notification unless you turn one on.
Permissions you already have
A tick is a checkbox in a GitHub issue. Who may tick follows the repo's permissions, and sluiceway.yaml narrows it to roles or usernames.
Your runners, your credentials
Every preview and deploy runs in your own GitHub Actions. About your repos the app talks only to GitHub; Stripe takes payments and Resend sends its emails. The action calls the GitHub API and a notification channel only when you name one.
Go one step at a time
Each step shows you something before the next one can change anything.
With the app
Install it on GitHub
Pick the org or account and the repos. It is free for five repos, and a repo counts only once it has a dashboard.
Read the pull request it opens
On each repo with no dashboard: the workflow, a starter
sluiceway.yaml, and the secrets each stack needs. Adding the secrets stays your step.Merge it
The first scan previews every stack and writes the dashboard issue. The console guides the rest.
Or run the action yourself
Without the app you add the files the pull request would write by hand, and the issue is your only dashboard. Running the action yourself has the whole path.
Check your setup
A check on every pull request reads your files and says which stacks Sluiceway found and whether its settings are valid. It runs from its own workflow, with no credentials, no tool and no write access. To see your dashboard first with nothing that can deploy, start read only with
readOnly: trueinsluiceway.yaml.Add the workflow
The file below, on the default branch: one job with one Sluiceway step, and no
if:anywhere. A push or the schedule scans, and a tick deploys.Declare your stacks
Optional. Without
sluiceway.yamlevery stack that discovery finds gets a row, and anyone with write access can tick.Load your credentials
In steps before Sluiceway, where the comments say. Sluiceway passes that environment to the tool as it is and never looks inside.
# Read only: rows have no boxes,
# and nothing deploys from here.
dashboard:
readOnly: true
# An OpenTofu root module, declared as a stack.
stacks:
- path: infra/dns
tool: opentofu
# A stack config file with no stack in the backend.
ignore:
- glob: apps/web:dev
reason: no stack in the backendWrite the full stack id
apps/web:dev, never apps/web. A Pulumi stack file with no stack in your state backend becomes a failed row until you ignore it, and the check warns about an ignore pattern that matches nothing.
name: deploy-dashboard
on:
push:
branches: [main]
schedule:
- cron: "0 6 * * *" # keep this: the daily full scan is part of the design
workflow_dispatch:
issues:
types: [edited]
# This block is everything Sluiceway can do in your repo.
permissions:
contents: read
issues: write
deployments: write
actions: write
pull-requests: read
checks: write
jobs:
sluiceway:
runs-on: ubuntu-latest
# One run at a time, and none is dropped. An edit of any other issue gets
# a group of its own, so it never waits for a scan or a deploy.
concurrency:
group: sluiceway-${{ github.event.issue.number }}
queue: max
steps:
- uses: actions/checkout@v7
- uses: pulumi/actions@v7 # without a command this only installs the CLI
with:
pulumi-version: ^3.229.0
# Install what your programs need, once, for example: npm ci
# Load your credentials and your state backend settings into the job
# environment here. They preview and deploy, so they must be able to
# change things. Sluiceway passes the environment to the tool and never
# looks inside. Whatever loads a secret must also mask it.
- uses: sluiceway/sluiceway@v0If your first scan finds nothing
An empty first scan is expected outside Pulumi. Pulumi stacks are found from their files alone. OpenTofu, Terraform, Helm and Kubernetes manifests need each stack declared in sluiceway.yaml, as infra/dns is above, and the next scan gives it a row. Declaring stacks covers every tool.