Build a Quality-Gated Static Site
Deploy a quality-gated static site with GitHub Actions and Render.
Introduction
30 Second Summary
A small website update can replace a working page with a broken one in seconds. Without an automated checkpoint, every change carries that risk.
In this project, you will build a release-status website from a private GitHub repository. You will use GitHub Actions to test each release before Render publishes it through a quality-gated CI/CD pipeline.
What You'll Build
You will watch a bad release stop at the gate while your last working page stays live for visitors.
By the end of this project, you'll have:
- A public release-status website built from private source code and available through an `onrender.com` URL.
- An automated release gate that tests every change before it can update the live site.
- A complete rollback recovery drill that restores a specific prior release before returning the approved version to production.
- Secret Mission: Add a second rule that blocks deployment until a release-proof footer exists.
Are there any prerequisites?
You need a GitHub account, a Render account, and a modern browser. The project uses browser tools and cloud runners, so you do not need to install local software.
Before We Start
Before the hands-on work begins, lock in the release pipeline you are about to build and why its quality gate matters. Your private source will produce a public Render site only after GitHub Actions checks pass.
Set Up the Private Repository
The quality gate needs a private source repository before it can protect a public release. GitHub gives that source a controlled home.
Render needs explicit permission to discover the private repository. In this step, you will create the repository before giving Render access to that source.
In this step, get ready to:
- Prepare signed-in browser sessions for the project accounts.
- Create quality-gated-site as a private repository with an initialized README.
- Connect Render to GitHub with access to quality-gated-site.
Sign in to the project accounts
GitHub holds the private source for your site. Render later reads that source through an authorized connection.
- Go to GitHub in your browser.
- Sign in with the GitHub account you prepared for this project.
- Open a second browser tab.
- Go to Render in the new tab.
- Sign in with the Render account you prepared for this project.
- Return to the GitHub tab to confirm your account is signed in.
- Return to the Render tab to confirm your account is signed in.
Both project accounts are ready in your browser for the connection ahead.
Can't sign in?
Use help me troubleshoot my account access if either account rejects your sign-in. Share only non-sensitive error details with the NextWork community if the problem depends on your account.
Create the private repository
A private repository keeps your source hidden while Render later serves the finished page publicly. The initialized README gives the repository a starting file on its default branch.
- Switch back to the GitHub tab.
- Select New repository.
- Enter quality-gated-site as the repository name.
- Select Private under repository visibility.
- Turn on the option that initializes the repository with a README.
- Select Create repository.
GitHub opens quality-gated-site on its default branch. You'll see the repository's private status plus its initialized README.
That is the first foundation in place. Your source now has a private home.
Connect Render to the repository
Render cannot read private source until GitHub grants access through Git Deployment Credentials. The connection flow opens a GitHub authorization screen.
Account connections can feel broad, so you will limit this one to quality-gated-site.
- Switch back to the Render tab.
- Open Account Settings.
- Select Account Security.
- Find Git Deployment Credentials.
- Select Add credential.
- Choose GitHub as the Git provider.
- Configure Repository access to include only quality-gated-site.
- Approve the GitHub connection.
When authorization finishes, you'll see GitHub listed under Git Deployment Credentials in Render.
Credential not listed?
Use help me troubleshoot the GitHub connection if the credential does not appear. Share the non-sensitive details with the NextWork community if the issue depends on account ownership.
The final check asks whether the credential can see the right repository. Before you check, do you expect the picker to include quality-gated-site?
- Return to the Render Dashboard.
- Select New.
- Select Static Site to open the repository picker.
- Look for quality-gated-site without selecting it.
You'll see quality-gated-site listed as an available private repository. This proves Render has access without creating a Render Static Site.
Your private repository is now visible to Render through the scoped GitHub connection.
Your private repository is ready for source files. Next, you will build the first release for Render to publish.
Ship the First Render Release
Your private GitHub repository is ready. It now needs a release that people can actually see.
A working Render Static Site gives your later quality gate a baseline. A failed check can then prove its value by leaving the last good page online.
In this step, get ready to:
- Pin the Node.js runtime to 24.21.0.
- Create the build script plus the first-release page.
- Publish the page at a public Render URL.
Create the build files
Render needs a repeatable build before it can publish your page. Node.js runs a small script that copies the website into the deployment folder.
The .node-version file pins the runtime. The index.js file creates a fresh dist/ folder for every build.
- In the GitHub repository from earlier, select Add file.
- Select Create new file.
- Enter .node-version in the filename field.
- Paste the pinned runtime version into the editor using the code below:
24.21.0
Why Pin This Version?
The .node-version file tells Render to use Node.js 24.21.0. The same pin later keeps the automated checks on the matching runtime.
- Select Commit changes....
- Submit the commit from the commit dialog.
- Confirm that .node-version appears in the repository file list.
Don't See the Runtime File?
Check that the filename starts with a full stop. Confirm that you committed it to the repository's default branch.
Help me find why my .node-version file is missing from my GitHub repository.
✔️ Awesome, I've got everything!
Your runtime pin is committed to the repository.
ⓧ I'd like to double check the full code
24.21.0
This complete file contains the single Node.js version used by the project.
- Return to the repository file list.
- Select Add file.
- Select Create new file.
- Enter index.js in the filename field.
- Paste the build script into the editor using the code below:
const { cpSync, rmSync } = require('node:fs');
rmSync('dist', { recursive: true, force: true });
cpSync('public', 'dist', { recursive: true });
console.log('Built static site into dist/');
What Does This Build Script Do?
- The file-system helpers let the script remove folders plus copy files.
- The rmSync() call removes old dist/ output so stale files cannot survive another build.
- The cpSync() call copies everything from public/ into dist/.
- The final log line confirms that the deployment folder was built.
- Select Commit changes....
- Submit the commit from the commit dialog.
- Confirm that index.js appears beside .node-version in the repository file list.
Build Script Missing or Incomplete?
Confirm that the filename is exactly index.js. Compare each quote plus bracket with the reference below.
Help me compare my index.js build script with the expected version.
✔️ Awesome, I've got everything!
Your build script is committed beside the runtime pin.
ⓧ I'd like to double check the full code
const { cpSync, rmSync } = require('node:fs');
rmSync('dist', { recursive: true, force: true });
cpSync('public', 'dist', { recursive: true });
console.log('Built static site into dist/');
This complete script rebuilds dist/ from the source files in public/.
Good work. Your repository now has a pinned runtime plus a repeatable build process.
Add the first-release page
Your website source belongs in public/. The generated build output belongs in dist/.
This first page deliberately describes a release with no automated gate. That visible baseline lets you prove later that a failed release cannot replace it.
- Return to the repository file list.
- Select Add file.
- Select Create new file.
- Enter public/index.html in the filename field.
- Paste the complete first-release page into the editor using the code below:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Quality-Gated Release</title>
<style>
:root {
color-scheme: dark;
font-family: Inter, ui-sans-serif, system-ui, sans-serif;
background: #07111f;
color: #e8f0ff;
}
* {
box-sizing: border-box;
}
body {
min-height: 100vh;
margin: 0;
display: grid;
place-items: center;
padding: 24px;
background:
radial-gradient(circle at top left, #173663 0, transparent 42%),
linear-gradient(145deg, #07111f, #0b1729 60%, #0d2036);
}
.card {
width: min(680px, 100%);
padding: clamp(28px, 6vw, 56px);
border: 1px solid #27466d;
border-radius: 28px;
background: rgba(9, 24, 43, 0.88);
box-shadow: 0 24px 80px rgba(0, 0, 0, 0.35);
}
.eyebrow {
margin: 0 0 12px;
color: #7dd3fc;
font-size: 0.82rem;
font-weight: 800;
letter-spacing: 0.14em;
text-transform: uppercase;
}
h1 {
margin: 0;
font-size: clamp(2.25rem, 8vw, 4.75rem);
line-height: 0.98;
letter-spacing: -0.055em;
}
.summary {
margin: 24px 0 32px;
color: #b6c8e2;
font-size: 1.1rem;
line-height: 1.7;
}
.status {
display: grid;
grid-template-columns: auto 1fr;
gap: 4px 12px;
align-items: center;
padding: 18px;
border: 1px solid #1f6f55;
border-radius: 18px;
background: rgba(16, 80, 61, 0.28);
}
.dot {
grid-row: 1 / 3;
width: 14px;
height: 14px;
border-radius: 50%;
background: #34d399;
box-shadow: 0 0 22px #34d399;
}
.status span:last-child {
color: #a7f3d0;
}
</style>
</head>
<body>
<main class="card">
<p class="eyebrow">Quality-gated deployment</p>
<h1>My first Render release is live.</h1>
<p class="summary">Private source. Public site. Automated checks between every change and deployment.</p>
<div class="status" aria-label="Release status">
<span class="dot" aria-hidden="true"></span>
<strong>First deploy</strong>
<span>No automated gate yet.</span>
</div>
</main>
</body>
</html>
What Does This Page Show?
- The style rules create a centered release card with a dark background.
- The status panel labels this version as First deploy.
- The message No automated gate yet. records the pipeline's current state.
- The missing release marker gives the next step a deliberate test failure to detect.
- Select Commit changes....
- Submit the commit from the commit dialog.
- Confirm that the repository file list contains a public folder.
- Select the public folder to confirm that it contains index.html.
Can't Find the Page?
Check that the filename contains the forward slash in public/index.html. GitHub uses that path to create the folder plus the file.
Help me find why public/index.html was not created in the expected folder.
✔️ Awesome, I've got everything!
Your first-release page is committed inside public/.
ⓧ I'd like to double check the full code
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Quality-Gated Release</title>
<style>
:root {
color-scheme: dark;
font-family: Inter, ui-sans-serif, system-ui, sans-serif;
background: #07111f;
color: #e8f0ff;
}
* {
box-sizing: border-box;
}
body {
min-height: 100vh;
margin: 0;
display: grid;
place-items: center;
padding: 24px;
background:
radial-gradient(circle at top left, #173663 0, transparent 42%),
linear-gradient(145deg, #07111f, #0b1729 60%, #0d2036);
}
.card {
width: min(680px, 100%);
padding: clamp(28px, 6vw, 56px);
border: 1px solid #27466d;
border-radius: 28px;
background: rgba(9, 24, 43, 0.88);
box-shadow: 0 24px 80px rgba(0, 0, 0, 0.35);
}
.eyebrow {
margin: 0 0 12px;
color: #7dd3fc;
font-size: 0.82rem;
font-weight: 800;
letter-spacing: 0.14em;
text-transform: uppercase;
}
h1 {
margin: 0;
font-size: clamp(2.25rem, 8vw, 4.75rem);
line-height: 0.98;
letter-spacing: -0.055em;
}
.summary {
margin: 24px 0 32px;
color: #b6c8e2;
font-size: 1.1rem;
line-height: 1.7;
}
.status {
display: grid;
grid-template-columns: auto 1fr;
gap: 4px 12px;
align-items: center;
padding: 18px;
border: 1px solid #1f6f55;
border-radius: 18px;
background: rgba(16, 80, 61, 0.28);
}
.dot {
grid-row: 1 / 3;
width: 14px;
height: 14px;
border-radius: 50%;
background: #34d399;
box-shadow: 0 0 22px #34d399;
}
.status span:last-child {
color: #a7f3d0;
}
</style>
</head>
<body>
<main class="card">
<p class="eyebrow">Quality-gated deployment</p>
<h1>My first Render release is live.</h1>
<p class="summary">Private source. Public site. Automated checks between every change and deployment.</p>
<div class="status" aria-label="Release status">
<span class="dot" aria-hidden="true"></span>
<strong>First deploy</strong>
<span>No automated gate yet.</span>
</div>
</main>
</body>
</html>
This complete file matches the first-release page that Render publishes in this step.
Deploy the site on Render
The repository now contains source files plus a build process. Render can connect to the default branch before publishing the generated dist/ folder.
Why Build Into a Separate Folder?
The public/ folder holds the source you edit. The dist/ folder holds the clean output Render serves.
Rebuilding that output for every deployment prevents an old generated file from reaching the public site.
- Switch back to the Render Dashboard from earlier.
- Select New.
- Select Static Site.
- Choose quality-gated-site from the repository picker.
- Choose the repository's default branch.
- Enter node index.js in the Build Command field.
- Enter dist in the Publish Directory field.
- Select Create Static Site.
The first cloud build can take a few minutes while Render prepares the service. A quiet Deploys page during this period does not mean the build is stuck.
- Stay on the Deploys page until the current deployment shows a Live badge.
- Check the build log for Built static site into dist/.
Deployment Did Not Become Live?
Confirm that the Build Command matches node index.js. Confirm that the Publish Directory matches dist.
Check that index.js plus public/index.html exist on the connected default branch.
Help me diagnose why my Render Static Site build did not become Live. Share a redacted build log in the NextWork community if you still need help.
- Record the generated public URL here: your generated onrender.com URL.
Before you open the public URL, what release status do you expect the page to show?
- Open your generated onrender.com URL in a new browser tab.
You'll see the release card with First deploy plus No automated gate yet. This proves that the private repository can produce a public release.
That is a real milestone: your private source now produces a public release. Next, you'll add a check that blocks an invalid update.
Make CI Block a Bad Release
Your first release is live on Render. You now have a known-good page to protect.
A push-triggered deployment still lacks a quality gate. In this step, GitHub Actions rejects a deliberately invalid release before Render can replace the working page.
In this step, get ready to:
- Set Render to wait for GitHub Actions checks.
- Add an automated release test with its workflow.
- Confirm the rejected commit leaves the first release live.
Turn on the CI deployment gate
A deployment gate links continuous integration to publishing. The gate lets Render evaluate the result of every check before it starts a deployment.
- Return to the Render Dashboard from earlier.
- Select the quality-gated-site Static Site.
- Select Settings in the service navigation.
- Find Auto-Deploy.
- Select After CI Checks Pass.
- Confirm Auto-Deploy displays After CI Checks Pass.
How does the gate protect the site?
Render now waits for every check associated with a commit. A failed check prevents that commit from deploying.
Render also holds a commit when it detects zero checks. The existing successful deployment stays online while the new commit waits or fails.
Add the failing release check
An automated test turns a release requirement into a pass-or-fail result. This test reads the built page from dist/ and checks for its required release marker.
- Switch back to the quality-gated-site repository on GitHub.
- Leave public/index.html unchanged for this experiment.
- Select Add file above the repository file list.
- Select Create new file.
- Enter test/release.test.js in the filename field.
- Add the release test by pasting this code into the editor:
const assert = require('node:assert/strict');
const { readFileSync } = require('node:fs');
const test = require('node:test');
test('release marker is present', () => {
const html = readFileSync('dist/index.html', 'utf8');
assert.match(html, /data-release="ready"/);
});
What does this test do?
- The built-in Node.js test runner registers a check named release marker is present.
- The readFileSync function loads the generated dist/index.html file.
- The assert.match assertion requires the built HTML to contain data-release="ready".
- Select Commit changes....
- Confirm the commit to the repository's default branch.
- Confirm the repository now displays test/release.test.js.
Can't see the new test file?
Check that the file path is test/release.test.js. A different folder keeps the workflow from discovering this test.
Help me check my release test file.
Good progress. Your release requirement now lives as an executable test in the private repository.
The workflow gives that test a GitHub-hosted runner whenever code is pushed. It also checks pull requests before their changes are merged.
- Return to the repository's Code page.
- Select Add file above the repository file list.
- Select Create new file.
- Enter .github/workflows/validate.yml in the filename field.
- Add the validation workflow by pasting this code into the editor:
name: Validate release
on:
push:
pull_request:
permissions:
contents: read
jobs:
validate:
runs-on: ubuntu-latest
steps:
- name: Check out repository
uses: actions/checkout@v7.0.1
- name: Use the pinned Node.js version
uses: actions/setup-node@v7.1.0
with:
node-version-file: '.node-version'
package-manager-cache: false
- name: Build site
run: node index.js
- name: Require the release marker
run: node --test
What does this workflow do?
- The push trigger checks commits sent to the repository. The pull_request trigger checks proposed changes.
- The permissions: contents: read setting gives the workflow read access to the repository contents.
- The actions/checkout@v7.0.1 step copies the private repository into the workflow runner.
- The actions/setup-node@v7.1.0 step reads .node-version. This keeps the workflow runtime aligned with the Render build.
- The Build site step creates dist/ from the source site.
- The Require the release marker step runs the tests against that build output.
Before you commit this workflow, do you think the unchanged first release will satisfy the marker test?
- Select Commit changes....
- Confirm the commit to the repository's default branch.
- Confirm the repository now displays .github/workflows/validate.yml.
Workflow file missing?
Confirm that the workflow is stored at .github/workflows/validate.yml. GitHub only discovers workflow files inside .github/workflows.
Help me check my workflow path.
✔️ Awesome, I've got everything!
Great. Confirm both new files are committed to the repository's default branch.
ⓧ I'd like to double check the full code
Compare your two new files with these complete versions.
const assert = require('node:assert/strict');
const { readFileSync } = require('node:fs');
const test = require('node:test');
test('release marker is present', () => {
const html = readFileSync('dist/index.html', 'utf8');
assert.match(html, /data-release="ready"/);
});
name: Validate release
on:
push:
pull_request:
permissions:
contents: read
jobs:
validate:
runs-on: ubuntu-latest
steps:
- name: Check out repository
uses: actions/checkout@v7.0.1
- name: Use the pinned Node.js version
uses: actions/setup-node@v7.1.0
with:
node-version-file: '.node-version'
package-manager-cache: false
- name: Build site
run: node index.js
- name: Require the release marker
run: node --test
Watch the bad release get blocked
Committing the workflow starts its first validation run. This run reveals whether the current release meets the rule you just encoded.
Before you check the run, do you expect the unchanged first release to pass or fail?
- Select Actions in the GitHub repository navigation.
- Select the latest Validate release workflow run.
- Open the Require the release marker step.
- Confirm the workflow is marked red.
The failure is intentional
The Require the release marker step fails because public/index.html does not contain data-release="ready".
The red run proves the gate has a meaningful condition. A push alone no longer earns a deployment.
Before you check Render, do you expect the failing commit to replace the live deployment?
- Switch back to the Render service from earlier.
- Select Deploys in the service navigation.
- Check which deployment carries the Live badge.
You'll see that the original successful deployment is still live. Render did not create a replacement deployment for the failing commit.
Before you refresh the public site, predict which release text the page will show.
- Return to the generated public onrender.com URL from earlier.
- Refresh the page in your browser.
You'll still see First deploy with No automated gate yet. The failed release never replaced the known-good page.
Not seeing the blocked release?
- If no workflow run appears, confirm that .github/workflows/validate.yml is committed to the default branch.
- If the workflow is green, compare test/release.test.js with the double-check tab. Also confirm public/index.html still lacks the required marker.
- If Render starts a replacement deployment, return to Settings. Reset Auto-Deploy to After CI Checks Pass.
Help me diagnose my deployment gate.
That is the gate doing its job. Your public page survived a bad release because the automated check rejected it first.
Next, you will satisfy the release requirement and watch the approved version deploy automatically.
Fix the Release and Complete CD
The red workflow has already proved that your quality gate protects the live site. The public release is still serving its last approved artifact.
Now you can correct the source that failed the check. GitHub Actions can then approve the commit. The Render Static Site can complete continuous deployment automatically.
In this step, get ready to:
- Correct the release marker and approved status text.
- Confirm the build and release test pass in GitHub Actions.
- Watch Render publish the approved release automatically.
Update the approved release
The release test reads the built HTML and searches for data-release="ready". This marker gives the release an explicit approval signal.
- Switch back to the quality-gated-site repository on GitHub.
- Select public/index.html from the repository file list.
- Select the pencil icon in the file toolbar to enter edit mode.
- Find the section that starts with <body>.
- Replace the complete body section by pasting this code:
<body>
<main class="card" data-release="ready">
<p class="eyebrow">Quality-gated deployment</p>
<h1>My Render release is live.</h1>
<p class="summary">Private source. Public site. Automated checks between every change and deployment.</p>
<div class="status" aria-label="Release status">
<span class="dot" aria-hidden="true"></span>
<strong>CI passed</strong>
<span>This release cleared the gate.</span>
</div>
</main>
</body>
What does this code change?
- The data-release="ready" attribute gives the automated test the marker it requires.
- The updated heading identifies this page as the approved Render release.
- The status panel provides visible evidence that the release cleared its automated check.
- Select Commit changes... in the upper-right corner.
- Enter Approve quality-gated release as the commit message.
- Select Commit changes... in the confirmation dialog.
Your corrected release is now committed. GitHub starts a new Validate release workflow run for this commit.
Unable to commit the HTML?
Confirm that the replacement begins with <body> and ends with </body>. Make sure the closing </html> line remains below the replacement.
Ask for help checking the edited body section.
✔️ Awesome, I've got everything!
Great. Your committed public/index.html now contains the approved release marker and status text.
ⓧ I'd like to double check the full code
Use this complete approved file as your comparison reference.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Quality-Gated Release</title>
<style>
:root {
color-scheme: dark;
font-family: Inter, ui-sans-serif, system-ui, sans-serif;
background: #07111f;
color: #e8f0ff;
}
* {
box-sizing: border-box;
}
body {
min-height: 100vh;
margin: 0;
display: grid;
place-items: center;
padding: 24px;
background:
radial-gradient(circle at top left, #173663 0, transparent 42%),
linear-gradient(145deg, #07111f, #0b1729 60%, #0d2036);
}
.card {
width: min(680px, 100%);
padding: clamp(28px, 6vw, 56px);
border: 1px solid #27466d;
border-radius: 28px;
background: rgba(9, 24, 43, 0.88);
box-shadow: 0 24px 80px rgba(0, 0, 0, 0.35);
}
.eyebrow {
margin: 0 0 12px;
color: #7dd3fc;
font-size: 0.82rem;
font-weight: 800;
letter-spacing: 0.14em;
text-transform: uppercase;
}
h1 {
margin: 0;
font-size: clamp(2.25rem, 8vw, 4.75rem);
line-height: 0.98;
letter-spacing: -0.055em;
}
.summary {
margin: 24px 0 32px;
color: #b6c8e2;
font-size: 1.1rem;
line-height: 1.7;
}
.status {
display: grid;
grid-template-columns: auto 1fr;
gap: 4px 12px;
align-items: center;
padding: 18px;
border: 1px solid #1f6f55;
border-radius: 18px;
background: rgba(16, 80, 61, 0.28);
}
.dot {
grid-row: 1 / 3;
width: 14px;
height: 14px;
border-radius: 50%;
background: #34d399;
box-shadow: 0 0 22px #34d399;
}
.status span:last-child {
color: #a7f3d0;
}
</style>
</head>
<body>
<main class="card" data-release="ready">
<p class="eyebrow">Quality-gated deployment</p>
<h1>My Render release is live.</h1>
<p class="summary">Private source. Public site. Automated checks between every change and deployment.</p>
<div class="status" aria-label="Release status">
<span class="dot" aria-hidden="true"></span>
<strong>CI passed</strong>
<span>This release cleared the gate.</span>
</div>
</main>
</body>
</html>
What should match?
This reference contains the exact approved version of public/index.html. The release marker and visible status text are the changes that close the quality gate.
Watch the checks turn green
The new commit triggers the same build and test that rejected the previous release. GitHub Actions now evaluates the corrected output.
Before you check the run, predict whether both workflow steps will pass.
- Select the Actions tab in the repository navigation.
- Select the latest Validate release workflow run.
- Wait for the workflow run to finish.
You will see Build site and Require the release marker complete successfully. The Validate release workflow is now green.
That is the key transition. The same check that blocked the bad release now approves the corrected one.
Is the workflow still red?
If Require the release marker still fails, confirm that data-release="ready" appears inside the opening <main> tag in the committed file.
Ask for help reading the failed workflow step.
Confirm continuous deployment
Render is already set to wait for passing checks. The green GitHub result now releases this commit for automatic deployment.
The deployment needs time to build, so a short wait here is normal. Render keeps the existing release available during that process.
- Switch back to the Render Dashboard from earlier.
- Select the quality-gated-site Static Site.
- Select the Deploys page.
- Wait until the newest deployment displays the Live badge.
The approved commit is now the live Render deployment. The failed commit never replaced the public site.
- Select the service Settings page.
- Confirm that Auto-Deploy displays After CI Checks Pass.
Before you open the site, predict which release status the new live artifact will show.
- Open the public Render URL from earlier.
You will see CI passed and This release cleared the gate. The approved page became public only after its GitHub check passed.
Your full quality-gated delivery loop now works. Next, you will test its recovery path by restoring a specific retained deployment.
Roll Back to a Specific Release
Your approved release is live after GitHub Actions passed the release check. Next, you will rehearse recovery on the Render Static Site.
Automated checks reduce release risk. A rollback uses a retained build artifact to restore a known version without changing repository source.
In this step, get ready to:
- Roll the live site back to the retained first release.
- Restore the approved release from its retained deployment.
- Restore quality-gated automatic deployments.
Roll back to the first release
The Deploys page keeps the service's deployment history. Selecting a prior successful deployment activates its retained artifact.
Changing the live production version can feel risky. Your approved repository source remains untouched throughout this drill.
- Return to the Render Dashboard tab from earlier.
- Select the Static Site connected to quality-gated-site.
- Select Deploys in the service navigation.
- Find the original successful deployment from before the approved release.
- Select Rollback for that deployment.
- Select Rollback to this deploy in the confirmation screen.
- Wait until the rollback deployment receives the Live badge.
What did the rollback change?
The Dashboard created a new deploy from the selected retained artifact. Your repository still contains the approved source.
Dashboard rollback also switches Auto-Deploy to Off. This safeguard protects the recovered version from an immediate replacement.
Before you refresh the public site, which release do you expect the retained artifact to show?
- Refresh the public onrender.com URL from earlier.
You'll see First deploy in the status panel. The supporting text reads No automated gate yet..
Still seeing the approved release?
- Confirm the rollback deployment has the Live badge before refreshing.
- Choose the original retained deployment if the selected artifact shows the approved release.
Help me identify why my Render deployment cannot be rolled back.
Restore the approved release
The first-release artifact proves that recovery works. The approved artifact returns the site to its intended release.
Render needs a brief moment to activate the retained artifact. The public page can show the first release until the new rollback deployment becomes Live.
- Switch back to the Deploys page from earlier.
- Find the earlier successful deployment that published the approved CI passed version.
- Select Rollback for the approved deployment.
- Select Rollback to this deploy in the confirmation screen.
- Wait until the restored deployment receives the Live badge.
Before you refresh the public site, which status do you expect the approved artifact to restore?
- Refresh the public onrender.com URL.
You'll see CI passed in the status panel. The supporting text reads This release cleared the gate..
Still seeing the first release?
- Confirm the approved rollback deployment has the Live badge.
- Refresh the public URL after the approved rollback becomes Live.
Help me restore the approved Render deployment.
Restore quality-gated auto-deploys
The approved site is restored. Continuous deployment still needs its quality-gated setting before future commits can flow through CI.
- Switch back to the Render service Dashboard.
- Select Settings in the service navigation.
- Find the Auto-Deploy setting.
- Select After CI Checks Pass.
Before you make the final check, which release should remain live after the gate is restored?
- Confirm Auto-Deploy shows After CI Checks Pass.
- Switch back to the public onrender.com URL.
- Refresh the page.
You'll see CI passed again. The supporting text remains This release cleared the gate..
You've now rehearsed a production recovery without changing repository history. Your approved release is live with quality-gated auto-deploys restored.
Secret mission
Add a Second Release Rule
Add a release-proof rule to your existing quality gate. You will run a two-commit experiment that proves the site deploys only after both release requirements pass.
Clean Up Your Resources
Clean Up Your Resources
Decide whether to keep your pipeline active, pause automated updates, or delete the resources entirely. Included GitHub Actions and Render Static Site usage should cover this project.
Cost warning
GitHub Free includes 2,000 standard-runner minutes per month for private repositories. Render Hobby includes 500 standard build pipeline minutes per month.
Render Hobby also includes 5 GB of bandwidth per month. Usage beyond included quotas can be billed when an account has a payment method.
Pause or Delete is the safer option when you have finished testing the pipeline.
Resources you used:
- The private quality-gated-site GitHub repository containing your source files.
- The Validate release GitHub Actions workflow with its run history.
- The public Render Static Site with its deploy history and retained rollback artifacts.
Keep everything running
No action is needed. Choose this option if you plan to keep testing the pipeline or sharing the public site.
- Leave Validate release enabled so new commits keep running both tests.
- Leave Auto-Deploy set to After CI Checks Pass so only passing commits deploy.
- Keep the quality-gated-site repository private while the public onrender.com site remains available.
Pause - I'll come back to this later
Stop automatic checks and deployments while keeping the current static site available. Your source files remain in GitHub.
- Set Auto-Deploy to Off on the Render Static Site's Settings page.
- Choose Disable workflow for Validate release on the quality-gated-site repository's Actions page.
- Refresh the public onrender.com URL to confirm the current two-rule release remains available.
- Choose Enable workflow for Validate release when you return.
- Set Auto-Deploy back to After CI Checks Pass before your next commit.
Delete - I don't want to use this again
Deleting these resources permanently removes the public site and private repository. This option fits when you no longer need either resource.
- Delete the Render Static Site connected to quality-gated-site by using its service deletion control in the Render Dashboard.
- Refresh the public onrender.com URL to confirm it no longer serves the release page.
- Open Settings for quality-gated-site on GitHub.
- Select General.
- Scroll to Danger Zone.
- Select Delete this repository.
- Complete GitHub's confirmation prompts to permanently delete quality-gated-site.
- Return to the repository URL to confirm GitHub no longer opens it.
Deleting quality-gated-site also removes the Validate release workflow with its run history.
Nice Work!
Nice Work!
You did it! Your private source now powers a public release-status page on a Render Static Site.
Two automated checks now guard each deployment. A failed release leaves the last approved version online.
You've learned how to:
- Built a private GitHub repository with a Node.js build that copies public/ into dist/. Published the build output as a public website.
- Created a GitHub Actions workflow that runs a CI/CD quality gate on every commit. Proved the gate works when a deliberate failure left the first release online.
- Recovered the live site with a targeted rollback to the first successful deployment. Restored the approved release. Reenabled After CI Checks Pass for future commits.
- Secret Mission: Authored a second release rule for id="release-proof". Watched GitHub Actions block the incomplete release. Added the footer. Published a release that proves both checks passed.
Ready to quiz yourself?