Build the mental model
A common pattern connects a git repository directly to a hosting platform: pushing code, or merging a pull request, triggers an automatic deployment with no manual upload step.
Commit & Push
A developer pushes code to a branch instead of building locally and copying files by hand.
Lint & Test (CI)
The platform automatically runs linting and the test suite on every push, catching problems before they reach users.
Build & Deploy (CD)
Once checks pass, the project is built and the result is shipped to production automatically.
Each step gates the next — a failed step stops the deployment instead of letting broken code reach production.
Preview deployments are a related pattern: opening a pull request automatically produces a temporary, shareable URL where reviewers click through the actual running change before it merges.
This lesson stays at the orientation level — pipeline YAML, caching, matrix builds, secrets, and approval gates are covered in depth in the CI/CD with GitHub Actions tutorial, and the git workflow itself in Git & GitHub.
GIT PUSH TO CI TO DEPLOY TO PRODUCTION
--------------------------------------
git push --> [lint] --> [test] --> [build] --> deploy --> prod
PULL REQUEST BRANCH:
git push (PR) --> CI checks --> preview URL --> merge --> prodConnect it to a real scenario
Setting up git-based deployment typically means connecting a hosting platform to a repository once, choosing which branch maps to production (usually main), and letting default build settings take over.
After that, the everyday workflow becomes simply pushing code — deployment happens automatically in the background, without a separate manual step.
Treat a failing check as something to fix before merging, not something to route around — the entire point of gating deployment behind checks is catching a problem before production.
Opening a pull request instead of pushing to main gives you a preview URL to sanity-check yourself, and gives teammates the same chance before it reaches real users.
Try the working example
function runPipeline(steps) {
for (const step of steps) {
console.log(`Running: ${step.name}...`);
if (!step.passes) {
console.log(`Failed at: ${step.name}. Deployment stopped.`);
return false;
}
}
console.log("All steps passed. Deploying to production.");
return true;
}
const goodPipeline = [
{ name: "lint", passes: true },
{ name: "test", passes: true },
{ name: "build", passes: true },
];
const badPipeline = [
{ name: "lint", passes: true },
{ name: "test", passes: false },
{ name: "build", passes: true },
];
console.log("--- Passing run ---");
runPipeline(goodPipeline);
console.log("--- Failing run ---");
runPipeline(badPipeline);--- Passing run ---
Running: lint...
Running: test...
Running: build...
All steps passed. Deploying to production.
--- Failing run ---
Running: lint...
Running: test...
Failed at: test. Deployment stopped.5-minute try-it
Add a fourth step called 'deploy-preview' that always passes, placed between build and the final production deploy, and confirm both the passing and failing runs still behave correctly.
One important caution
Pushing straight to the main branch instead of opening a pull request, skipping the review and preview step entirely.
Treating a red/failing CI check as something to bypass or ignore rather than a signal that stops an unsafe deployment.
Continuous integration — MDN Web Docs Glossary — Cloud & Deployment