SELF-HOST GUIDE

Installing RenderPR

A complete, step-by-step walkthrough for self-hosting RenderPR — the GitHub App that visually reviews frontend pull requests. It's BYOC (bring your own cloud): it deploys entirely into your own AWS account, and every secret stays in your SSM Parameter Store.

There are two halves to the install, and the order matters because they feed each other:

  1. 1.Register a GitHub App → gives you an App ID, a private key (.pem), and a webhook secret.
  2. 2.Deploy the AWS infrastructure (CDK) → gives you a public webhook URL.

You create the GitHub App first (leaving its webhook URL blank), deploy AWS to get the URL, inject the GitHub secrets into AWS, then paste the URL back into the GitHub App:

 GitHub App (register)            AWS (deploy via CDK)
 ─────────────────────           ──────────────────────
  App ID                  ──┐
  private key (.pem)      ──┼──►  setup-secrets.sh ──► SSM Parameter Store
  webhook secret          ──┘
                                  cdk deploy ──► ApiGatewayUrl ──┐
                                                                 │
  Webhook URL  ◄─────────────────────────────────────────────  ┘
A note on screenshots:GitHub frequently restyles its settings pages, so each GitHub screen below is shown as a labeled field layout (a text mockup of the form) rather than a screenshot that goes stale. The AWS half is all command-line, so there's nothing to screenshot there.

0 · Prerequisites

Install these on your local machine before starting:

ToolWhyCheck
AWS account + AWS CLIRenderPR deploys here; CLI must be authenticatedaws sts get-caller-identity
Node.js 20+Runs the AWS CDKnode --version
AWS CDK CLIProvisions the infrastructurenpm i -g aws-cdk · cdk --version
Docker (running)CDK builds the Fargate image locallydocker info
Python 3.12+Lambda + agent code (only for tests)python3 --version
jq & opensslUsed by the setup scriptsjq --version · openssl version
OpenRouter API keyLLM that writes the reviewsopenrouter.ai
GitHub account/orgWhere you register & install the app

Your AWS identity needs permission to create: CloudFormation stacks, IAM roles, VPC + security groups, ECS clusters/tasks, Lambda, API Gateway, S3, CloudFront, and SSM parameters. An admin role is simplest; a scoped role works if it covers all of the above.

Make sure your AWS CLI is pointed at the region you want to deploy to (everything lives in one region):

your terminal
# set Access Key, Secret Key, and default region (e.g. eu-west-1)
$ aws configure

# confirm you're authenticated as the right account
$ aws sts get-caller-identity

1 · Register the GitHub App

Go to one of these, depending on whether the app belongs to your personal account or an organization:

  • Personal: GitHub → your avatar → SettingsDeveloper settings GitHub AppsNew GitHub App
  • Organization: GitHub → Your organizations → pick the org → SettingsDeveloper settings GitHub AppsNew GitHub App

Direct link (personal): github.com/settings/apps/new

Fill in the “Register new GitHub App” form. Here is the full form with the values to use — leave anything not listed at its default:

Register new GitHub App
GitHub App name
renderpr-<your-name>
must be unique
Homepage URL
https://github.com/pelazas/renderpr
Identifying and authorizing users
Callback URL
(blank)
Webhook
Active
Webhook URL
leave blank for now — set in step 4
Webhook secret★ SAVE IT
<paste a long random string>
Repository permissions
ContentsRead and writeclone, diff, apply
Pull requestsRead and writeread PRs, post review
IssuesRead and writePR comments
everything elseNo access
Subscribe to events
Pull request
Issue comment
Where can this GitHub App be installed?
Only on this accountrecommended for a private deploy
Create GitHub App

Notes on the fields:

  • Webhook secret— generate one and keep it; you'll feed it into AWS in step 3. A good way to make one is openssl rand -hex 32. RenderPR's Lambda verifies every webhook's X-Hub-Signature-256 HMAC against this exact value, so it must match what you store in AWS.
  • Webhook URL— you don't have it yet (it comes from the AWS deploy). Leave it blank; you'll set the real one in step 4.
  • Permissions / events— these are the minimum RenderPR's code uses. Contents read and write is required because @renderpr apply commits proposed changes back to the PR branch. The Lambda only acts on Pull request and Issue comment events.

1a · Generate the private key

After clicking Create GitHub App, you land on the app's settings page. Scroll to the Private keys section:

Private keys

Generate a private key to sign access token requests.

Generate a private key

Click Generate a private key. A .pem file downloads automatically (e.g. renderpr-yourname.2026-06-16.private-key.pem). Save it somewhere safe — you give its path to the setup script in step 3. GitHub never shows it again.

1b · Note the App ID

At the top of the same page (the About section):

About
App ID★ NOTE THIS NUMBER
123456
Client ID
Iv1.xxxxxxxx
not needed by RenderPR

