> For the complete documentation index, see [llms.txt](https://docs.duku.ai/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.duku.ai/integrations/github-actions/quickstart-environment-action.md).

# Quickstart: environment action

Start Duku automatically for stable environments.

Use this workflow for an environment you declare in Viewport, such as staging or a production canary.

{% stepper %}
{% step %}

### 1. Declare the environment in Viewport

Open the product in Viewport and go to **Product settings → Environments**. Add the environment with its name and URL, such as `staging`. New products start with a production environment.

Store the product ID as the repository variable `PLATFORM_PRODUCT_ID`.

{% hint style="info" %}
[Releases](/viewport-dashboard/releases.md) and environments are switched on per organisation. If **Product settings → Environments** is not available to you yet, contact Duku. Until then, pass `target-id` with a target created in Viewport - see [Configuration reference](/integrations/github-actions/configuration-reference.md).
{% endhint %}
{% endstep %}

{% step %}

### 2. Add the repository secret

Create `PLATFORM_API_KEY` in Viewport, under **Settings → API Keys**.

Store it as the repository secret `PLATFORM_API_KEY`.
{% endstep %}

{% step %}

### 3. Create the workflow file

Copy this file into `.github/workflows/duku-environment.yml`:

```yaml
name: Duku environment

on:
  push:
    branches: [main]
  workflow_dispatch:

concurrency:
  group: duku-env-staging
  cancel-in-progress: false

jobs:
  explore:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      pull-requests: read
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0
      - uses: duku-ai/actions/environment@environment/v0.5.0
        with:
          api-key: ${{ secrets.PLATFORM_API_KEY }}
          product-id: ${{ vars.PLATFORM_PRODUCT_ID }}
          environment: staging
          github-token: ${{ github.token }}
```

{% endstep %}

{% step %}

### 4. Update the workflow values

* `branches: [main]` should match the branch that deploys this environment.
* `environment` names the environment you declared. Leave it out to use the product's default environment.
* Each push is recorded as a release on that environment, named from `git describe`. Pass `release-label` to use your own name.
* `github-token` with `pull-requests: read` links each merged pull request to the release it shipped in.

The action explores the URL the environment declares.

`cancel-in-progress: false` serializes runs per environment so overlapping pushes do not create duplicate explorations. Set it to `true` if you would rather drop earlier in flight runs.
{% endstep %}
{% endstepper %}

#### Wait for the deploy

On a fresh push the environment may still serve the previous build for a minute. Set `wait-for-deployment: true` and the action waits until the environment serves the pushed commit before it starts.

```yaml
wait-for-deployment: true
wait-commit-header: x-commit-sha
```

Your app must return its deployed commit in that header. On Vercel, set it from `VERCEL_GIT_COMMIT_SHA` in `next.config.js` headers. The wait applies to `push` and `pull_request` runs; scheduled and manual runs start straight away.

### Run on a pull request against a fixed environment

Use this mode when you merge into a protected branch, such as `prod`, and want Duku to gate the PR by exploring a permanent environment.

```yaml
name: Duku prod gate

on:
  pull_request:
    branches: [prod]

jobs:
  explore:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      pull-requests: write
    steps:
      - uses: duku-ai/actions/environment@environment/v0.5.0
        with:
          api-key: ${{ secrets.PLATFORM_API_KEY }}
          product-id: ${{ vars.PLATFORM_PRODUCT_ID }}
          environment: dev
          github-token: ${{ github.token }}
```

Each run records the pull request's head commit as a release on that environment and marks it **Live** there. Use this mode only where the environment already serves the pull request's head when the run starts - for example when pull requests are deployed to `dev` before this check runs - otherwise results are attributed to a commit the environment is not running. Set `wait-for-deployment: true` to have the action wait until it does.

On a native `pull_request` event, results are also posted to the PR comment when the Duku GitHub App is installed.

On `pull_request` runs, pass `github-token: ${{ github.token }}` so the action can post the sticky status comment. On `push`, pass it too, as the first example does, so each merged pull request is linked to the release it shipped in.

Without the App, the action still runs and results still appear in Viewport.

### Nightly health checks

A scheduled workflow is a good fit for nightly health checks against staging.

```yaml
name: Duku nightly staging

on:
  schedule:
    - cron: '0 3 * * *'
  workflow_dispatch:

jobs:
  explore:
    runs-on: ubuntu-latest
    permissions:
      contents: read
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0
      - uses: duku-ai/actions/environment@environment/v0.5.0
        with:
          api-key: ${{ secrets.PLATFORM_API_KEY }}
          product-id: ${{ vars.PLATFORM_PRODUCT_ID }}
          environment: staging
```

The checkout lets the run name the release the same way your push workflow did, so the nightly run reuses that release.

To run against a release you name - for example when the deployed build is not the commit the workflow runs on - pass it as `release-label`:

```yaml
on:
  workflow_dispatch:
    inputs:
      release:
        required: true
# ...
        with:
          api-key: ${{ secrets.PLATFORM_API_KEY }}
          product-id: ${{ vars.PLATFORM_PRODUCT_ID }}
          environment: staging
          release-label: ${{ inputs.release }}
          release-label-from: input
```

A new label records a new release and makes it the one that environment serves. Sending the same label again records nothing new, and the run explores whatever the environment serves at that moment.

### Keeping Duku in step with your releases

Run the environment action as the step after each deploy, once per environment, with `environment:` set to the environment you just deployed to (for example `staging` in your staging workflow and `production` in your production workflow). Keep `wait-for-deployment` on so Duku explores the new build, not the one it replaced.

* **One commit is one release.** Promoting the same commit from staging to production records one release deployed to two environments, so its results sit together on the release page.
* **Redeploys and rollbacks** reuse the existing release. Deploying an older commit again makes that release **Live** on the environment once more and marks it **Redeployed**.
* **Release names** come from `git describe` by default, so check out with `fetch-depth: 0` and a tagged commit is named after its tag. To use your own name, pass `release-label`. See Release names.
* **Gating a promotion:** the environment action does not post a check run. To stop a promotion on a failing result, poll the batch from your pipeline as shown in Triggering explorations from any CI.

### Read next

* [Choosing preview vs environment](/integrations/github-actions/choosing-preview-vs-environment.md)
* [Configuration reference](/integrations/github-actions/configuration-reference.md)
* [Troubleshooting](/integrations/github-actions/troubleshooting.md)
