Preview Environments
A Preview Environment is a temporary copy of your HTTP project's runtime capacity, with its own URL, lifecycle, storage, and billing. Use it to review a change without replacing the primary application.
Zenifra's GitHub Action creates or updates the preview from a ready-to-use image, waits for availability, and publishes the outputs required by the workflow. When the pull request closes, the same Action can remove the preview automatically.
Before you start
- Have an HTTP project in Zenifra.
- In the console, open the primary project's Previews tab and enable Preview Environments.
- Set the default plan, allowed plans, default and maximum TTL, and active environment limit.
- Store the primary project's API Key in a GitHub secret. Never put the key directly in the YAML file, Job Summary, or logs.
- Have a ready-to-use image and expose its reference 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 only 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.
Every upsert renews the expiration. 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.
Inheritance and isolation
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. Enable inheritance only when that sharing is intended.
The preview inherits the port, exposure, and access rules needed to run the application. Preview storage is always new, empty, and isolated: data from the primary project is never cloned or shared.
These configurations are not inherited or created in the MVP:
- 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 fails with preview_organization_limit_reached or preview_project_limit_reached; the Action ends the job with a public, actionable message.
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.
Action inputs
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 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 | Defined by the Action | Maximum time to wait for the operation. Provide a valid duration within the Action limit, such as 10m. |
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/action-zenifra-deploy@v1
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 or fails in a terminal state.
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/action-zenifra-deploy@v1
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 a terminal 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, such as available, deleted, or failed. |
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. Stable codes help you correct the workflow:
preview_not_enabled: enable Preview Environments for the primary project.invalid_preview_key: use a key in the allowed format.preview_ttl_out_of_range: use a TTL between1hand168h.preview_limit_reached: wait for an environment to be removed or adjust the allowed limit.parent_configuration_unavailable: check the primary project's configuration before trying again.preview_operation_in_progress: follow the existing operation; do not create a new key to bypass the state.preview_unavailable: review the image and the application's public configuration.preview_wait_timeout: increaseWAIT_TIMEOUTwithin the allowed limit or query the operation again.
No public error includes resource names, topology, credentials, variables, private URLs, or cleanup details.