Write down the App ID— you'll enter it in step 3.

1c · Install the app on your repositories

In the left sidebar of the app's settings, click Install App, then click Install next to your account/org:

Install renderpr-yourname

Install on:

All repositories
Only select repositories
your-org/your-frontend-repo
Install

Pick Only select repositories and choose the frontend repo(s) you want reviewed (you can add more later). Click Install.

After installing, the URL becomes github.com/settings/installations/<INSTALLATION_ID>. That trailing number is the installation ID — you only need it later if you want to store per-repo env vars / auth secrets (see the optional section below).

At this point GitHub will start sending webhooks, but they'll fail until the AWS endpoint exists. That's expected — let's deploy it now.

2 · Deploy the AWS infrastructure

Clone the repo and install the CDK dependencies:

your terminal
$ git clone https://github.com/pelazas/renderpr.git
$ cd renderpr/cdk
$ npm install

2a · Bootstrap CDK (one-time per account + region)

your terminal
$ npx cdk bootstrap

This creates the CDK's own support stack (an S3 bucket and IAM roles it uses to deploy). You only do this once per AWS account/region.

2b · Create the CloudFront signing key (required before first deploy)

Screenshots can contain private/authenticated UI, so RenderPR keeps the S3 bucket fully private and serves screenshots through CloudFront using signed URLs. The deploy will fail fastif this key doesn't exist yet, so run this first, from the repo root:

your terminal
$ cd ..  # back to the repo root
$ ./scripts/setup-cloudfront-key.sh

This generates an RSA-2048 key pair, stores the private key in SSM (/renderpr/cloudfront-private-key, encrypted), and writes the public key to cdk/cloudfront-public-key.pem (gitignored). CDK reads that public key at deploy time.

2c · (Optional) tune cost / behavior knobs

The deploy works with zero configuration. If you want to change defaults, edit the context block in cdk/cdk.json or pass -c key=value flags to cdk deploy. The knobs the stack actually reads:

Context keyDefaultWhat it does
maxConcurrentTasks5Hard cap on simultaneous review containers (cost guard)
maxTaskAgeSeconds1500Reaper force-stops tasks older than this
reaperIntervalMinutes5How often the cleanup Lambda runs
webhookRateLimit / Burst20 / 40API Gateway throttling
idleTimeoutSeconds900Idle time (15 min) before a task self-terminates
pollIntervalSeconds10Seconds between PR-comment polls
Don't bother setting commandToken, openrouterApiKey, or repositoryAllowlist in cdk.json — older docs mention these but the current stack does not read them. The command token is generated automatically by the secrets script (step 3), and the OpenRouter key is prompted there too.

2d · Deploy

your terminal
$ cd cdk
$ npx cdk deploy

The first deploy takes several minutes (it builds the Docker image, creates a VPC, ECS cluster, Lambda functions, API Gateway, S3, and a CloudFront distribution). Approve the IAM/security-group changes when prompted.

When it finishes, CDK prints an Outputs block. Copy the ApiGatewayUrl— that's your webhook URL:

cdk outputs
Outputs:
RenderprStack.ApiGatewayUrl = https://abc123xyz.execute-api.eu-west-1.amazonaws.com/
RenderprStack.ClusterArn = arn:aws:ecs:eu-west-1:...:cluster/renderpr-cluster
RenderprStack.GitHubParamName = /renderpr/github-app
RenderprStack.ScreenshotBucketName = renderpr-screenshots-<account-id>
RenderprStack.ScreenshotDistributionDomain = dxxxx.cloudfront.net
RenderprStack.TaskDefinitionArn = arn:aws:ecs:...:task-definition/renderpr-review:N

3 · Inject the GitHub + OpenRouter secrets into SSM

From the repo root, run:

your terminal
$ bash scripts/setup-secrets.sh

It prompts for four things you collected in step 1 (plus your OpenRouter key):

=== RenderPR — Post-Deploy Secret Injection ===

GitHub App ID:  123456
Path to GitHub App private key (.pem file):  ~/Downloads/renderpr-yourname.2026-06-16.private-key.pem
Webhook Secret:  ********              (the value from step 1)
OpenRouter API Key:  ********          (sk-or-v1-...)

This writes:

  • /renderpr/github-app — JSON {app_id, private_key, webhook_secret} (encrypted)
  • /renderpr/openrouter — your OpenRouter key (encrypted)
  • /renderpr/renderpr-command-token— a fresh random token it generates for you, if one doesn't already exist (this is the Lambda ↔ container auth for @renderpr conversational commands)

The Fargate task role has read-only access to these.

Important: the Lambda reads the webhook secret once at cold start. If you change the secret later, force a new cold start (re-deploy, or update the Lambda) so it picks up the new value. The very first time, the Lambda was created in step 2 before the secret existed — so trigger a fresh invocation (the next webhook spins up a new execution environment that reads it). If verification in step 5 fails with signature errors, re-run cdk deploy to roll the Lambda.

4 · Point the GitHub App at your webhook URL

Back in the GitHub App settings (Developer settings → GitHub Apps → your app → General), find the Webhook section and set the URL to the ApiGatewayUrl from step 2d:

Webhook
Active
Webhook URL
https://abc123xyz.execute-api.eu-west-1.amazonaws.com/
Webhook secret
(unchanged — same value you stored in step 3)
Content type
application/json
Save changes

Click Save changes. GitHub immediately sends a ping to test it.

You can check delivery health under the app's Advanced tab → Recent Deliveries, where each webhook shows a green ✓ (2xx) or red ✗ with the response body — handy for debugging.

5 · Verify the installation

  1. 1.Open a pull request that touches the frontend in one of your installed repos (or comment @renderpr review this on an existing PR).
  2. 2.Within a few seconds you should see a live progress comment from the app: a checklist that updates through setup → install → dev server → screenshots → review.
  3. 3.It edits that same comment into the final review — screenshots at 4 viewports plus an LLM analysis of the diff.
  4. 4.The review includes a live preview link (http://<public-ip>:3000) to the running app. It works only while the task is alive (15-minute idle window).

Try the conversational commands while the task is still running:

  • @renderpr code change: make the header sticky — the LLM edits the cloned repo, re-runs the dev server, and posts updated screenshots.
  • @renderpr apply — commits the proposed change to the PR branch.
If something doesn't show up, jump to Troubleshooting below.

6 · (Optional) Per-repo env vars & auth-gated apps

If your app needs environment variables to build, or hides pages behind a login, store per-repo secrets in SSM and add a .renderpr.yml to the repo.

Store secrets (you need the installation ID from step 1c):

your terminal
$ scripts/renderpr-secrets.sh <installation_id> <owner/repo> \
    NEXT_PUBLIC_API_URL=https://api.example.com \
    NEXTAUTH_SECRET="$(openssl rand -hex 32)"

They're stored at /renderpr/secrets/{installation_id}/{owner}/{repo}/{KEY}, and each review task can only read its ownrepo's path.

Then commit a .renderpr.yml to the repo root:

.renderpr.yml

env:
  from: .env.example          # which vars to inject from your stored secrets
auth:
  type: jwt                   # nextauth | jwt | supabase | clerk | firebase
  user:
    email: preview@example.com
    name: Preview User
    role: admin
Secrets are never injected on fork PRs. Full details and the per-provider auth matrix are in docs/AUTH_AND_ENV.md.

7 · Updating and tearing down

Update (after pulling new code or changing config)

your terminal
$cd cdk && npx cdk deploy

CDK diffs and updates only what changed. SSM secrets are preserved. There's also a GitHub Actions workflow (.github/workflows/deploy.yml) that redeploys automatically when src/**, cdk/**, or the Dockerfile change.

Tear down (removes all AWS resources this stack created)

your terminal
$cd cdk && npx cdk destroy

SSM parameters are not deleted by destroy (they persist in your account); the CloudFront key, GitHub credentials, and OpenRouter key stay unless you remove them manually with aws ssm delete-parameter.

Troubleshooting

Webhook deliveries show ✗ with 401/403
Webhook secret in GitHub ≠ value in SSM, or Lambda is still holding the old/empty secret. Re-run setup-secrets.sh, then cdk deploy to roll the Lambda.
cdk deploy errors: “CloudFront signing public key not found”
You skipped step 2b. Run ./scripts/setup-cloudfront-key.sh, then deploy again.
No progress comment appears
Check the Recent Deliveries tab (is GitHub even reaching the URL?) and CloudWatch Logs for the WebhookHandler Lambda. Confirm the app is installed on that repo and Pull request / Issue comment events are subscribed.
Fargate task starts then dies
CloudWatch Logs for the task. Common: missing SSM secrets, bad .pem formatting, or the cloned project's npm ci / npm run dev fails.
No review comment, task ran
Invalid OpenRouter key, or the GitHub installation token couldn't be minted. Verify /renderpr/openrouter and the private key in /renderpr/github-app.
Live preview link dead
Expected after the 15-min idle window — the link uses the task's ephemeral public IP.
npm ci fails on native modules
The Docker image bundles common build deps; if a project needs another, extend the apt-get install line in the Dockerfile and re-deploy.
For deeper architecture and runtime detail see docs/ARCHITECTURE.md, docs/DEPLOYMENT.md, and docs/WORKFLOW.md.

That's the whole install.

Star the repo to follow along, or grab the source and deploy it into your own AWS in a few minutes.