Skip to content
sluicewayPublic

About

A deploy dashboard for infrastructure as code that lives in one GitHub issue. Tick a stack, and GitHub Actions deploys exactly that stack. Pulumi, OpenTofu, Terraform, Helm and Kubernetes.

Topics

Resources

Contributing

Security policy

Stars

1 star

Watchers

0 watching

Forks

Use this GitHub action with your project
Add this Action to an existing workflow or create a new one
View on Marketplace

Repository files navigation

Sluiceway: Penny, the sluice gate, resting on a calm quay because every stack is in sync

Sluiceway keeps one GitHub issue that shows which Pulumi, OpenTofu, Terraform, Helm or Kubernetes stacks have changes waiting, and deploys a stack when you tick its box. Nothing deploys unless a person asks, or unless your own sluiceway.yaml says a stack goes out on merge.

Important

Sluiceway is in beta. It is released as 0.x, and the roadmap says what comes before 1.0. Use sluiceway/sluiceway@v0 or pin a commit, and report rough edges as issues.

What it looks like

The dashboard is Markdown, so here is one: an example from made-up rows, rendered by Sluiceway's own code, with every section at once. In a real dashboard issue you tick its boxes; here they are disabled. The same body, as a dashboard issue holds it, is assets/example-dashboard.md.

Sluiceway: 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

Open the example dashboard: all 16 stacks and every section

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

  • apps/billing: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
  • apps/legacy-worker:prod · 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
  • apps/web:staging · 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
  • infra/network:prod · 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

How it works

A scan previews your stacks, and nothing deploys until someone ticks a box, unless your own sluiceway.yaml sets a stack to go out on merge.

  1. After a merge to the default branch, and once a day, a scan previews the stacks in the repo.
  2. The scan writes the dashboard issue: one row per stack, and a box on every stack that has changes waiting.
  3. You tick a box. That asks for that stack to be deployed exactly as its row shows it.
  4. Sluiceway checks that you may tick that stack, and previews it again. It deploys only if the fresh preview still matches the row.
  5. The row goes back to in sync, or says why the deploy failed, with a link to the run.

The issue is a view and never the source of truth. What is pending is always worked out again from a fresh preview.

Get started

Check your setup. In your clone, npx sluiceway check says which stacks Sluiceway finds and whether its settings are valid, and npx sluiceway init (or bunx sluiceway init) writes the workflow and a first sluiceway.yaml from them, and commits nothing (start with init). The same check runs on every pull request. It needs no credentials, no tool and no write access, so start with it before anything can deploy. Put the check workflow in .github/workflows/deploy-dashboard-check.yml and open a pull request with it. The summary of its run lists every stack it found, with its tick rule, and which credentials each stack needs, as names, and which of them nothing in the workflow provides. To see your dashboard first with nothing that can deploy, start read only.

Before the first scan. Two red rows are the ones new users met first. A Pulumi stack config file with no stack in the backend is still a stack, and its preview fails: leave it out with ignore and its full stack id, <path>:<name>, such as apps/web:dev, never apps/web, or let the scan create the stack with createInBackend: true on its entry in sluiceway.yaml. A program that pulls from a private registry works on your laptop and fails on the runner until a step of the workflow logs in to that registry.

Decide who may deploy. A tick asks for a deploy of that stack, production included. Without sluiceway.yaml, anyone with write access to the repo can tick, and without a GitHub Environment with required reviewers on the job that deploys, a tick is enough to deploy. Decide this before the workflow reaches the default branch: make the people who may deploy the reviewers of that environment, which needs the split workflow, or narrow who may tick with a tick rule in sluiceway.yaml at the repo root, such as tickers: maintain (who can tick). The same file leaves stacks out, names the files outside a stack's directory that it reads, and declares the stacks discovery does not find from files: Helm releases, Kubernetes manifests, and the OpenTofu and Terraform root modules that discovery leaves out. For Pulumi stacks and the root modules discovery finds, the file is optional. Configuration has every key.

Load your credentials. The workflow gives the tool what it needs. For GitHub secrets, put them as env: on Sluiceway's own step, so no other step sees them. For a cloud with OIDC or a secret manager, add a loading step right before Sluiceway's, after every install step. Sluiceway passes that environment to the tool as it is and never reads a credential by name. Or name a file of NAME=value lines with the env-file input, and Sluiceway reads it for the tool and masks every value itself. A repo whose stacks live in different places names a file per stack with envFile in sluiceway.yaml, and each stack's tool gets its own on top. Credentials has recipes for GitHub secrets, an env file, a cloud with OIDC, a secret manager and private registries.

Add the workflow. This is the whole loop: one job with one Sluiceway step, and no if: anywhere. The step reads the event of the run and does what it asks for: a push or the schedule scans, a tick deploys, an edit of any other issue ends with a notice. GitHub starts the job for an edit of any issue in the repo, so that run also installs your tools and loads your credentials before it ends, and it runs only code from the default branch. The split workflow keeps credentials out of the job an issue edit starts. The file goes in .github/workflows/deploy-dashboard.yml on the default branch, and the comments mark where your own steps go. Merge it once the steps above are in place. The push of that merge starts the first scan. A scan only previews: it writes the dashboard issue with one row per stack, and nothing deploys until someone ticks a box. The workflow explains every part, and what merge and deploy, stack dependencies, self-hosted runners and GitHub Environments add. Example workflows has it complete for common setups, and init writes a first version from what it finds in your repo.

