Skip to content

feat: ✨ Additional features to Vercel transition#751

Open
slugb0t wants to merge 7 commits intonextfrom
next-preview
Open

feat: ✨ Additional features to Vercel transition#751
slugb0t wants to merge 7 commits intonextfrom
next-preview

Conversation

@slugb0t
Copy link
Member

@slugb0t slugb0t commented Mar 12, 2026

The GH_TOKEN or GH_PAT will need a scope of repo or Environments: Read and write to allow the removal of deployment environments. This will help prevent the deployments page being cluttered with so many preview deployments. It should remove environments that are no longer needed/stale.

Summary by Sourcery

Enhance preview and production deployment workflows with richer GitHub deployment tracking, automated cleanup, and improved Next.js standalone Docker packaging.

New Features:

  • Post and maintain preview deployment status comments on pull requests, reflecting in-progress, ready, failed, and removed states.
  • Create and update GitHub deployment records for preview and production environments, including environment URLs and status transitions.
  • Introduce a scheduled workflow to detect and remove stale preview environments, their server deployments, registry images, and associated GitHub environments.

Enhancements:

  • Unify and tighten preview workflow triggers and concurrency settings to better handle push and pull request events.
  • Improve preview PR notifications to show the latest deployment status when a pull request is opened or reopened.
  • Adjust Dockerfile and Next.js configuration to use Next.js standalone output for production builds, simplifying runtime dependencies and startup.
  • Extend preview teardown workflow to clean up registry images, deactivate/delete GitHub deployments, and update existing PR comments instead of adding new ones.
  • Add required GitHub token permissions for deployment-related operations in workflows.

Build:

  • Add a dedicated cleanup-preview-envs GitHub Actions workflow to periodically purge stale preview environments and their resources.

@slugb0t slugb0t requested a review from megasanjay as a code owner March 12, 2026 00:35
@fairdataihub-bot
Copy link

Thank you for submitting this pull request! We appreciate your contribution to the project. Before we can merge it, we need to review the changes you've made to ensure they align with our code standards and meet the requirements of the project. We'll get back to you as soon as we can with feedback. Thanks again!

@vercel
Copy link

vercel bot commented Mar 12, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
fairdataihub-website Ready Ready Preview, Comment Mar 12, 2026 0:35am

Request Review

@sourcery-ai
Copy link
Contributor

sourcery-ai bot commented Mar 12, 2026

Reviewer's Guide

This PR enhances the preview and production deployment workflows, introduces automated cleanup of stale preview environments, and switches the Next.js app to a standalone Docker deployment model with tighter integration to GitHub Deployments and richer PR commenting behavior.

Sequence diagram for preview deployment workflow and PR comments

sequenceDiagram
  actor Developer
  participant GitHub
  participant Workflow_deploy_preview
  participant Kamal
  participant Preview_Server
  participant Registry
  participant GitHub_Deployments
  participant PR_Comments

  Developer->>GitHub: Push to branch / open PR
  GitHub-->>Workflow_deploy_preview: Trigger deploy-preview-app workflow

  activate Workflow_deploy_preview
  Workflow_deploy_preview->>PR_Comments: Post "Preview deployment in progress" (or update existing)

  Workflow_deploy_preview->>Kamal: kamal deploy -d preview
  Kamal->>Preview_Server: Build and deploy preview app
  Kamal->>Registry: Push preview image
  Preview_Server-->>Workflow_deploy_preview: Deployment result

  alt Deployment succeeded
    Workflow_deploy_preview->>GitHub_Deployments: createDeployment(environment = KAMAL_APP_NAME)
    GitHub_Deployments-->>Workflow_deploy_preview: deployment id
    Workflow_deploy_preview->>GitHub_Deployments: createDeploymentStatus(state = success, environment_url = preview URL)
    Workflow_deploy_preview->>GitHub_Deployments: Mark older deployments inactive
    Workflow_deploy_preview->>PR_Comments: Update comment to "Preview deployment ready" with URL and timestamp
  else Deployment failed
    Workflow_deploy_preview->>GitHub_Deployments: createDeployment(environment = KAMAL_APP_NAME)
    GitHub_Deployments-->>Workflow_deploy_preview: deployment id
    Workflow_deploy_preview->>GitHub_Deployments: createDeploymentStatus(state = failure)
    Workflow_deploy_preview->>PR_Comments: Update comment to "Preview deployment failed" with link to workflow run
  end

  deactivate Workflow_deploy_preview

  Developer->>GitHub: Reopen PR / check status
  GitHub-->>Workflow_deploy_preview: Trigger notify-pr-on-open job
  activate Workflow_deploy_preview
  Workflow_deploy_preview->>GitHub_Deployments: listDeployments(environment = KAMAL_APP_NAME)
  alt Deployment record found
    Workflow_deploy_preview->>GitHub_Deployments: listDeploymentStatuses(deployment_id)
    GitHub_Deployments-->>Workflow_deploy_preview: latest state
    Workflow_deploy_preview->>PR_Comments: Upsert comment to match success / in_progress / pending state
  else No deployment record yet
    Workflow_deploy_preview->>GitHub: listWorkflowRunsForRepo(branch)
    GitHub-->>Workflow_deploy_preview: running workflows
    Workflow_deploy_preview->>PR_Comments: Upsert comment reflecting in_progress or waiting for next push
  end
  deactivate Workflow_deploy_preview
Loading

Sequence diagram for preview teardown and stale cleanup

sequenceDiagram
  actor Developer
  participant GitHub
  participant Workflow_close_PR as Workflow_close_PR_remove_preview
  participant Workflow_cleanup as Workflow_cleanup_stale_preview_envs
  participant Kamal
  participant Registry
  participant GitHub_Deployments
  participant GitHub_Environments
  participant PR_Comments
  participant Scheduler

  Developer->>GitHub: Close PR / delete branch
  GitHub-->>Workflow_close_PR: Trigger remove-preview job

  activate Workflow_close_PR
  Workflow_close_PR->>Kamal: kamal remove -y -d preview
  Kamal-->>Workflow_close_PR: Preview removed from server

  Workflow_close_PR->>Registry: Delete preview images for KAMAL_APP_NAME

  Workflow_close_PR->>GitHub_Deployments: listDeployments(environment = KAMAL_APP_NAME)
  loop For each deployment
    Workflow_close_PR->>GitHub_Deployments: createDeploymentStatus(state = inactive, description = Preview environment removed)
    Workflow_close_PR->>GitHub_Deployments: deleteDeployment
  end

  Workflow_close_PR->>GitHub_Environments: deleteAnEnvironment(environment_name = KAMAL_APP_NAME)

  Workflow_close_PR->>PR_Comments: Upsert comment to "Preview deployment removed"
  deactivate Workflow_close_PR

  Scheduler->>GitHub: Cron schedule (weekly)
  GitHub-->>Workflow_cleanup: Trigger cleanup-preview-envs workflow

  activate Workflow_cleanup
  Workflow_cleanup->>GitHub: listBranches
  Workflow_cleanup->>GitHub_Environments: getAllEnvironments
  Workflow_cleanup->>GitHub: getCommit for each branch
  Workflow_cleanup-->>Workflow_cleanup: Determine stale_envs based on missing branch or age > 90 days

  loop For each stale_env
    Workflow_cleanup->>Kamal: kamal remove -y -d preview (KAMAL_APP_NAME = env_name)
    Workflow_cleanup->>Registry: Delete registry images for env_name
    Workflow_cleanup->>GitHub_Deployments: listDeployments(environment = env_name)
    loop For each deployment of env_name
      Workflow_cleanup->>GitHub_Deployments: createDeploymentStatus(state = inactive)
      Workflow_cleanup->>GitHub_Deployments: deleteDeployment
    end
    Workflow_cleanup->>GitHub_Environments: deleteAnEnvironment(environment_name = env_name)
  end
  deactivate Workflow_cleanup
Loading

Sequence diagram for production deployment with GitHub Deployments

sequenceDiagram
  actor Developer
  participant GitHub
  participant Workflow_deploy_ui
  participant Kamal
  participant Production_Server
  participant GitHub_Deployments

  Developer->>GitHub: Push to production branch (e.g. main)
  GitHub-->>Workflow_deploy_ui: Trigger deploy-app workflow

  activate Workflow_deploy_ui
  Workflow_deploy_ui->>GitHub_Deployments: createDeployment(environment = production, ref = context.ref)
  GitHub_Deployments-->>Workflow_deploy_ui: deployment id
  Workflow_deploy_ui->>GitHub_Deployments: createDeploymentStatus(state = in_progress)

  Workflow_deploy_ui->>Kamal: kamal setup (full production deploy)
  Kamal->>Production_Server: Build and deploy production app
  Production_Server-->>Workflow_deploy_ui: Deployment result

  alt Deployment succeeded
    Workflow_deploy_ui->>GitHub_Deployments: createDeploymentStatus(state = success, environment_url = production URL)
  else Deployment failed
    Workflow_deploy_ui->>GitHub_Deployments: createDeploymentStatus(state = failure)
  end
  deactivate Workflow_deploy_ui
Loading

File-Level Changes

Change Details Files
Improve preview deployment workflow to create/manage GitHub Deployments and maintain a single status comment on PRs.
  • Simplified concurrency group naming for preview deployments to avoid including event name.
  • Adjusted pull_request triggers to handle opened, reopened, and closed events in a single workflow and added deployments write permission.
  • Added a step to post or update an in-progress deployment comment on the associated PR before running Kamal.
  • Wrapped Kamal preview deploy in a named step to capture its outcome and used it to create a GitHub Deployment with success/failure status and environment URL, marking older deployments inactive.
  • Reworked the final PR comment logic to always update a single comment with success/failure status and timestamp, with a fallback path if an initial comment was not created.
  • Replaced the pull_request_target-based notification job with a pull_request job that looks up the latest deployment and workflow runs to derive an accurate status message, reusing/updating a single preview comment.
.github/workflows/deploy-preview-app.yml
Make preview teardown more thorough by cleaning GitHub environments, deployments, and registry images while keeping PR comments consistent.
  • Granted deployments write permission to the preview removal job.
  • After running kamal remove, set up crane and delete all tags for the preview image from the container registry.
  • Added logic to mark all related GitHub deployments inactive, delete them, and attempt to delete the associated GitHub environment, warning on failure.
  • Updated the PR notification on removal to edit any existing preview status comment instead of always creating a new one.
.github/workflows/deploy-preview-app.yml
Integrate production deployment with GitHub Deployments to track status and provide environment URLs.
  • Declared minimal contents/deployments permissions on the production deploy-ui job.
  • Added a step to create a GitHub Deployment for the production environment, mark it in_progress, and store its ID as an output.
  • Wrapped Kamal setup in a named step to capture its outcome.
  • Appended a status update step that, based on Kamal outcome, updates the GitHub Deployment to success/failure and optionally sets the environment URL.
.github/workflows/deploy-app.yml
Switch Next.js Docker image to standalone output and remove NEXT_IMAGE_UNOPTIMIZED, aligning with Next.js best practices behind a proxy.
  • Removed NEXT_IMAGE_UNOPTIMIZED environment variables from the runtime image stages and from Kamal env clear configuration.
  • Changed the final Docker stage to copy the .next/standalone server bundle and its static/public assets instead of copying node_modules and .next directly.
  • Updated the container entrypoint to run the standalone server directly instead of invoking the Next CLI.
  • Enabled Next.js standalone output in next.config.js.
Dockerfile
config/deploy.yml
next.config.js
Add a scheduled workflow to automatically find and clean up stale preview environments and associated resources.
  • Created a find-stale job that builds a mapping of branches to expected preview environment names, lists GitHub environments with a given prefix, and identifies stale ones based on missing branches or last commit older than 90 days.
  • Exported the list of stale environment names as a JSON output for downstream jobs.
  • Added a cleanup job that, for each stale environment, runs Kamal remove against the corresponding preview app name/domain, deletes associated registry images using crane, and deactivates/deletes GitHub deployments and environments via the GitHub API, with logging and error handling.
  • Configured the workflow to run on a weekly cron plus manual dispatch and granted deployments write permission for cleanup operations.
.github/workflows/cleanup-preview-envs.yml

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@github-actions
Copy link
Contributor

Preview deployment ready

Preview URL: https://website-next-preview.fairdataihub.org

Last deployed: Thu, 12 Mar 2026 00:32:00 GMT

@fairdataihub-bot
Copy link

Thanks for making updates to your pull request. Our team will take a look and provide feedback as soon as possible. Please wait for any GitHub Actions to complete before editing your pull request. If you have any additional questions or concerns, feel free to let us know. Thank you for your contributions!

Copy link
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 1 issue, and left some high level feedback:

  • There’s a fair amount of duplicated GitHub Script logic for finding the matching PR and creating/updating the ## Preview deployment... comment; consider extracting this into a composite action or a shared script to reduce drift between the different workflows.
  • The cleanup workflow hardcodes the ENV_PREFIX (fairdataihub-website-) and the preview domain pattern (website-${branch_slug}.fairdataihub.org), which are separate from how KAMAL_APP_NAME/KAMAL_APP_DOMAIN are derived elsewhere; aligning these through a single shared naming convention or constants will make future changes less error‑prone.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- There’s a fair amount of duplicated GitHub Script logic for finding the matching PR and creating/updating the `## Preview deployment...` comment; consider extracting this into a composite action or a shared script to reduce drift between the different workflows.
- The cleanup workflow hardcodes the `ENV_PREFIX` (`fairdataihub-website-`) and the preview domain pattern (`website-${branch_slug}.fairdataihub.org`), which are separate from how `KAMAL_APP_NAME`/`KAMAL_APP_DOMAIN` are derived elsewhere; aligning these through a single shared naming convention or constants will make future changes less error‑prone.

## Individual Comments

### Comment 1
<location path=".github/workflows/cleanup-preview-envs.yml" line_range="51-40" />
<code_context>
+            }
+
+            // Get all GitHub environments
+            const { data: envData } = await github.rest.repos.getAllEnvironments({
+              owner: context.repo.owner,
+              repo: context.repo.repo,
+            });
+
</code_context>
<issue_to_address>
**suggestion:** Environment list is not paginated, which can miss stale environments in larger repos

`github.rest.repos.getAllEnvironments` only returns the first page of results. In repos with many environments, some preview environments may never be considered for cleanup. Use `github.paginate` here (as with branches) so all environments are included when determining staleness.

Suggested implementation:

```
            // Get all GitHub environments
            const environments = await github.paginate(
              github.rest.repos.getAllEnvironments,
              {
                owner: context.repo.owner,
                repo: context.repo.repo,
              },
              (response) => response.data.environments
            );

on:

```

Because the original change used:

```js
const { data: envData } = await github.rest.repos.getAllEnvironments({ ... });
```

you should also:

1. Remove that old `const { data: envData } = ...` line (if it still exists elsewhere in the script).
2. Update any subsequent references:
   * If the code currently uses `envData.environments`, change those references to just `environments`.
   * If the code currently uses `envData` directly as the list, change those references to `environments` as well.
3. Ensure this matches how branches are paginated in the same script (likely also via `github.paginate`).
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

// List all branches
const branches = await github.paginate(github.rest.repos.listBranches, {
owner: context.repo.owner,
repo: context.repo.repo,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: Environment list is not paginated, which can miss stale environments in larger repos

github.rest.repos.getAllEnvironments only returns the first page of results. In repos with many environments, some preview environments may never be considered for cleanup. Use github.paginate here (as with branches) so all environments are included when determining staleness.

Suggested implementation:

            // Get all GitHub environments
            const environments = await github.paginate(
              github.rest.repos.getAllEnvironments,
              {
                owner: context.repo.owner,
                repo: context.repo.repo,
              },
              (response) => response.data.environments
            );

on:

Because the original change used:

const { data: envData } = await github.rest.repos.getAllEnvironments({ ... });

you should also:

  1. Remove that old const { data: envData } = ... line (if it still exists elsewhere in the script).
  2. Update any subsequent references:
    • If the code currently uses envData.environments, change those references to just environments.
    • If the code currently uses envData directly as the list, change those references to environments as well.
  3. Ensure this matches how branches are paginated in the same script (likely also via github.paginate).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant