Preview Environments skill for Codex
A live URL per PR, gone when the PR closes. Codex sets up previews that deploy every PR to its own namespace or platform preview with a stable pr-number URL, wildcard TLS and authentication in front. Data comes from seeds or sanitized database branches, secrets point at sandbox accounts, fork PRs never receive credentials, and teardown happens on close with a label-scoped TTL reaper catching anything left behind.
curl -fsSL codexskills.dev/install.sh \ | sh -s -- preview-environments Installs to ~/.agents/skills/preview-environments/ for every project on this machine.
curl -fsSL codexskills.dev/cli.mjs -o codex-skills.mjsnode codex-skills.mjs install preview-environments The CLI also searches, updates and removes skills. Run it with no arguments to pick from a list. Needs Node 20.12+.
curl -fsSL codexskills.dev/install.sh \ | sh -s -- --project preview-environments Installs to .agents/skills/preview-environments/ in the current repository. Commit it and everyone who runs Codex there gets it.
curl -fsSLO codexskills.dev/downloads/preview-environments.zipunzip -o preview-environments.zip -d ~/.agents/skills/ The archive holds the skill folder with its generated agents/openai.yaml. Unzip into .agents/skills/ to scope it to a repo.
& ([scriptblock]::Create((irm codexskills.dev/install.ps1))) preview-environments PowerShell 5.1 or 7. Installs to %USERPROFILE%\.agents\skills\. Add -Project for the current repo.
104-line POSIX script. No sudo, no telemetry. Read install.sh
Use it in Codex
Start a prompt with $preview-environments, or pick it from /skills.
You can also just describe the task: Codex reads the description below and loads the playbook when your request matches.
What Codex reads before loading it
Builds per-pull-request preview environments - Kubernetes namespace per PR via Argo CD ApplicationSet pull request generator or CI-driven Helm installs, GitLab review apps, platform previews on Vercel, Netlify, Render or Fly, wildcard DNS and TLS, seeded or branched databases, isolated secrets, fork PR safety, PR comments with the URL, and guaranteed teardown on close plus TTL reapers for cost control. Use when a team wants a live URL for every PR, previews leak resources or money, or preview data and secrets need isolating from production.
Use it when
- Reviewers or product owners want to try changes before merge
- Preview environments exist but leak resources or cost
- Preview data or secrets are shared with staging or production
- Fork contributions need previews without exposing credentials
Use something else when
- You only need the production rollout strategy: use deployment-strategies
- The app already deploys on Vercel and needs general project setup: use vercel-deployment
How to check the result
A test PR gets a working TLS URL with seeded data, a second push updates the same environment, closing the PR removes every resource, and a fork PR deploys nothing.
The mistake it prevents
Relying only on the PR-closed event for teardown, so failed or skipped cleanup jobs leave orphaned environments running for months.
A good preview system gives each open PR its own URL within minutes of a push, with realistic but non-production data, secrets that cannot touch production, and teardown that happens automatically on merge or close and again by a TTL reaper for anything missed. Cost is bounded by design, not by someone remembering to clean up.
Inspect first
- Read
AGENTS.mdand the deploy setup: platform (Kubernetes, ECS, a PaaS), how production deploys, and what a full stack needs (DB, cache, queues, third-party APIs). - Check whether the hosting platform already does previews (Vercel, Netlify, Render, Fly, Railway, Heroku review apps). If it does, configure that before building your own.
- Measure the cost of one environment (pods, DB instance, load balancer) and the number of concurrently open PRs. Cost per preview times open PRs is the monthly bill.
- Find dependencies that cannot be duplicated per PR (payment provider webhooks, SSO callback URLs, email sending) and plan stubs or shared sandboxes for them.
Choose the approach
| Situation | Approach |
|---|---|
| Frontend or full stack on a PaaS with built-in previews | Platform previews; add env vars and a DB strategy |
| Kubernetes with Argo CD | ApplicationSet pull request generator, namespace per PR |
| Kubernetes without GitOps | CI job: helm upgrade --install into pr-<n> namespace, teardown job on close |
| GitLab | Review apps: environment with on_stop and auto_stop_in |
| Heavy stack (many services) | Deploy only changed services per PR; route the rest to a shared staging baseline |
Naming and routing
- Identifier:
pr-<number>; it is stable, short and DNS-safe. Branch names are not (length limit 63 for labels, slashes, uppercase). - Wildcard DNS
*.preview.example.comto the ingress, and a wildcard certificate via DNS-01 (cert-manager or the platform). Per-PR HTTP-01 certificates hit rate limits. - URL:
https://pr-123.preview.example.com. Add the preview domain to OAuth redirect allowlists with a wildcard if the provider supports it, or use a shared auth proxy. - Put previews behind authentication (SSO proxy, basic auth, or platform protection) and
X-Robots-Tag: noindex; public previews get indexed and scraped.
Kubernetes with Argo CD
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata: { name: api-previews, namespace: argocd }
spec:
goTemplate: true
generators:
- pullRequest:
github:
owner: acme
repo: api
tokenRef: { secretName: github-token, key: token }
labels: [preview] # opt-in per PR
requeueAfterSeconds: 300
template:
metadata:
name: 'api-pr-{{ .number }}'
spec:
project: previews
source:
repoURL: https://github.com/acme/api.git
targetRevision: '{{ .head_sha }}'
path: deploy/chart
helm:
parameters:
- { name: image.tag, value: 'sha-{{ .head_short_sha }}' }
- { name: ingress.host, value: 'pr-{{ .number }}.preview.example.com' }
destination:
server: https://kubernetes.default.svc
namespace: 'pr-{{ .number }}'
syncPolicy:
automated: { prune: true, selfHeal: true }
syncOptions: [CreateNamespace=true]
When the PR closes or loses the label, the Application is deleted. Make sure the
Application has the resources finalizer so its namespace contents are pruned, and give the
previews AppProject a destination restricted to pr-* namespaces. Apply a ResourceQuota
and LimitRange to each preview namespace.
CI-driven (GitHub Actions)
on:
pull_request:
types: [opened, synchronize, reopened, closed]
concurrency:
group: preview-${{ github.event.pull_request.number }}
cancel-in-progress: true
jobs:
deploy:
if: github.event.action != 'closed' && github.event.pull_request.head.repo.full_name == github.repository
runs-on: ubuntu-latest
environment:
name: preview
url: https://pr-${{ github.event.pull_request.number }}.preview.example.com
steps:
- run: >
helm upgrade --install api-pr-${{ github.event.pull_request.number }} deploy/chart
--namespace pr-${{ github.event.pull_request.number }} --create-namespace
--set image.tag=sha-${{ github.event.pull_request.head.sha }} --wait --timeout 10m
teardown:
if: github.event.action == 'closed' && github.event.pull_request.head.repo.full_name == github.repository
runs-on: ubuntu-latest
steps:
- run: kubectl delete namespace pr-${{ github.event.pull_request.number }} --ignore-not-found
The head.repo.full_name check skips fork PRs: they must never receive deploy credentials.
For fork contributions, let a maintainer trigger a preview after review (label or
workflow_dispatch), never via pull_request_target checking out fork code.
Data
| Option | Use when |
|---|---|
| Seed script with fixtures | Default; fast, deterministic, no personal data |
| Database branching (Neon, Supabase, PlanetScale-style branches) | Need realistic volume; branch from a sanitized parent, never raw production |
| Shared preview DB with schema per PR | Many previews, small data; drop schema on teardown |
| Snapshot of anonymized staging | Large datasets where seeding is too slow |
Run migrations on deploy; a PR with a migration must not touch any shared database. Never copy production personal data into previews.
Secrets and side effects
- Separate credentials for previews, scoped to sandbox accounts: payment test mode, email
to a catch-all (Mailpit or a provider sandbox), feature-flag environment
preview. - Preview secrets live in their own store path or environment; the preview deploy role cannot read production paths.
- Background jobs and cron in previews: disable or point at preview-only queues.
Cleanup and cost
- Teardown on PR close or merge (event-driven)
- Scheduled reaper daily: delete preview namespaces or apps whose PR is closed or whose last deploy is older than N days (7 is common)
- Scale-to-zero or sleep after inactivity where the platform supports it
- Quotas per namespace and a cap on concurrent previews
- Label every resource with
preview=trueandpr=<n>so reapers and cost reports find them
Reapers delete infrastructure. Show the user the selection logic and a dry-run list before enabling deletion, and scope it by label so it can never match non-preview resources.
PR feedback
Post or update a single comment with the URL, commit SHA and status rather than a new comment per push (use the deployment environment URL or an upserted comment keyed by a hidden marker). Report failures with a link to logs.
Verification
- Open a test PR: URL is live with TLS, behind auth, data seeded, within the target time (under 10 minutes is a reasonable goal).
- Push again: the same environment updates; no second namespace appears.
- Close the PR: all resources gone within minutes; the reaper dry run finds nothing left.
- A fork PR does not deploy and does not receive secrets.
What the zip contains
SKILL.md plus the interface file below. Nothing runs on install.
agents/openai.yaml
Gives the Codex app a display name, a one-line description and a starter prompt for this skill.
interface:
display_name: "Preview Environments"
short_description: "A live URL per PR, gone when the PR closes"
default_prompt: "$preview-environments Give every pull request its own preview URL on our Kubernetes cluster" About this skill
Written and maintained by the Codex Skills editors to the same editorial standard as every skill here: a selective description, a real workflow, known pitfalls and a way to verify the work. First published , last updated . MIT licensed. Something wrong or out of date? Tell us.