name: deploy-dashboard

on:
  push:
    branches: [main]
  schedule:
    - cron: "0 6 * * *" # keep this: a push previews only some stacks, this scan all
  workflow_dispatch:
  issues:
    types: [edited]

# This block is everything Sluiceway can do in your repo.
permissions:
  contents: read # check out the code
  issues: write # write the dashboard and its comments
  deployments: write # record who deployed what, and when
  actions: write # the rescan box and settle start this workflow again
  pull-requests: read # name the pull requests behind a row
  checks: write # a preview page per pending stack

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
      # This installs Pulumi. For OpenTofu, Terraform, Helm or kubectl, install
      # that tool here instead (see Requirements).
      - 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. Or name a
      # file of NAME=value lines with the env-file input on the step below,
      # and Sluiceway loads it for the tool and masks every value itself.
      # Runs after a step above failed too, and then only says so on the
      # dashboard, so it never looks fresh while no scan could run.
      - uses: sluiceway/sluiceway@v0
        if: ${{ !cancelled() }}

What it promises

  • Your credentials stay in your runners, and there is no backend. Previews and deploys run in your own runners with the secrets your workflow loads, or with the env file you name, which Sluiceway reads for the tool and masks. Sluiceway itself fetches no credential, and calls the GitHub API and nothing else (security).
  • A fresh preview before every deploy. A tick deploys only what the row showed. If the change moved since, nothing is deployed and the row comes back with the new diff (what a tick promises).
  • No values, unless you list them. Rows show resource types, resource names and the paths of changed properties, never a value, except at the paths you list, and never one the tool marks secret (what reaches the issue).

What it does

  • Pulumi, with stacks found from their files alone (configuration).
  • OpenTofu and Terraform, with root modules found from their files when the files say so: a backend block, a lock file or .tofu files that name the tool, and no other directory using the directory as a module. Anything else, and Terragrunt or CDK for Terraform, is declared in sluiceway.yaml. A tick deploys the plan file whose diff the row showed (configuration, stacks[].tool).
  • Helm, with a release in a namespace declared in sluiceway.yaml. A tick deploys only what the chart rendered when the diff was checked (stacks[].tool).
  • Kubernetes manifests, with a directory of manifests or a kustomization declared in sluiceway.yaml. A tick deploys the set of manifests that was diffed (stacks[].tool).
  • Tick to deploy. One box per stack with changes waiting, checked against who may tick (using the dashboard).
  • Deploy on merge, per stack, opt-in: a stack you set to deploy: on-merge goes out after the merge that changed it, through the same fresh preview as a tick, attributed to whoever merged. A delete, a replace or drift still waits for a tick, and every other stack keeps its box (configuration).
  • Policies, opt-in: Rego policies that Conftest runs against the preview of every pending stack, in the same job. A policy that fails takes the box off the row, names the policy in its own words, and stops a deploy on merge (configuration).
  • Merge and deploy, for Renovate and other routine updates: one tick merges a green pull request and deploys its stack (merge and deploy).
  • Drift, opt-in: a scheduled scan finds changes made outside the code, and a tick puts them back (drift.enabled).
  • Stack dependencies with dependsOn or phases: a stack waits for the stacks it depends on, or for every stack of the phases before its own, and a chain deploys one layer per run (dependsOn, phases).
  • A preview page per pending stack, a check run with the stack's whole diff (using the dashboard).
  • The check mode, which reads your files in a pull request and says what Sluiceway will find and what your workflow lacks (check your setup).
  • A pull request preview, opt-in: the check previews the stacks a pull request claims, as they would be after the merge, and writes a check run per stack for the reviewer. Nothing deploys from it, and a fork is refused (check your setup).
  • Values at the paths you list with showValues, such as a chart's version, and the tool's own diff in the job log if you ask (dashboard.showValues). Every other value is covered by a fingerprint on the row, so a value that changed since the tick stops the deploy without being shown (what a tick promises).
  • Stop every deploy, or rehearse a tick: deploys: false stops every deploy, and dry-run rehearses a tick without deploying (deploys, dry-run).
  • 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, plus outputs and a result file for anything else (notifications).
  • Readable by scripts and agents: the markers in the dashboard, the payload of each deployment record and the result file are documented and versioned, with a JSON schema for the payload and the result file, and a rule for what may change (what Sluiceway hands over).

More

About

A deploy dashboard for infrastructure as code that lives in one GitHub issue. Tick a stack, and GitHub Actions deploys exactly that stack. Pulumi, OpenTofu, Terraform, Helm and Kubernetes.

Topics

Resources

Contributing

Security policy

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages