Skip to content

[Epic] PR preview deployments #3042

Open
28 of 42 issues completed
Open

Description

@dawsontoth

Turn on pull-request previews for an application on a Fabric cluster, and every PR's build is served at its own subdomain, <slug>.app.<cluster-domain>, with a valid certificate. Each push updates the preview, and closing the PR removes it. Customer setup should be one toggle in Studio plus one workflow file, with no stored secrets, DNS records or certificates to manage. Fabric already serves app. on every cluster, so the toggle no longer touches DNS or certificates.

Example: a PR from branch david/1778-custom-regions is served at https://david-1778-custom-regions.app.<cluster-domain>.

This is the next step after staged component deploys (#2315), and the "preview deployments" item of the Ephemeral Instances roadmap lane, beside #641 and #642. Studio is the first consumer.

Where things stand

Update, 2026-10-30: the cluster side is done for 5.4. #3617 adds enable_previews, disable_previews and get_preview_policy, per-application limits (maxPreviews, maxTtlDays), and harper preview setup / teardown, which write the starter workflow. #3463 is fixed by #3600 and #3763. Every harper-core sub-issue is merged except #3055, plus four follow-ups from review: #3608, #3609, #3613 and #3758. Every row of the table below now has its fix merged, except a reusable action (#3055). Names Studio's panel must match, from derivePreviewNames in utility/previewIdentity.ts: role and user <app>-ci-preview; trust policies github-actions-<app>-preview (environment preview) and github-actions-<app>-preview-cleanup (environment preview-cleanup, drop only); workflow .github/workflows/preview.yaml, which reads its target from the HARPER_PREVIEW_TARGET repository variable.

The recipe described below is the 5.3 one: no documentation covers #3505's or #3617's features yet, and HarperFast/documentation#750 (draft) covers #3339's.

Update, 2026-10-26: every harper-core sub-issue except #3055 and #3463 is merged for 5.4: #3045 and #3091 in #3339, the rest in #3505.

Decision, 2026-10-26: the customer's cluster owns previews, and Central Manager stays out of the loop. Trust is between GitHub (or another issuer) and the cluster. A core enable/disable operation on the cluster creates the scoped role, user and trust policy, stores and enforces each application's preview policy, and removes them all again. The CLI and Studio's panel (HarperFast/studio#2044) call it directly, as HarperFast/studio#1788 does for deploys. harper preview deploy reports a preview host it cannot reach, in place of a Central Manager or host-manager gate. So HarperFast/central-manager#900, HarperFast/central-manager#904, HarperFast/central-manager#905, HarperFast/central-manager#907, HarperFast/central-manager#909 and HarperFast/host-manager#242 are closed as not planned. That core work merged in #3617. What remains: #3055, the follow-ups #3608, #3609, #3613 and #3758, Studio's panel (HarperFast/studio#2044) and its own previews (HarperFast/central-manager#908, HarperFast/central-manager#910, HarperFast/central-manager#911, HarperFast/central-manager#912), and HarperFast/host-manager#243.

Previews already work by hand on 5.3.0. HarperFast/documentation#715 (stacked on HarperFast/documentation#713) documents a recipe, "Per-PR previews with shared databases":

On Fabric, the custom-domain step is going away. Preview hosts merged on 2026-10-21 in HarperFast/central-manager#917 and HarperFast/host-manager#244, and are documented in HarperFast/documentation#718:

  • Every Colocated and Dedicated cluster serves *.app.<cluster>.<org>.harperfabric.com, with no setting.
  • Central Manager writes the wildcard record with the cluster's own record, and backfills existing clusters from a staff endpoint. It lets the cluster's live instances prove _acme-challenge.app.<cluster>.
  • Host-manager adds the wildcard to each instance's certificate at its next order or renewal. It routes a host exactly one label under it only to the isolated application that lists it, and only once that instance's certificate carries the wildcard. A host no isolated application lists gets no route.
  • New clusters serve previews at once. Existing ones gain them as each instance's certificate renews, within about seven weeks.

The label is app., not the cluster name itself. Let's Encrypt refuses an order that lists a name beside a wildcard covering it, and the instance and replication names sit one label under the cluster name. Let's Encrypt staging confirmed the refusal for *.<cluster>, and issued the same order with the wildcard one label down, every name included.

It works, but it takes a lot of setup. This epic removes that cost one mechanism at a time:

What the recipe needs today Removed by
On Fabric, each preview hostname is added, verified and bound as a custom domain, for every PR HarperFast/central-manager#917 and HarperFast/host-manager#244, merged 2026-10-21; they supersede HarperFast/central-manager#900, HarperFast/central-manager#901, HarperFast/central-manager#902, HarperFast/central-manager#903, HarperFast/host-manager#240 and HarperFast/host-manager#241
The first deploy of each preview restarts the shared workers on every node at once #3045
Cleanup calls each node's own URL, with a trust policy per node URL, and checks files and workers itself #3045, #3091
The preview role can deploy and drop any component, and read every component's configuration #3046
An app that needs a build publishes each PR commit to a registry first #3043
A private repository needs an administrator to grant a credential to each preview, and delete it afterwards #3090
Previews share the cluster's data #3053 for the bare databases/tables globals, #3043 for payload deploys (#3071 and #3044 are done)
Nothing removes a preview whose close event was missed #3048
A request with a preview's host on Harper's shared port is answered by production #3052
Each application needs a hand-made role, user, trust policy and workflow #3617 (enable_previews, harper preview setup)
Each repository carries its own long workflow #3054, #3617's starter workflow, #3055

What already exists

Shape

  1. A preview is an isolated application named <component>-pr-<n>, with host: <slug>.app.<cluster-domain>. Every application on the cluster shares app., and a host two applications list gets no route, so the slug must be unique on the cluster: it carries the component when a cluster previews more than one. Applications with tables add branchedDatabases, which a by_ref deploy honors since 5.3.2 (Honor branchedDatabases for package-deployed applications #3083). An application that uses the bare databases/tables globals still writes the shared data until Bind the bare databases/tables globals to an isolated application's branches #3053.
  2. CI deploys it straight to the cluster, authenticated by OIDC, as an identity that can only deploy and drop that application's previews.
  3. DNS, certificates and routing need nothing per application: every cluster serves *.app.<cluster-domain> (HarperFast/central-manager#917, HarperFast/host-manager#244). Everything per application lives on the cluster: enabling previews there creates the trust policy, scoped role and user and stores the application's preview policy, and disabling them drops every preview and the identity. Central Manager has no part in it.
  4. Closing the PR drops the preview. An expiry and a per-application cap catch anything the close event missed.

The branch slug is one lowercase DNS label:

  • Anything outside [a-z0-9] becomes -, and runs collapse.
  • It is cut to 40 characters, with a hash suffix when cut.
  • Reserved names fall back to pr-<n>.

For example, david/1778-custom-regions becomes david-1778-custom-regions. The documented recipe uses pr-<n>.<suffix>, which needs no slug at all.

Sub-issues

Harper core

Follow-ups from review of #3600 and #3617:

CI tooling

Central Manager (superseded: the cluster owns previews)

  • HarperFast/central-manager#900. Not planned: the cluster stores and enforces each application's preview policy.
  • HarperFast/central-manager#901. Covered by HarperFast/central-manager#917, as one *.app. record per cluster rather than one per application.
  • HarperFast/central-manager#902. Covered by HarperFast/central-manager#917, through _acme-challenge.app.<cluster>.
  • HarperFast/central-manager#903. Superseded: host-manager derives the wildcard from the cluster name, so Central Manager sends nothing new.
  • HarperFast/central-manager#904. Not planned: the cluster's enable operation creates the identity.
  • HarperFast/central-manager#905. Not planned: the cluster's disable operation cleans up, and HarperFast/central-manager#917 deletes the wildcard with the cluster's own record.
  • HarperFast/central-manager#906. Replaced by PR previews: Previews panel for an application studio#2044: the panel calls the cluster directly.
  • HarperFast/central-manager#907. Not planned: the cluster enforces capacity, and harper preview deploy reports an unreachable host.

Studio

Studio as the first consumer of previews:

  • HarperFast/central-manager#908
  • HarperFast/central-manager#909. Not planned: superseded by *.app. hosts.
  • HarperFast/central-manager#910
  • HarperFast/central-manager#911
  • HarperFast/central-manager#912

Host Manager

  • HarperFast/host-manager#240. Superseded by HarperFast/host-manager#244, which routes under the derived *.app.<cluster> with no claim.
  • HarperFast/host-manager#241. Covered by HarperFast/host-manager#244.
  • HarperFast/host-manager#242. Not planned: no Central Manager gate needs the proxy mode.
  • HarperFast/host-manager#243. The deployed canary in HarperFast/central-manager#917's rollout is this check.

Security model

  • The workflow is trusted, not the PR. The job runs on pull_request_target from the default branch and never checks out PR code. The trust policy pins workflow_ref to that branch's workflow file, event_name: pull_request_target, and an environment, so a PR can't change the privileged workflow. Fork PRs are skipped by the workflow's same-repository condition.
  • The PR's code still runs on Harper, with the preview role's rights. From 5.4 (Scope deploy permissions to component names #3046, Enable PR previews from the cluster: enable_previews and disable_previews, per-app preview limits, and harper preview setup #3617), that role can deploy and drop only its application's <app>-pr-<n> previews, within its grant's limits. On 5.3 it can deploy and drop any component, so previews there belong on a separate preview cluster, or behind required reviewers when they share a production cluster.
  • Data is shared unless the preview is branched. Without branchedDatabases, a preview's writes, schema changes and roles apply to the cluster's databases. With it, writes to its branched databases stay in its fork. The system database (users and roles) cannot be branched, and the bare databases/tables globals reach the base until Bind the bare databases/tables globals to an isolated application's branches #3053.
  • A preview is the same site as what production serves from the cluster's host name. It can set cookies production receives, and SameSite does not separate them. Document preview hosts and custom domain name rules on Fabric documentation#718 recommends __Host- cookie names and CSRF or Origin checks. What production serves only from a custom domain is a different site.
  • Public repositories: GitHub's default execution protection blocks pull_request_target for affected public repositories from 2026-11-02. The repository's Actions policy has to allow it for the preview workflow.

Related, tracked elsewhere

Not in this epic

  • Customer-owned preview domains, via _acme-challenge CNAME delegation (HarperFast/central-manager#933).
  • A GitHub App mode that needs no workflow file.
  • Promoting a previewed build to production by deployment_id. Builds are bound to their component today.
  • Static-only previews served from the shared workers.
  • Stopping idle previews, and resetting a branch from its base.
  • A cluster per PR (HarperFast/central-manager#570).
  • Central Manager setup, policy or cleanup for customer previews: the cluster owns them.

Activity

  1. 3 remaining items

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Fields

    Priority

    P2

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions