Preview Environments
A Preview Environment is temporary runtime capacity for your HTTP project, with its own lifecycle, storage, and billing, plus a URL when the configured exposure allows one. Use it to review a change without replacing the primary application.
HTTP projects sourced from a GitHub or Forgejo repository can create previews automatically for pull requests without a workflow, API Key, or image configured in the repository. Zenifra's GitHub Action remains available when you already build an image and want to operate the preview explicitly.
Required permissions
The minimum key for the Preview operational lifecycle is limited to the primary project:
| Action | Minimum scope |
|---|---|
| Read the primary project | project.read on project:<project-id> |
| List Previews | project.preview.read on project:<project-id> |
| Create, update, read details and operation | project.preview.deploy on project:<project-id> |
| Remove a Preview | project.preview.delete on project:<project-id> |
Initial Preview configuration requires an authorized user session with project.preview.configure; it is not part of the Action's minimum key. owner has full access; assistant, member, and organization API Keys need the corresponding grants.
Before you start
- Have an HTTP project in Zenifra.
- If the project source is GitHub, confirm that the GitHub account is connected to the organization and that the repository is accessible to the project. For Forgejo, confirm that the connection uses
write:repositoryand an account that can administer repository hooks. - In the console, open the primary project's Preview Environments section, choose Configure previews, and turn on Enable Preview Environments.
- For the native flow, turn on Enable automatic GitHub previews or Enable automatic Forgejo previews and choose the Preview target branch.
- Set Default expiration (hours), Maximum expiration (hours), and Active environment limit for this project. The preview uses the primary project's plan.
- For the Action flow, store the primary project's organization API Key in a GitHub secret and expose a ready-to-use image through a repository or organization variable.
PREVIEW=true does not enable the feature by itself. Project opt-in and the limits configured in the console are always enforced before a preview is created or updated.
Each preview is billed hourly in BRL while active. The current price and effective limits come from the catalog and project settings; check the console before enabling the feature.
How identity works
A preview's identity is made of the organization, the primary project, and the preview key. Reusing the same key reuses the same environment across runs and makes retries safe.
- In a
pull_requestevent, the Action usespr-<number>whenPREVIEW_KEYis not provided. - Outside a pull request,
PREVIEW_KEYis required. - The key accepts 1 to 100 safe characters (
A-Z,a-z,0-9,.,_, and-). Invalid keys are rejected; they are not silently corrected. pull_requestevents withopened,synchronize, orreopenedupsert the preview.pull_request.closedrequests immediate removal.- Reopening a pull request with the same key recreates or reactivates the same logical identity and keeps the URL whenever possible.
A successful create or update renews the expiration. Duplicate deliveries and idempotent retries do not extend the TTL merely by being repeated. The default TTL is 24 hours and can be configured from 1 hour to 168 hours. If the close workflow does not run, Zenifra removes expired previews automatically.
Native previews for GitHub and Forgejo pull requests
The native flow uses the repository and commands already configured for the GitHub or Forgejo project. After Preview Environments and the provider's automatic previews are enabled, choose a target branch such as main. This branch is independent from the primary update mode: it defines which pull requests create previews and does not change the branch used to update the primary application.
For pull requests opened in the project's own repository:
opened,reopened, andsynchronizecreate or update the preview identified bypr-<number>when the target branch matches the selected branch;editedreevaluates the pull request when its target branch changes, creating, updating, or removing the preview according to the new eligibility;closedremoves the corresponding preview;- pull requests targeting another branch do not create a preview;
- pull requests from forks do not participate in the native flow;
- pull requests that were already open before activation are not imported automatically; they are considered on the next relevant event.
On Forgejo, events arrive through the repository hook created by the connection; without permission to administer hooks, automatic previews cannot be enabled.
Each pull request keeps the same preview identity across updates. Zenifra uses the runtime and commands configured for the project to build the pull request commit. The primary project keeps its own version while the preview is created or updated.
The preview follows the primary project's plan, exposure, and variables according to the current Preview behavior. Inherited variables may point to the same services used by the primary application. Storage is new and starts empty for the preview; data, custom domains, and custom image commands are not inherited. Do not treat the preview as a copy of production data.
This path does not require a GitHub Action, API Key, IMAGE, PREVIEW_KEY, or workflow configuration in the repository. To build an image outside this flow or operate a preview with your own automation, use the GitHub Action described below.
Warning: use only one flow for the
pr-<number>key in the same project. If an Action-managed preview already uses that key, the native flow does not replace it and the native run is blocked.
If you disable the provider's automatic previews or Preview Environments, change the target branch, or disconnect/change the project's repository, affected native previews are removed and pending publications are invalidated. The primary application and its update mode remain separate from this cleanup.
GitHub App permission
To receive pull request events, the Zenifra GitHub App needs the Pull requests permission (read-only). Installations made before this permission keep working for deploys, but do not create previews until the update is approved on GitHub:
- Someone with admin access to the account or organization opens its settings on GitHub, under GitHub Apps (for personal accounts, Applications > Installed GitHub Apps).
- Next to Zenifra, choose Configure.
- In the permission update notice, choose Review request and then Accept new permissions.
After the approval, the next pull request event, such as a new push, creates or updates the preview.
Preview inheritance and behavior
Every Preview inherits user-configured environment variables from the primary project inside the platform on every create or update. Values are never exposed by the API, Console, or Action.
Variable values never pass through the Action, outputs, Job Summary, or logs. Zenifra-managed variables are regenerated for the preview instead of being treated as user variables from the primary project.
Warning: inherited variables may point to the same databases, queues, buckets, or other services used by the primary project. Review these variables before using the preview when that sharing is not intended.
The preview inherits the port, exposure, and access rules needed to run the application. Storage is new and starts empty for the preview; data from the primary project is not inherited.
These configurations are currently not inherited or created:
- database, queue, bucket, and other service data
- custom domains
- custom image startup commands
Preview environment limits per organization
Active environments are limited at two levels: organization and primary project. The effective limit for a preview is always the lowest of the two.
- Per organization: an organization can have at most 10 simultaneously active preview environments.
- Per project: a primary project can have at most 2 simultaneously active preview environments.
- Maximum TTL: a preview can expire up to 168 hours (7 days) after the last update.
Limits are counted while the preview exists, regardless of the plan: each active preview consumes one slot in both limits. Creating a preview beyond the effective limit makes the operation end as failed, with a public message stating that the organization or project limit was reached; the Action fails the job.
The project opt-in, the limits configured in the console, and the organization entitlement are always enforced before any create, update, or removal. The public API does not change the organization limit: project settings can be adjusted in the console, but the organization limit is set by Zenifra.
How to increase the organization limit
If your organization needs more concurrent preview environments, send an email to [email protected] including:
- the organization name and access email;
- the current usage (how many active previews the organization typically keeps);
- the desired limit and the reason (for example, multiple development fronts working on parallel pull requests).
The Zenifra team reviews the request, adjusts the organization limit, and confirms by email. After approval, the new limits apply immediately to subsequent upserts, with no workflow changes required.
GitHub Action inputs
This section describes the explicit Action flow. It is independent from native pull request previews and remains valid for projects with a ready-to-use image, including projects that do not use a GitHub source.
Use the names exactly as shown below. Existing inputs continue to work when PREVIEW is absent or false; in that case, the Action updates only the primary project.
| Input | Required | Default | Description |
|---|---|---|---|
PROJECT_ID | Yes | — | ID of the primary HTTP project. |
API_KEY | Yes | — | Primary project organization API Key, stored in a GitHub secret. It is never displayed. |
IMAGE | Upsert | — | Ready-to-use image for deployment. Required for create/update and may be omitted when removing. |
PREVIEW | No | false | Set to true to select Preview Environment mode. |
PREVIEW_KEY | Conditional | Automatic in PR | Stable preview key. In pull requests, the Action derives pr-<number>; elsewhere, provide a valid key. |
PREVIEW_TTL | No | 24h | Lifetime until expiration. Accepts 1h to 168h. |
PREVIEW_ACTION | No | auto | auto, upsert, or delete. With auto, a closed PR selects delete and all other cases select upsert. |
WAIT_TIMEOUT | No | 10m | Maximum time to wait for the operation, from 1s to 15m. |
Every Preview inherits the primary project's user variables on every upsert without exposing their values.
The Preview Environment always inherits the primary project's plan. If the project uses basic, the preview uses basic; there is no PREVIEW_PLAN or separate Preview price.
The Action validates booleans, duration, action, and context before making the request. IMAGE is conditional: it is not required for deletion. Invalid values, a disallowed plan, and an out-of-range TTL fail the job with a public, actionable message.
Recommended pull request workflow
This workflow uses one key derived from the pull request number. Updates to the same pull request reuse the preview, and the closed event removes it through PREVIEW_ACTION=auto.
name: Zenifra preview
on:
pull_request:
types: [opened, synchronize, reopened, closed]
permissions:
contents: read
jobs:
preview:
runs-on: ubuntu-latest
steps:
- name: Create or remove preview
id: zenifra-preview
uses: zenifra/[email protected]
with:
PROJECT_ID: ${{ vars.ZENIFRA_PROJECT_ID }}
API_KEY: ${{ secrets.ZENIFRA_API_KEY }}
IMAGE: ${{ vars.ZENIFRA_PREVIEW_IMAGE }}
PREVIEW: true
PREVIEW_ACTION: auto
PREVIEW_TTL: 24h
WAIT_TIMEOUT: 10mOn the closed event, the Action does not need the image to remove the environment. Upsert outputs become available only after the operation reaches available; if the operation fails, the job ends with an error.
Manual workflow with PREVIEW_KEY
Use workflow_dispatch to test a branch, a specific version, or a flow that is not triggered by a pull request. In this case, you must choose the key yourself.
name: Zenifra manual preview
on:
workflow_dispatch:
inputs:
preview_key:
description: Stable Preview Environment key
required: true
type: string
action:
description: Requested operation
required: true
default: upsert
type: choice
options:
- upsert
- delete
permissions:
contents: read
jobs:
preview:
runs-on: ubuntu-latest
steps:
- name: Run preview operation
id: zenifra-preview
uses: zenifra/[email protected]
with:
PROJECT_ID: ${{ vars.ZENIFRA_PROJECT_ID }}
API_KEY: ${{ secrets.ZENIFRA_API_KEY }}
IMAGE: ${{ vars.ZENIFRA_PREVIEW_IMAGE }}
PREVIEW: true
PREVIEW_KEY: ${{ inputs.preview_key }}
PREVIEW_ACTION: ${{ inputs.action }}
PREVIEW_TTL: 24h
WAIT_TIMEOUT: 10mWhen delete is selected, IMAGE may be omitted. Do not reuse the same key for changes that must exist at the same time: a key represents one logical environment.
Outputs, polling, and status
The Action follows the operation with bounded polling. It waits for available on an upsert and deleted on removal. Transitional states include accepted, reserving, provisioning, updating, and deleting; failed ends the job with an error.
When the expected state is reached, the Action provides:
| Output | Description |
|---|---|
preview_id | Public identifier for the Preview Environment. |
preview_url | Public preview URL when it is available. It may not exist during removal. |
expires_at | Current expiration date and time. |
operation_id | Public identifier for the tracked operation. |
preview_status | Final state: available for an upsert or deleted for a removal. |
The Job Summary contains the public operation result, but never API Keys, environment variables, or credentials. A wait timeout or incompatible terminal state fails the job; repeating the same operation with the same key is safe.
Public errors and troubleshooting
Messages are product-oriented and do not reveal operational details. The API returns a code field that helps you correct the workflow; the Action shows an equivalent public message in the job:
preview_disabled: enable Preview Environments for the primary project.preview_ttl_invalidorpreview_ttl_exceeds_limit: use a TTL between1hand168hthat respects the maximum expiration configured for the project.PREVIEW_OPERATION_CONFLICT: another operation is already in progress for the key; wait for it or follow the existing operation instead of creating a new key to bypass the state.PARENT_CONFIGURATION_UNAVAILABLE: check the primary project's configuration before trying again.PARENT_IMAGE_AUTHENTICATION_UNAVAILABLE: review the primary project's private image credentials.- Operation
failedbecause a limit was reached: wait for an environment to be removed or request a higher limit. - Key outside the allowed format: the Action rejects the run before calling the API.
Timed out waiting for the preview operation to finish.: increaseWAIT_TIMEOUTup to15mor query the operation again.
No public error includes resource names, topology, credentials, variables, private URLs, or cleanup details.
Next steps
Last updated on
Deploy from Forgejo
Connect a Forgejo instance in Console, select an organization and repository, publish an application by branch, tag, or release, and create pull request previews while following its build.
OCI image deployment
Publish applications on Zenifra using ready Docker or OCI images, with versioned tags, variables, and operational validation.