Repository files navigation

Previa banner

Previa | Self-hosted preview & ephemeral environments

Try the live demo (no install): numnes.github.io/previa
Sign in with admin@demo.local / demo (or operator@demo.local / demo). UI-only mocked data — details in docs/demo.md.

Previa is a self-hosted platform for preview environments, ephemeral environments, and review apps — temporary, isolated deploys you spin up per branch or pull request on infrastructure you control.

Formerly published as deployer; the CLI is now previa (the old deployer command still works as an alias).

Open a PR, trigger a GitHub Action, and get a live deploy preview URL. No Vercel lock-in, no per-seat SaaS. One VPS (or bare metal), nginx, PM2, and a dashboard to manage what is running.

Also useful if you search for: feature-branch environments, dynamic environments, on-demand test environments, PR preview deployments, or a lightweight self-hosted alternative to hosted preview/review-app services.

Each branch gets its own checkout, PM2 process, and nginx route (/{project-slug}/{branch-slug}/). The dashboard shows active, waiting, paused, and failed instances; a global slot limit queues excess deploys until a preview is torn down. Teardown on PR close is supported via workflow.

Self-host on a single machine — or aggregate several previa hosts from one dashboard via cluster credentials.

Coming soon:Kubernetes as a runtime backend for preview instances. Docker is already supported per project (previa project init); PM2 remains the default on the host.

Quick start

Prerequisites

Install these on the machine that will run previa (the install.sh script checks git, Node.js, and Docker):

DependencyUsed forInstall
GitClone previa and app reposgit-scm.com/downloads
Node.js (LTS recommended, v18+)API build, CLI helpersnodejs.org/en/download · nvm
Docker + ComposePostgres, Redis, and web UI containersdocs.docker.com/get-docker
PM2Runs the previa API locally; also runs preview instances on the host (default runner)pm2.keymetrics.io — Quick start (npm install -g pm2)
nginxReverse proxy for preview URLs (/{project-slug}/{branch-slug}/)nginx.org/en/download · Ubuntu/Debian

If PM2 is not installed globally, previa setup falls back to npx pm2 for the API only. For production preview deploys with the PM2 runner, install PM2 on the host.

nginx is required to serve preview URLs to browsers, but not to start the previa stack itself. See Configure nginx.

Install and start

Install the CLI (clones to ~/previa, adds previa to ~/.local/bin):

curl -fsSL https://raw.githubusercontent.com/numnes/previa/main/scripts/install.sh | bash

Make sure ~/.local/bin is on your PATH, then:

previa setup # Postgres + Redis + web (Docker) + API (PM2)
previa status # check services

On first setup you'll be prompted for an admin email and password.

Behind a reverse proxy (one domain / path for UI + API), copy previa.env.exampleprevia.env, set the public URLs, then previa restart. See docs/configuration.md.

previa down # stop everything (asks for confirmation)
previa down -y # skip confirmation
previa help# all commands

Setup in a project

After the previa stack is running, wire each application repository once.

1. Generate workflows and previa.yaml

From your app repo root:

previa project init

This copies:

  • .github/workflows/deploy-preview.yml — deploy on PR open/update
  • .github/workflows/teardown-preview.yml — remove preview on PR close or branch delete
  • previa.yaml — build commands and PM2 entrypoint for your stack

The command detects gitUrl and slug when possible, asks for anything missing, embeds the project slug in the workflow files, and prints a registration JSON block:

{
"slug": "my-app",
"gitUrl": "https://github.com/org/my-app.git",
"serverUrl": "https://preview.example.com"
}

serverUrl is optional in the JSON (omit it if you will set the Public URL later in the dashboard).

Useful options:

previa project init ../my-app # target another directory
previa project init --branches main,develop # PR target branches
previa project init --force # overwrite existing files

Non-interactive (e.g. scripts or CI — still prompts for Public URL unless PREVIA_PROJECT_SERVER_URL is set):

PREVIA_PROJECT_SLUG=my-app \
PREVIA_PROJECT_GIT_URL=https://github.com/org/my-app.git \
PREVIA_PROJECT_SERVER_URL=https://preview.example.com \
previa project init

2. Register the project in the dashboard

  1. Copy the JSON printed by previa project init
  2. Open Projects → Add project → Import registration JSON
  3. Paste the JSON and click Create from JSON (or Apply to form to review first)
  4. Set the Public URL if you already know the domain where previews will be served (see Configure nginx)

3. Create an API key

In the dashboard: Users → API Keys → create a key and save the value (shown once).

4. Configure GitHub secrets

In the app repo on GitHub: Settings → Secrets and variables → Actions

SecretValue
PREVIA_API_URLPublic URL of your previa API (no trailing slash), e.g. https://previa.example.com
PREVIA_API_KEYAPI key from step 3

The project slug is already set in the workflow files — no extra GitHub variable is required.

5. Adjust previa.yaml and commit

Edit previa.yaml for your build (install, build, start command / PM2 target). Then commit and push .github/workflows/ and previa.yaml.

Opening or updating a PR against a configured branch triggers a deploy; closing the PR or deleting the branch runs teardown (if you kept the teardown workflow).

More detail: dashboard Setup → GitHub Actions and Setup → Secrets.

Documentation

TopicGuide
Dashboarddocs/dashboard.md
Instances & lifetimedocs/instances.md
Cluster (multi-machine)docs/cluster.md
Configure nginxdocs/nginx.md
Architecturedocs/architecture.md
Configurationdocs/configuration.md
CLI referencedocs/cli.md
App configexamples/previa.yaml in each project repo
API referencehttp://localhost:3000/docs after previa setup

License

Licensed under the Apache License, Version 2.0.

Built for teams who want simple, self-hosted preview environments.

About

Self-hosted preview & ephemeral environments per branch/PR.

Resources

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Repository files navigation

Previa banner

Previa | Self-hosted preview & ephemeral environments

Try the live demo (no install): numnes.github.io/previa
Sign in with admin@demo.local / demo (or operator@demo.local / demo). UI-only mocked data — details in docs/demo.md.

Previa is a self-hosted platform for preview environments, ephemeral environments, and review apps — temporary, isolated deploys you spin up per branch or pull request on infrastructure you control.

Formerly published as deployer; the CLI is now previa (the old deployer command still works as an alias).

Open a PR, trigger a GitHub Action, and get a live deploy preview URL. No Vercel lock-in, no per-seat SaaS. One VPS (or bare metal), nginx, PM2, and a dashboard to manage what is running.

Also useful if you search for: feature-branch environments, dynamic environments, on-demand test environments, PR preview deployments, or a lightweight self-hosted alternative to hosted preview/review-app services.

Each branch gets its own checkout, PM2 process, and nginx route (/{project-slug}/{branch-slug}/). The dashboard shows active, waiting, paused, and failed instances; a global slot limit queues excess deploys until a preview is torn down. Teardown on PR close is supported via workflow.

Self-host on a single machine — or aggregate several previa hosts from one dashboard via cluster credentials.

Coming soon:Kubernetes as a runtime backend for preview instances. Docker is already supported per project (previa project init); PM2 remains the default on the host.

Quick start

Prerequisites

Install these on the machine that will run previa (the install.sh script checks git, Node.js, and Docker):

DependencyUsed forInstall
GitClone previa and app reposgit-scm.com/downloads
Node.js (LTS recommended, v18+)API build, CLI helpersnodejs.org/en/download · nvm
Docker + ComposePostgres, Redis, and web UI containersdocs.docker.com/get-docker
PM2Runs the previa API locally; also runs preview instances on the host (default runner)pm2.keymetrics.io — Quick start (npm install -g pm2)
nginxReverse proxy for preview URLs (/{project-slug}/{branch-slug}/)nginx.org/en/download · Ubuntu/Debian

If PM2 is not installed globally, previa setup falls back to npx pm2 for the API only. For production preview deploys with the PM2 runner, install PM2 on the host.

nginx is required to serve preview URLs to browsers, but not to start the previa stack itself. See Configure nginx.

Install and start

Install the CLI (clones to ~/previa, adds previa to ~/.local/bin):

curl -fsSL https://raw.githubusercontent.com/numnes/previa/main/scripts/install.sh | bash

Make sure ~/.local/bin is on your PATH, then:

previa setup # Postgres + Redis + web (Docker) + API (PM2)
previa status # check services

On first setup you'll be prompted for an admin email and password.

Behind a reverse proxy (one domain / path for UI + API), copy previa.env.exampleprevia.env, set the public URLs, then previa restart. See docs/configuration.md.

previa down # stop everything (asks for confirmation)
previa down -y # skip confirmation
previa help# all commands

Setup in a project

After the previa stack is running, wire each application repository once.

1. Generate workflows and previa.yaml

From your app repo root:

previa project init

This copies:

  • .github/workflows/deploy-preview.yml — deploy on PR open/update
  • .github/workflows/teardown-preview.yml — remove preview on PR close or branch delete
  • previa.yaml — build commands and PM2 entrypoint for your stack

The command detects gitUrl and slug when possible, asks for anything missing, embeds the project slug in the workflow files, and prints a registration JSON block:

{
"slug": "my-app",
"gitUrl": "https://github.com/org/my-app.git",
"serverUrl": "https://preview.example.com"
}

serverUrl is optional in the JSON (omit it if you will set the Public URL later in the dashboard).

Useful options:

previa project init ../my-app # target another directory
previa project init --branches main,develop # PR target branches
previa project init --force # overwrite existing files

Non-interactive (e.g. scripts or CI — still prompts for Public URL unless PREVIA_PROJECT_SERVER_URL is set):

PREVIA_PROJECT_SLUG=my-app \
PREVIA_PROJECT_GIT_URL=https://github.com/org/my-app.git \
PREVIA_PROJECT_SERVER_URL=https://preview.example.com \
previa project init

2. Register the project in the dashboard

  1. Copy the JSON printed by previa project init
  2. Open Projects → Add project → Import registration JSON
  3. Paste the JSON and click Create from JSON (or Apply to form to review first)
  4. Set the Public URL if you already know the domain where previews will be served (see Configure nginx)

3. Create an API key

In the dashboard: Users → API Keys → create a key and save the value (shown once).

4. Configure GitHub secrets

In the app repo on GitHub: Settings → Secrets and variables → Actions

SecretValue
PREVIA_API_URLPublic URL of your previa API (no trailing slash), e.g. https://previa.example.com
PREVIA_API_KEYAPI key from step 3

The project slug is already set in the workflow files — no extra GitHub variable is required.

5. Adjust previa.yaml and commit

Edit previa.yaml for your build (install, build, start command / PM2 target). Then commit and push .github/workflows/ and previa.yaml.

Opening or updating a PR against a configured branch triggers a deploy; closing the PR or deleting the branch runs teardown (if you kept the teardown workflow).

More detail: dashboard Setup → GitHub Actions and Setup → Secrets.

Documentation

TopicGuide
Dashboarddocs/dashboard.md
Instances & lifetimedocs/instances.md
Cluster (multi-machine)docs/cluster.md
Configure nginxdocs/nginx.md
Architecturedocs/architecture.md
Configurationdocs/configuration.md
CLI referencedocs/cli.md
App configexamples/previa.yaml in each project repo
API referencehttp://localhost:3000/docs after previa setup

License

Licensed under the Apache License, Version 2.0.

Built for teams who want simple, self-hosted preview environments.

About

Self-hosted preview & ephemeral environments per branch/PR.

Resources

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

Previa banner

Previa | Self-hosted preview & ephemeral environments

Try the live demo (no install): numnes.github.io/previa
Sign in with admin@demo.local / demo (or operator@demo.local / demo). UI-only mocked data — details in docs/demo.md.

Previa is a self-hosted platform for preview environments, ephemeral environments, and review apps — temporary, isolated deploys you spin up per branch or pull request on infrastructure you control.

Formerly published as deployer; the CLI is now previa (the old deployer command still works as an alias).

Open a PR, trigger a GitHub Action, and get a live deploy preview URL. No Vercel lock-in, no per-seat SaaS. One VPS (or bare metal), nginx, PM2, and a dashboard to manage what is running.

Also useful if you search for: feature-branch environments, dynamic environments, on-demand test environments, PR preview deployments, or a lightweight self-hosted alternative to hosted preview/review-app services.

Each branch gets its own checkout, PM2 process, and nginx route (/{project-slug}/{branch-slug}/). The dashboard shows active, waiting, paused, and failed instances; a global slot limit queues excess deploys until a preview is torn down. Teardown on PR close is supported via workflow.

Self-host on a single machine — or aggregate several previa hosts from one dashboard via cluster credentials.

Coming soon:Kubernetes as a runtime backend for preview instances. Docker is already supported per project (previa project init); PM2 remains the default on the host.

Quick start

Prerequisites

Install these on the machine that will run previa (the install.sh script checks git, Node.js, and Docker):

DependencyUsed forInstall
GitClone previa and app reposgit-scm.com/downloads
Node.js (LTS recommended, v18+)API build, CLI helpersnodejs.org/en/download · nvm
Docker + ComposePostgres, Redis, and web UI containersdocs.docker.com/get-docker
PM2Runs the previa API locally; also runs preview instances on the host (default runner)pm2.keymetrics.io — Quick start (npm install -g pm2)
nginxReverse proxy for preview URLs (/{project-slug}/{branch-slug}/)nginx.org/en/download · Ubuntu/Debian

If PM2 is not installed globally, previa setup falls back to npx pm2 for the API only. For production preview deploys with the PM2 runner, install PM2 on the host.

nginx is required to serve preview URLs to browsers, but not to start the previa stack itself. See Configure nginx.

Install and start

Install the CLI (clones to ~/previa, adds previa to ~/.local/bin):

curl -fsSL https://raw.githubusercontent.com/numnes/previa/main/scripts/install.sh | bash

Make sure ~/.local/bin is on your PATH, then:

previa setup # Postgres + Redis + web (Docker) + API (PM2)
previa status # check services

On first setup you'll be prompted for an admin email and password.

Behind a reverse proxy (one domain / path for UI + API), copy previa.env.exampleprevia.env, set the public URLs, then previa restart. See docs/configuration.md.

previa down # stop everything (asks for confirmation)
previa down -y # skip confirmation
previa help# all commands

Setup in a project

After the previa stack is running, wire each application repository once.

1. Generate workflows and previa.yaml

From your app repo root:

previa project init

This copies:

  • .github/workflows/deploy-preview.yml — deploy on PR open/update
  • .github/workflows/teardown-preview.yml — remove preview on PR close or branch delete
  • previa.yaml — build commands and PM2 entrypoint for your stack

The command detects gitUrl and slug when possible, asks for anything missing, embeds the project slug in the workflow files, and prints a registration JSON block:

{
"slug": "my-app",
"gitUrl": "https://github.com/org/my-app.git",
"serverUrl": "https://preview.example.com"
}

serverUrl is optional in the JSON (omit it if you will set the Public URL later in the dashboard).

Useful options:

previa project init ../my-app # target another directory
previa project init --branches main,develop # PR target branches
previa project init --force # overwrite existing files

Non-interactive (e.g. scripts or CI — still prompts for Public URL unless PREVIA_PROJECT_SERVER_URL is set):

PREVIA_PROJECT_SLUG=my-app \
PREVIA_PROJECT_GIT_URL=https://github.com/org/my-app.git \
PREVIA_PROJECT_SERVER_URL=https://preview.example.com \
previa project init

2. Register the project in the dashboard

  1. Copy the JSON printed by previa project init
  2. Open Projects → Add project → Import registration JSON
  3. Paste the JSON and click Create from JSON (or Apply to form to review first)
  4. Set the Public URL if you already know the domain where previews will be served (see Configure nginx)

3. Create an API key

In the dashboard: Users → API Keys → create a key and save the value (shown once).

4. Configure GitHub secrets

In the app repo on GitHub: Settings → Secrets and variables → Actions

SecretValue
PREVIA_API_URLPublic URL of your previa API (no trailing slash), e.g. https://previa.example.com
PREVIA_API_KEYAPI key from step 3

The project slug is already set in the workflow files — no extra GitHub variable is required.

5. Adjust previa.yaml and commit

Edit previa.yaml for your build (install, build, start command / PM2 target). Then commit and push .github/workflows/ and previa.yaml.

Opening or updating a PR against a configured branch triggers a deploy; closing the PR or deleting the branch runs teardown (if you kept the teardown workflow).

More detail: dashboard Setup → GitHub Actions and Setup → Secrets.

Documentation

TopicGuide
Dashboarddocs/dashboard.md
Instances & lifetimedocs/instances.md
Cluster (multi-machine)docs/cluster.md
Configure nginxdocs/nginx.md
Architecturedocs/architecture.md
Configurationdocs/configuration.md
CLI referencedocs/cli.md
App configexamples/previa.yaml in each project repo
API referencehttp://localhost:3000/docs after previa setup

License

Licensed under the Apache License, Version 2.0.

Built for teams who want simple, self-hosted preview environments.

About

Self-hosted preview & ephemeral environments per branch/PR.

Resources

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

Previa banner

Previa | Self-hosted preview & ephemeral environments

Try the live demo (no install): numnes.github.io/previa
Sign in with admin@demo.local / demo (or operator@demo.local / demo). UI-only mocked data — details in docs/demo.md.

Previa is a self-hosted platform for preview environments, ephemeral environments, and review apps — temporary, isolated deploys you spin up per branch or pull request on infrastructure you control.

Formerly published as deployer; the CLI is now previa (the old deployer command still works as an alias).

Open a PR, trigger a GitHub Action, and get a live deploy preview URL. No Vercel lock-in, no per-seat SaaS. One VPS (or bare metal), nginx, PM2, and a dashboard to manage what is running.

Also useful if you search for: feature-branch environments, dynamic environments, on-demand test environments, PR preview deployments, or a lightweight self-hosted alternative to hosted preview/review-app services.

Each branch gets its own checkout, PM2 process, and nginx route (/{project-slug}/{branch-slug}/). The dashboard shows active, waiting, paused, and failed instances; a global slot limit queues excess deploys until a preview is torn down. Teardown on PR close is supported via workflow.

Self-host on a single machine — or aggregate several previa hosts from one dashboard via cluster credentials.

Coming soon:Kubernetes as a runtime backend for preview instances. Docker is already supported per project (previa project init); PM2 remains the default on the host.

Quick start

Prerequisites

Install these on the machine that will run previa (the install.sh script checks git, Node.js, and Docker):

DependencyUsed forInstall
GitClone previa and app reposgit-scm.com/downloads
Node.js (LTS recommended, v18+)API build, CLI helpersnodejs.org/en/download · nvm
Docker + ComposePostgres, Redis, and web UI containersdocs.docker.com/get-docker
PM2Runs the previa API locally; also runs preview instances on the host (default runner)pm2.keymetrics.io — Quick start (npm install -g pm2)
nginxReverse proxy for preview URLs (/{project-slug}/{branch-slug}/)nginx.org/en/download · Ubuntu/Debian

If PM2 is not installed globally, previa setup falls back to npx pm2 for the API only. For production preview deploys with the PM2 runner, install PM2 on the host.

nginx is required to serve preview URLs to browsers, but not to start the previa stack itself. See Configure nginx.

Install and start

Install the CLI (clones to ~/previa, adds previa to ~/.local/bin):

curl -fsSL https://raw.githubusercontent.com/numnes/previa/main/scripts/install.sh | bash

Make sure ~/.local/bin is on your PATH, then:

previa setup # Postgres + Redis + web (Docker) + API (PM2)
previa status # check services

On first setup you'll be prompted for an admin email and password.

Behind a reverse proxy (one domain / path for UI + API), copy previa.env.exampleprevia.env, set the public URLs, then previa restart. See docs/configuration.md.

previa down # stop everything (asks for confirmation)
previa down -y # skip confirmation
previa help# all commands

Setup in a project

After the previa stack is running, wire each application repository once.

1. Generate workflows and previa.yaml

From your app repo root:

previa project init

This copies:

  • .github/workflows/deploy-preview.yml — deploy on PR open/update
  • .github/workflows/teardown-preview.yml — remove preview on PR close or branch delete
  • previa.yaml — build commands and PM2 entrypoint for your stack

The command detects gitUrl and slug when possible, asks for anything missing, embeds the project slug in the workflow files, and prints a registration JSON block:

{
"slug": "my-app",
"gitUrl": "https://github.com/org/my-app.git",
"serverUrl": "https://preview.example.com"
}

serverUrl is optional in the JSON (omit it if you will set the Public URL later in the dashboard).

Useful options:

previa project init ../my-app # target another directory
previa project init --branches main,develop # PR target branches
previa project init --force # overwrite existing files

Non-interactive (e.g. scripts or CI — still prompts for Public URL unless PREVIA_PROJECT_SERVER_URL is set):

PREVIA_PROJECT_SLUG=my-app \
PREVIA_PROJECT_GIT_URL=https://github.com/org/my-app.git \
PREVIA_PROJECT_SERVER_URL=https://preview.example.com \
previa project init

2. Register the project in the dashboard

  1. Copy the JSON printed by previa project init
  2. Open Projects → Add project → Import registration JSON
  3. Paste the JSON and click Create from JSON (or Apply to form to review first)
  4. Set the Public URL if you already know the domain where previews will be served (see Configure nginx)

3. Create an API key

In the dashboard: Users → API Keys → create a key and save the value (shown once).

4. Configure GitHub secrets

In the app repo on GitHub: Settings → Secrets and variables → Actions

SecretValue
PREVIA_API_URLPublic URL of your previa API (no trailing slash), e.g. https://previa.example.com
PREVIA_API_KEYAPI key from step 3

The project slug is already set in the workflow files — no extra GitHub variable is required.

5. Adjust previa.yaml and commit

Edit previa.yaml for your build (install, build, start command / PM2 target). Then commit and push .github/workflows/ and previa.yaml.

Opening or updating a PR against a configured branch triggers a deploy; closing the PR or deleting the branch runs teardown (if you kept the teardown workflow).

More detail: dashboard Setup → GitHub Actions and Setup → Secrets.

Documentation

TopicGuide
Dashboarddocs/dashboard.md
Instances & lifetimedocs/instances.md
Cluster (multi-machine)docs/cluster.md
Configure nginxdocs/nginx.md
Architecturedocs/architecture.md
Configurationdocs/configuration.md
CLI referencedocs/cli.md
App configexamples/previa.yaml in each project repo
API referencehttp://localhost:3000/docs after previa setup

License

Licensed under the Apache License, Version 2.0.

Built for teams who want simple, self-hosted preview environments.

About

Self-hosted preview & ephemeral environments per branch/PR.

Resources

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Repository files navigation

Previa banner

Previa | Self-hosted preview & ephemeral environments

Try the live demo (no install): numnes.github.io/previa
Sign in with admin@demo.local / demo (or operator@demo.local / demo). UI-only mocked data — details in docs/demo.md.

Previa is a self-hosted platform for preview environments, ephemeral environments, and review apps — temporary, isolated deploys you spin up per branch or pull request on infrastructure you control.

Formerly published as deployer; the CLI is now previa (the old deployer command still works as an alias).

Open a PR, trigger a GitHub Action, and get a live deploy preview URL. No Vercel lock-in, no per-seat SaaS. One VPS (or bare metal), nginx, PM2, and a dashboard to manage what is running.

Also useful if you search for: feature-branch environments, dynamic environments, on-demand test environments, PR preview deployments, or a lightweight self-hosted alternative to hosted preview/review-app services.

Each branch gets its own checkout, PM2 process, and nginx route (/{project-slug}/{branch-slug}/). The dashboard shows active, waiting, paused, and failed instances; a global slot limit queues excess deploys until a preview is torn down. Teardown on PR close is supported via workflow.

Self-host on a single machine — or aggregate several previa hosts from one dashboard via cluster credentials.

Coming soon:Kubernetes as a runtime backend for preview instances. Docker is already supported per project (previa project init); PM2 remains the default on the host.

Quick start

Prerequisites

Install these on the machine that will run previa (the install.sh script checks git, Node.js, and Docker):

DependencyUsed forInstall
GitClone previa and app reposgit-scm.com/downloads
Node.js (LTS recommended, v18+)API build, CLI helpersnodejs.org/en/download · nvm
Docker + ComposePostgres, Redis, and web UI containersdocs.docker.com/get-docker
PM2Runs the previa API locally; also runs preview instances on the host (default runner)pm2.keymetrics.io — Quick start (npm install -g pm2)
nginxReverse proxy for preview URLs (/{project-slug}/{branch-slug}/)nginx.org/en/download · Ubuntu/Debian

If PM2 is not installed globally, previa setup falls back to npx pm2 for the API only. For production preview deploys with the PM2 runner, install PM2 on the host.

nginx is required to serve preview URLs to browsers, but not to start the previa stack itself. See Configure nginx.

Install and start

Install the CLI (clones to ~/previa, adds previa to ~/.local/bin):

curl -fsSL https://raw.githubusercontent.com/numnes/previa/main/scripts/install.sh | bash

Make sure ~/.local/bin is on your PATH, then:

previa setup # Postgres + Redis + web (Docker) + API (PM2)
previa status # check services

On first setup you'll be prompted for an admin email and password.

Behind a reverse proxy (one domain / path for UI + API), copy previa.env.exampleprevia.env, set the public URLs, then previa restart. See docs/configuration.md.

previa down # stop everything (asks for confirmation)
previa down -y # skip confirmation
previa help# all commands

Setup in a project

After the previa stack is running, wire each application repository once.

1. Generate workflows and previa.yaml

From your app repo root:

previa project init

This copies:

  • .github/workflows/deploy-preview.yml — deploy on PR open/update
  • .github/workflows/teardown-preview.yml — remove preview on PR close or branch delete
  • previa.yaml — build commands and PM2 entrypoint for your stack

The command detects gitUrl and slug when possible, asks for anything missing, embeds the project slug in the workflow files, and prints a registration JSON block:

{
"slug": "my-app",
"gitUrl": "https://github.com/org/my-app.git",
"serverUrl": "https://preview.example.com"
}

serverUrl is optional in the JSON (omit it if you will set the Public URL later in the dashboard).

Useful options:

previa project init ../my-app # target another directory
previa project init --branches main,develop # PR target branches
previa project init --force # overwrite existing files

Non-interactive (e.g. scripts or CI — still prompts for Public URL unless PREVIA_PROJECT_SERVER_URL is set):

PREVIA_PROJECT_SLUG=my-app \
PREVIA_PROJECT_GIT_URL=https://github.com/org/my-app.git \
PREVIA_PROJECT_SERVER_URL=https://preview.example.com \
previa project init

2. Register the project in the dashboard

  1. Copy the JSON printed by previa project init
  2. Open Projects → Add project → Import registration JSON
  3. Paste the JSON and click Create from JSON (or Apply to form to review first)
  4. Set the Public URL if you already know the domain where previews will be served (see Configure nginx)

3. Create an API key

In the dashboard: Users → API Keys → create a key and save the value (shown once).

4. Configure GitHub secrets

In the app repo on GitHub: Settings → Secrets and variables → Actions

SecretValue
PREVIA_API_URLPublic URL of your previa API (no trailing slash), e.g. https://previa.example.com
PREVIA_API_KEYAPI key from step 3

The project slug is already set in the workflow files — no extra GitHub variable is required.

5. Adjust previa.yaml and commit

Edit previa.yaml for your build (install, build, start command / PM2 target). Then commit and push .github/workflows/ and previa.yaml.

Opening or updating a PR against a configured branch triggers a deploy; closing the PR or deleting the branch runs teardown (if you kept the teardown workflow).

More detail: dashboard Setup → GitHub Actions and Setup → Secrets.

Documentation

TopicGuide
Dashboarddocs/dashboard.md
Instances & lifetimedocs/instances.md
Cluster (multi-machine)docs/cluster.md
Configure nginxdocs/nginx.md
Architecturedocs/architecture.md
Configurationdocs/configuration.md
CLI referencedocs/cli.md
App configexamples/previa.yaml in each project repo
API referencehttp://localhost:3000/docs after previa setup

License

Licensed under the Apache License, Version 2.0.

Built for teams who want simple, self-hosted preview environments.

About

Self-hosted preview & ephemeral environments per branch/PR.

Resources

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

Previa banner

Previa | Self-hosted preview & ephemeral environments

Try the live demo (no install): numnes.github.io/previa
Sign in with admin@demo.local / demo (or operator@demo.local / demo). UI-only mocked data — details in docs/demo.md.

Previa is a self-hosted platform for preview environments, ephemeral environments, and review apps — temporary, isolated deploys you spin up per branch or pull request on infrastructure you control.

Formerly published as deployer; the CLI is now previa (the old deployer command still works as an alias).

Open a PR, trigger a GitHub Action, and get a live deploy preview URL. No Vercel lock-in, no per-seat SaaS. One VPS (or bare metal), nginx, PM2, and a dashboard to manage what is running.

Also useful if you search for: feature-branch environments, dynamic environments, on-demand test environments, PR preview deployments, or a lightweight self-hosted alternative to hosted preview/review-app services.

Each branch gets its own checkout, PM2 process, and nginx route (/{project-slug}/{branch-slug}/). The dashboard shows active, waiting, paused, and failed instances; a global slot limit queues excess deploys until a preview is torn down. Teardown on PR close is supported via workflow.

Self-host on a single machine — or aggregate several previa hosts from one dashboard via cluster credentials.

Coming soon:Kubernetes as a runtime backend for preview instances. Docker is already supported per project (previa project init); PM2 remains the default on the host.

Quick start

Prerequisites

Install these on the machine that will run previa (the install.sh script checks git, Node.js, and Docker):

DependencyUsed forInstall
GitClone previa and app reposgit-scm.com/downloads
Node.js (LTS recommended, v18+)API build, CLI helpersnodejs.org/en/download · nvm
Docker + ComposePostgres, Redis, and web UI containersdocs.docker.com/get-docker
PM2Runs the previa API locally; also runs preview instances on the host (default runner)pm2.keymetrics.io — Quick start (npm install -g pm2)
nginxReverse proxy for preview URLs (/{project-slug}/{branch-slug}/)nginx.org/en/download · Ubuntu/Debian

If PM2 is not installed globally, previa setup falls back to npx pm2 for the API only. For production preview deploys with the PM2 runner, install PM2 on the host.

nginx is required to serve preview URLs to browsers, but not to start the previa stack itself. See Configure nginx.

Install and start

Install the CLI (clones to ~/previa, adds previa to ~/.local/bin):

curl -fsSL https://raw.githubusercontent.com/numnes/previa/main/scripts/install.sh | bash

Make sure ~/.local/bin is on your PATH, then:

previa setup # Postgres + Redis + web (Docker) + API (PM2)
previa status # check services

On first setup you'll be prompted for an admin email and password.

Behind a reverse proxy (one domain / path for UI + API), copy previa.env.exampleprevia.env, set the public URLs, then previa restart. See docs/configuration.md.

previa down # stop everything (asks for confirmation)
previa down -y # skip confirmation
previa help# all commands

Setup in a project

After the previa stack is running, wire each application repository once.

1. Generate workflows and previa.yaml

From your app repo root:

previa project init

This copies:

  • .github/workflows/deploy-preview.yml — deploy on PR open/update
  • .github/workflows/teardown-preview.yml — remove preview on PR close or branch delete
  • previa.yaml — build commands and PM2 entrypoint for your stack

The command detects gitUrl and slug when possible, asks for anything missing, embeds the project slug in the workflow files, and prints a registration JSON block:

{
"slug": "my-app",
"gitUrl": "https://github.com/org/my-app.git",
"serverUrl": "https://preview.example.com"
}

serverUrl is optional in the JSON (omit it if you will set the Public URL later in the dashboard).

Useful options:

previa project init ../my-app # target another directory
previa project init --branches main,develop # PR target branches
previa project init --force # overwrite existing files

Non-interactive (e.g. scripts or CI — still prompts for Public URL unless PREVIA_PROJECT_SERVER_URL is set):

PREVIA_PROJECT_SLUG=my-app \
PREVIA_PROJECT_GIT_URL=https://github.com/org/my-app.git \
PREVIA_PROJECT_SERVER_URL=https://preview.example.com \
previa project init

2. Register the project in the dashboard

  1. Copy the JSON printed by previa project init
  2. Open Projects → Add project → Import registration JSON
  3. Paste the JSON and click Create from JSON (or Apply to form to review first)
  4. Set the Public URL if you already know the domain where previews will be served (see Configure nginx)

3. Create an API key

In the dashboard: Users → API Keys → create a key and save the value (shown once).

4. Configure GitHub secrets

In the app repo on GitHub: Settings → Secrets and variables → Actions

SecretValue
PREVIA_API_URLPublic URL of your previa API (no trailing slash), e.g. https://previa.example.com
PREVIA_API_KEYAPI key from step 3

The project slug is already set in the workflow files — no extra GitHub variable is required.

5. Adjust previa.yaml and commit

Edit previa.yaml for your build (install, build, start command / PM2 target). Then commit and push .github/workflows/ and previa.yaml.

Opening or updating a PR against a configured branch triggers a deploy; closing the PR or deleting the branch runs teardown (if you kept the teardown workflow).

More detail: dashboard Setup → GitHub Actions and Setup → Secrets.

Documentation

TopicGuide
Dashboarddocs/dashboard.md
Instances & lifetimedocs/instances.md
Cluster (multi-machine)docs/cluster.md
Configure nginxdocs/nginx.md
Architecturedocs/architecture.md
Configurationdocs/configuration.md
CLI referencedocs/cli.md
App configexamples/previa.yaml in each project repo
API referencehttp://localhost:3000/docs after previa setup

License

Licensed under the Apache License, Version 2.0.

Built for teams who want simple, self-hosted preview environments.

About

Self-hosted preview & ephemeral environments per branch/PR.

Resources

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

Previa banner

Previa | Self-hosted preview & ephemeral environments

Try the live demo (no install): numnes.github.io/previa
Sign in with admin@demo.local / demo (or operator@demo.local / demo). UI-only mocked data — details in docs/demo.md.

Previa is a self-hosted platform for preview environments, ephemeral environments, and review apps — temporary, isolated deploys you spin up per branch or pull request on infrastructure you control.

Formerly published as deployer; the CLI is now previa (the old deployer command still works as an alias).

Open a PR, trigger a GitHub Action, and get a live deploy preview URL. No Vercel lock-in, no per-seat SaaS. One VPS (or bare metal), nginx, PM2, and a dashboard to manage what is running.

Also useful if you search for: feature-branch environments, dynamic environments, on-demand test environments, PR preview deployments, or a lightweight self-hosted alternative to hosted preview/review-app services.

Each branch gets its own checkout, PM2 process, and nginx route (/{project-slug}/{branch-slug}/). The dashboard shows active, waiting, paused, and failed instances; a global slot limit queues excess deploys until a preview is torn down. Teardown on PR close is supported via workflow.

Self-host on a single machine — or aggregate several previa hosts from one dashboard via cluster credentials.

Coming soon:Kubernetes as a runtime backend for preview instances. Docker is already supported per project (previa project init); PM2 remains the default on the host.

Quick start

Prerequisites

Install these on the machine that will run previa (the install.sh script checks git, Node.js, and Docker):

DependencyUsed forInstall
GitClone previa and app reposgit-scm.com/downloads
Node.js (LTS recommended, v18+)API build, CLI helpersnodejs.org/en/download · nvm
Docker + ComposePostgres, Redis, and web UI containersdocs.docker.com/get-docker
PM2Runs the previa API locally; also runs preview instances on the host (default runner)pm2.keymetrics.io — Quick start (npm install -g pm2)
nginxReverse proxy for preview URLs (/{project-slug}/{branch-slug}/)nginx.org/en/download · Ubuntu/Debian

If PM2 is not installed globally, previa setup falls back to npx pm2 for the API only. For production preview deploys with the PM2 runner, install PM2 on the host.

nginx is required to serve preview URLs to browsers, but not to start the previa stack itself. See Configure nginx.

Install and start

Install the CLI (clones to ~/previa, adds previa to ~/.local/bin):

curl -fsSL https://raw.githubusercontent.com/numnes/previa/main/scripts/install.sh | bash

Make sure ~/.local/bin is on your PATH, then:

previa setup # Postgres + Redis + web (Docker) + API (PM2)
previa status # check services

On first setup you'll be prompted for an admin email and password.

Behind a reverse proxy (one domain / path for UI + API), copy previa.env.exampleprevia.env, set the public URLs, then previa restart. See docs/configuration.md.

previa down # stop everything (asks for confirmation)
previa down -y # skip confirmation
previa help# all commands

Setup in a project

After the previa stack is running, wire each application repository once.

1. Generate workflows and previa.yaml

From your app repo root:

previa project init

This copies:

  • .github/workflows/deploy-preview.yml — deploy on PR open/update
  • .github/workflows/teardown-preview.yml — remove preview on PR close or branch delete
  • previa.yaml — build commands and PM2 entrypoint for your stack

The command detects gitUrl and slug when possible, asks for anything missing, embeds the project slug in the workflow files, and prints a registration JSON block:

{
"slug": "my-app",
"gitUrl": "https://github.com/org/my-app.git",
"serverUrl": "https://preview.example.com"
}

serverUrl is optional in the JSON (omit it if you will set the Public URL later in the dashboard).

Useful options:

previa project init ../my-app # target another directory
previa project init --branches main,develop # PR target branches
previa project init --force # overwrite existing files

Non-interactive (e.g. scripts or CI — still prompts for Public URL unless PREVIA_PROJECT_SERVER_URL is set):

PREVIA_PROJECT_SLUG=my-app \
PREVIA_PROJECT_GIT_URL=https://github.com/org/my-app.git \
PREVIA_PROJECT_SERVER_URL=https://preview.example.com \
previa project init

2. Register the project in the dashboard

  1. Copy the JSON printed by previa project init
  2. Open Projects → Add project → Import registration JSON
  3. Paste the JSON and click Create from JSON (or Apply to form to review first)
  4. Set the Public URL if you already know the domain where previews will be served (see Configure nginx)

3. Create an API key

In the dashboard: Users → API Keys → create a key and save the value (shown once).

4. Configure GitHub secrets

In the app repo on GitHub: Settings → Secrets and variables → Actions

SecretValue
PREVIA_API_URLPublic URL of your previa API (no trailing slash), e.g. https://previa.example.com
PREVIA_API_KEYAPI key from step 3

The project slug is already set in the workflow files — no extra GitHub variable is required.

5. Adjust previa.yaml and commit

Edit previa.yaml for your build (install, build, start command / PM2 target). Then commit and push .github/workflows/ and previa.yaml.

Opening or updating a PR against a configured branch triggers a deploy; closing the PR or deleting the branch runs teardown (if you kept the teardown workflow).

More detail: dashboard Setup → GitHub Actions and Setup → Secrets.

Documentation

TopicGuide
Dashboarddocs/dashboard.md
Instances & lifetimedocs/instances.md
Cluster (multi-machine)docs/cluster.md
Configure nginxdocs/nginx.md
Architecturedocs/architecture.md
Configurationdocs/configuration.md
CLI referencedocs/cli.md
App configexamples/previa.yaml in each project repo
API referencehttp://localhost:3000/docs after previa setup

License

Licensed under the Apache License, Version 2.0.

Built for teams who want simple, self-hosted preview environments.

About

Self-hosted preview & ephemeral environments per branch/PR.

Resources

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Repository files navigation

