Skip to content

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.

Install on GitHubfree for 5 repos

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.

Sluiceway: deploying, 4 stacks are pending, some changes delete or replace resourcesSluiceway: deploying, 4 stacks are pending, some changes delete or replace resources

🟡 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 · compare
  • apps/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] · compare

    2 changes update kubernetes:apps/v1:Deployment billing · spec.replicas 2 → 3
    create kubernetes:monitoring.coreos.com/v1:ServiceMonitor billing
  • · 3 deletes, 1 tracking only · preview
    from #498 by dave · compare
    ⚠️ DELETE aws:sqs/queue:Queue legacy-jobs
    ⚠️ DELETE kubernetes:apps/v1:Deployment legacy-worker
    ⚠️ DELETE kubernetes:core/v1:Service legacy-worker

    1 other change forget aws:iam/role:Role legacy-worker
  • · 2 creates, 1 update · preview
    from #516 by carol, #510 by bob, and 1 change outside this stack · compare

    3 changes update kubernetes:apps/v1:Deployment web · metadata.labels["app.kubernetes.io/version"], spec.template.spec.containers[0].image
    create kubernetes:autoscaling/v2:HorizontalPodAutoscaler web
    create kubernetes:core/v1:ConfigMap web-feature-flags
    changes outside this stack #497 by frank
  • · 1 update, 1 replace · preview
    from 11fa403 by gina · compare
    ⚠️ REPLACE aws:ec2/subnet:Subnet private-b · forced by cidrBlock

    1 other change update aws:ec2/routeTable:RouteTable private · 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 changed kubernetes:apps/v1:Deployment grafana · spec.replicas
  • platform/external-dns:prod · 1 gone outside the code · preview

    1 change outside the code gone aws:route53/record:Record status-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.

  1. A scan previews the stacks

    After a merge to the default branch, the stacks that merge changed. Once a day, every stack.

  2. 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.

  3. You tick a box

    That asks for that stack to be deployed exactly as its row shows it.

  4. It previews again, then deploys

    Only if the fresh preview still matches the row. Otherwise the row comes back with the new diff.

Gate closed, water calm. Nothing to deploy.
Sluiceway: everything is in syncSluiceway: everything is in sync

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.

What every tier keeps
  • 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 Sluiceway app

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.yaml names 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 own resolve. 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.yaml has recordWriters: ["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.
What it promises
  • 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.

Without the app

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.

Get started

Go one step at a time

Each step shows you something before the next one can change anything.

With the app

  1. 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.

  2. 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.

  3. 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.

  1. 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: true in sluiceway.yaml.

  2. 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.

  3. Declare your stacks

    Optional. Without sluiceway.yaml every stack that discovery finds gets a row, and anyone with write access can tick.

  4. 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.

sluiceway.yaml
# 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 backend

Write 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.

.github/workflows/deploy-dashboard.yml
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@v0

If 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.

Sluiceway: no stacks yetSluiceway: no stacks yet