Ever wished your website would magically update every time you push code to GitHub? Well, that's exactly what CI/CD does!
In this tutorial, I'll show you how to deploy a beautiful portfolio website with automatic deployments using GitHub Actions and Netlify. No manual uploads, no clicking "Deploy" buttons β just pure automation magic! β¨
What you'll learn:
- β Set up a modern portfolio website
- β Configure GitHub Actions for CI/CD
- β Auto-deploy to Netlify on every push
- β Add testing to your pipeline
Let's dive in! πββοΈ
π Project Structure
First, let's see what we're building:
my-portfolio/
βββ .github/
β βββ workflows/
β βββ deploy.yml # GitHub Actions CI/CD
βββ index.html # Main website
βββ test.html # Test page (optional)
βββ test.js # Test scripts
βββ netlify.toml # Netlify configuration
βββ package.json # Project metadata
Each file has a specific purpose. Let me explain! π
π¨ Step 1: Create Your Website (index.html)
This is your main webpage. Copy this beautiful, interactive portfolio:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Modern Portfolio</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
color: #fff;
}
.container {
max-width: 1200px;
margin: 0 auto;
padding: 2rem;
}
header {
text-align: center;
padding: 3rem 0;
animation: fadeIn 1s ease-in;
}
h1 {
font-size: 3rem;
margin-bottom: 1rem;
text-shadow: 2px 2px 4px rgba(0,0,0,0.3);
}
.tagline {
font-size: 1.2rem;
opacity: 0.9;
}
.card-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 2rem;
margin: 3rem 0;
}
.card {
background: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(10px);
border-radius: 15px;
padding: 2rem;
transition: transform 0.3s ease, box-shadow 0.3s ease;
border: 1px solid rgba(255, 255, 255, 0.2);
}
.card:hover {
transform: translateY(-10px);
box-shadow: 0 10px 30px rgba(0,0,0,0.3);
}
.card h3 {
font-size: 1.5rem;
margin-bottom: 1rem;
color: #ffd700;
}
.card p {
line-height: 1.6;
opacity: 0.9;
}
.counter-section {
background: rgba(255, 255, 255, 0.15);
backdrop-filter: blur(10px);
border-radius: 15px;
padding: 3rem;
text-align: center;
margin: 2rem 0;
}
.counter {
font-size: 4rem;
font-weight: bold;
color: #ffd700;
margin: 1rem 0;
}
.btn {
background: linear-gradient(135deg, #ffd700 0%, #ffed4e 100%);
color: #333;
border: none;
padding: 1rem 2rem;
font-size: 1.1rem;
border-radius: 50px;
cursor: pointer;
transition: all 0.3s ease;
font-weight: bold;
box-shadow: 0 4px 15px rgba(0,0,0,0.2);
}
.btn:hover {
transform: scale(1.05);
box-shadow: 0 6px 20px rgba(0,0,0,0.3);
}
footer {
text-align: center;
padding: 2rem;
margin-top: 3rem;
border-top: 1px solid rgba(255, 255, 255, 0.2);
}
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(-20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@media (max-width: 768px) {
h1 {
font-size: 2rem;
}
.card-grid {
grid-template-columns: 1fr;
}
}
</style>
</head>
<body>
<div class="container">
<header>
<h1>π Modern Portfolio</h1>
<p class="tagline">Built with HTML, CSS & JavaScript</p>
</header>
<div class="card-grid">
<div class="card">
<h3>π¨ Beautiful Design</h3>
<p>Clean, modern interface with smooth animations and glassmorphism effects.</p>
</div>
<div class="card">
<h3>β‘ Lightning Fast</h3>
<p>Optimized performance with vanilla JavaScript. No frameworks needed!</p>
</div>
<div class="card">
<h3>π± Fully Responsive</h3>
<p>Looks great on all devices - from mobile phones to large desktops.</p>
</div>
</div>
<div class="counter-section">
<h2>Interactive Counter Demo</h2>
<div class="counter" id="counter">0</div>
<button class="btn" onclick="incrementCounter()">Click Me!</button>
<button class="btn" onclick="resetCounter()" style="margin-left: 1rem; background: linear-gradient(135deg, #ff6b6b 0%, #ee5a6f 100%);">Reset</button>
</div>
<footer>
<p>© 2025 Modern Portfolio | Deployed with CI/CD β€οΈ</p>
</footer>
</div>
<script>
let count = 0;
function incrementCounter() {
count++;
updateCounter();
}
function resetCounter() {
count = 0;
updateCounter();
}
function updateCounter() {
const counterEl = document.getElementById('counter');
counterEl.style.transform = 'scale(1.2)';
counterEl.textContent = count;
setTimeout(() => {
counterEl.style.transform = 'scale(1)';
}, 200);
}
</script>
</body>
</html>
What makes this special?
- π¨ Glassmorphism design (trendy in 2025!)
- β‘ Smooth animations
- π± Mobile-responsive
- π― Interactive counter
π§ͺ Step 2: Add Testing (test.js)
Let's add some basic tests to ensure our site works:
// Simple test framework
class TestRunner {
constructor() {
this.tests = [];
this.passed = 0;
this.failed = 0;
}
test(name, fn) {
this.tests.push({ name, fn });
}
async run() {
console.log('π§ͺ Running tests...\n');
for (const test of this.tests) {
try {
await test.fn();
this.passed++;
console.log(`β ${test.name}`);
} catch (error) {
this.failed++;
console.error(`β ${test.name}`);
console.error(` Error: ${error.message}`);
}
}
console.log(`\nπ Results: ${this.passed} passed, ${this.failed} failed`);
return this.failed === 0;
}
}
function assert(condition, message) {
if (!condition) {
throw new Error(message || 'Assertion failed');
}
}
// Create test runner
const runner = new TestRunner();
// DOM Tests
runner.test('Counter element exists', () => {
const counter = document.getElementById('counter');
assert(counter !== null, 'Counter element not found');
});
runner.test('Counter starts at 0', () => {
const counter = document.getElementById('counter');
assert(counter.textContent === '0', 'Counter does not start at 0');
});
runner.test('All cards are present', () => {
const cards = document.querySelectorAll('.card');
assert(cards.length === 3, 'Expected 3 cards');
});
// Export for use
if (typeof module !== 'undefined' && module.exports) {
module.exports = { runner, assert };
}
βοΈ Step 3: Configure Netlify (netlify.toml)
This file tells Netlify how to deploy your site:
[build]
publish = "."
command = "echo 'No build command needed for static site'"
[[redirects]]
from = "/*"
to = "/index.html"
status = 200
[build.environment]
NODE_VERSION = "18"
Why this matters:
-
publish = "."β Deploy current directory -
redirectsβ Handle SPA routing (if needed later) -
NODE_VERSIONβ Ensures consistent builds
π¦ Step 4: Add Package Metadata (package.json)
{
"name": "modern-portfolio",
"version": "1.0.0",
"description": "Modern portfolio with CI/CD deployment",
"scripts": {
"test": "echo 'Tests run in browser'",
"build": "echo 'Static site - no build needed'"
},
"keywords": ["portfolio", "cicd", "netlify"],
"author": "Your Name",
"license": "MIT"
}
π€ Step 5: Create GitHub Actions Workflow (.github/workflows/deploy.yml)
This is the magic that makes CI/CD work! πͺ
name: Deploy to Netlify
on:
push:
branches: [ main, master ]
pull_request:
branches: [ main, master ]
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '18'
- name: Validate HTML
run: |
echo "Checking if index.html exists..."
if [ ! -f "index.html" ]; then
echo "Error: index.html not found"
exit 1
fi
echo "β index.html found"
- name: Run basic tests
run: |
echo "Running validation tests..."
grep -q "<title>" index.html && echo "β Title tag found" || exit 1
grep -q "incrementCounter" index.html && echo "β JavaScript found" || exit 1
echo "β All tests passed"
deploy:
needs: test
runs-on: ubuntu-latest
if: github.event_name == 'push'
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Deploy to Netlify
uses: nwtgck/actions-netlify@v2.0
with:
publish-dir: '.'
production-branch: main
github-token: ${{ secrets.GITHUB_TOKEN }}
deploy-message: "Deploy from GitHub Actions"
enable-pull-request-comment: true
enable-commit-comment: true
env:
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SITE_ID }}
timeout-minutes: 1
What happens here?
- β Tests run automatically
- β If tests pass β Deploy to Netlify
- β GitHub comments deployment status
- β
Works on every push to
main
π Step 6: Get Netlify Credentials
Get Netlify Auth Token:
- Go to https://app.netlify.com/user/applications/personal
- Click "New access token"
- Give it a name:
GitHub Actions - Click "Generate token"
- Copy the token (you won't see it again!)
Get Netlify Site ID:
- Deploy your site manually first on Netlify
- Go to Site Settings β General β Site Information
- Copy the API ID (looks like:
abc12345-6789-def0-1234-56789abcdef0)
π― Step 7: Add Secrets to GitHub
- Go to your GitHub repository
- Click Settings β Secrets and variables β Actions
- Click "New repository secret"
Add these two secrets:
| Secret Name | Value |
|---|---|
NETLIFY_AUTH_TOKEN |
Your token from Netlify |
NETLIFY_SITE_ID |
Your site ID from Netlify |
β οΈ Important: Never commit these secrets to your code!
π Step 8: Deploy!
Now push your code to GitHub:
# Initialize git (if not done)
git init
git add .
git commit -m "Initial commit - CI/CD setup"
git branch -M main
# Add your GitHub repository
git remote add origin git@github.com:yourusername/your-repo.git
# Push and trigger deployment!
git push -u origin main
What happens next:
- π GitHub Actions workflow starts
- π§ͺ Tests run automatically
- β If tests pass β Deploys to Netlify
- π Your site is live!
Check the Actions tab in your GitHub repo to watch it deploy in real-time! π¬
π You Did It!
Your website is now live with automatic CI/CD!
What you've accomplished:
- β Built a modern portfolio website
- β Set up automated testing
- β Configured CI/CD with GitHub Actions
- β Deployed to Netlify automatically
- β Every push = instant deployment! π
Your site will be live at: https://your-site-name.netlify.app
π₯ Pro Tips
1. Custom Domain:
- Go to Netlify β Domain settings
- Add your custom domain (e.g.,
yourname.com)
2. Environment Variables:
- Add them in Netlify dashboard
- Access via
process.env.YOUR_VAR
3. Deploy Previews:
- Every PR gets its own preview URL
- Test before merging to production
4. Build Logs:
- Check GitHub Actions tab for deployment logs
- Debug any issues quickly
π€ Common Issues & Solutions
β Deployment failing?
- Check your GitHub Secrets are correct
- Verify the
NETLIFY_SITE_IDformat
β Tests not running?
- Make sure
.github/workflows/deploy.ymlis in the right folder - Check the workflow syntax
β Site not updating?
- Clear Netlify cache
- Force a new deployment
π What's Next?
Now that you have CI/CD set up, you can:
- Add more features to your portfolio
- Set up monitoring with Netlify Analytics
- Add a CMS like Netlify CMS
- Deploy a React app (I can write Part 2 if you want!)
- Add form handling with Netlify Forms
π Resources
- GitHub Actions Docs
- Netlify Docs
- My GitHub Repo β Add your link!
π¬ Let's Connect!
If you found this helpful, drop a β€οΈ and follow me for more tutorials!
Questions? Drop a comment below! π
Happy deploying! π
Tags: #webdev #cicd #netlify #github #beginners #tutorial #devops #javascript #html #css
Top comments (0)