Previa banner

Previa | Self-hosted preview & ephemeral environments

Try the live demo (no install): numnes.github.io/previa
Sign in with admin@demo.local / demo (or operator@demo.local / demo). UI-only mocked data — details in docs/demo.md.

Previa is a self-hosted platform for preview environments, ephemeral environments, and review apps — temporary, isolated deploys you spin up per branch or pull request on infrastructure you control.

Formerly published as deployer; the CLI is now previa (the old deployer command still works as an alias).

Open a PR, trigger a GitHub Action, and get a live deploy preview URL. No Vercel lock-in, no per-seat SaaS. One VPS (or bare metal), nginx, PM2, and a dashboard to manage what is running.

Also useful if you search for: feature-branch environments, dynamic environments, on-demand test environments, PR preview deployments, or a lightweight self-hosted alternative to hosted preview/review-app services.

Each branch gets its own checkout, PM2 process, and nginx route (/{project-slug}/{branch-slug}/). The dashboard shows active, waiting, paused, and failed instances; a global slot limit queues excess deploys until a preview is torn down. Teardown on PR close is supported via workflow.

Self-host on a single machine — or aggregate several previa hosts from one dashboard via cluster credentials.

Coming soon:Kubernetes as a runtime backend for preview instances. Docker is already supported per project (previa project init); PM2 remains the default on the host.

Quick start

Prerequisites

Install these on the machine that will run previa (the install.sh script checks git, Node.js, and Docker):

DependencyUsed forInstall
GitClone previa and app reposgit-scm.com/downloads
Node.js (LTS recommended, v18+)API build, CLI helpersnodejs.org/en/download · nvm
Docker + ComposePostgres, Redis, and web UI containersdocs.docker.com/get-docker
PM2Runs the previa API locally; also runs preview instances on the host (default runner)pm2.keymetrics.io — Quick start (npm install -g pm2)
nginxReverse proxy for preview URLs (/{project-slug}/{branch-slug}/)nginx.org/en/download · Ubuntu/Debian

If PM2 is not installed globally, previa setup falls back to npx pm2 for the API only. For production preview deploys with the PM2 runner, install PM2 on the host.

nginx is required to serve preview URLs to browsers, but not to start the previa stack itself. See Configure nginx.

Install and start

Install the CLI (clones to ~/previa, adds previa to ~/.local/bin):

curl -fsSL https://raw.githubusercontent.com/numnes/previa/main/scripts/install.sh | bash

Make sure ~/.local/bin is on your PATH, then:

previa setup # Postgres + Redis + web (Docker) + API (PM2)
previa status # check services

On first setup you'll be prompted for an admin email and password.

Behind a reverse proxy (one domain / path for UI + API), copy previa.env.exampleprevia.env, set the public URLs, then previa restart. See docs/configuration.md.

previa down # stop everything (asks for confirmation)
previa down -y # skip confirmation
previa help# all commands

Setup in a project

After the previa stack is running, wire each application repository once.

1. Generate workflows and previa.yaml

From your app repo root:

previa project init

This copies:

  • .github/workflows/deploy-preview.yml — deploy on PR open/update
  • .github/workflows/teardown-preview.yml — remove preview on PR close or branch delete
  • previa.yaml — build commands and PM2 entrypoint for your stack

The command detects gitUrl and slug when possible, asks for anything missing, embeds the project slug in the workflow files, and prints a registration JSON block:

{
"slug": "my-app",
"gitUrl": "https://github.com/org/my-app.git",
"serverUrl": "https://preview.example.com"
}

serverUrl is optional in the JSON (omit it if you will set the Public URL later in the dashboard).

Useful options:

previa project init ../my-app # target another directory
previa project init --branches main,develop # PR target branches
previa project init --force # overwrite existing files

Non-interactive (e.g. scripts or CI — still prompts for Public URL unless PREVIA_PROJECT_SERVER_URL is set):

PREVIA_PROJECT_SLUG=my-app \
PREVIA_PROJECT_GIT_URL=https://github.com/org/my-app.git \
PREVIA_PROJECT_SERVER_URL=https://preview.example.com \
previa project init

2. Register the project in the dashboard

  1. Copy the JSON printed by previa project init
  2. Open Projects → Add project → Import registration JSON
  3. Paste the JSON and click Create from JSON (or Apply to form to review first)
  4. Set the Public URL if you already know the domain where previews will be served (see Configure nginx)

3. Create an API key

In the dashboard: Users → API Keys → create a key and save the value (shown once).

4. Configure GitHub secrets

In the app repo on GitHub: Settings → Secrets and variables → Actions

SecretValue
PREVIA_API_URLPublic URL of your previa API (no trailing slash), e.g. https://previa.example.com
PREVIA_API_KEYAPI key from step 3

The project slug is already set in the workflow files — no extra GitHub variable is required.

5. Adjust previa.yaml and commit

Edit previa.yaml for your build (install, build, start command / PM2 target). Then commit and push .github/workflows/ and previa.yaml.

Opening or updating a PR against a configured branch triggers a deploy; closing the PR or deleting the branch runs teardown (if you kept the teardown workflow).

More detail: dashboard Setup → GitHub Actions and Setup → Secrets.

Documentation

TopicGuide
Dashboarddocs/dashboard.md
Instances & lifetimedocs/instances.md
Cluster (multi-machine)docs/cluster.md
Configure nginxdocs/nginx.md
Architecturedocs/architecture.md
Configurationdocs/configuration.md
CLI referencedocs/cli.md
App configexamples/previa.yaml in each project repo
API referencehttp://localhost:3000/docs after previa setup

License

Licensed under the Apache License, Version 2.0.

Built for teams who want simple, self-hosted preview environments.

About

Self-hosted preview & ephemeral environments per branch/PR.

Resources

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages