DEV Community

Aman Hussain Shaikh
Aman Hussain Shaikh

Posted on

πŸš€ Deploy Your First CI/CD Website in 10 Minutes Using GitHub Actions + Netlify (Complete Beginner's Guide)

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
Enter fullscreen mode Exit fullscreen mode

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>&copy; 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>
Enter fullscreen mode Exit fullscreen mode

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 };
}
Enter fullscreen mode Exit fullscreen mode

βš™οΈ 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"
Enter fullscreen mode Exit fullscreen mode

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"
}
Enter fullscreen mode Exit fullscreen mode

πŸ€– 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
Enter fullscreen mode Exit fullscreen mode

What happens here?

  1. βœ… Tests run automatically
  2. βœ… If tests pass β†’ Deploy to Netlify
  3. βœ… GitHub comments deployment status
  4. βœ… Works on every push to main

πŸ” Step 6: Get Netlify Credentials

Get Netlify Auth Token:

  1. Go to https://app.netlify.com/user/applications/personal
  2. Click "New access token"
  3. Give it a name: GitHub Actions
  4. Click "Generate token"
  5. Copy the token (you won't see it again!)

Get Netlify Site ID:

  1. Deploy your site manually first on Netlify
  2. Go to Site Settings β†’ General β†’ Site Information
  3. Copy the API ID (looks like: abc12345-6789-def0-1234-56789abcdef0)

🎯 Step 7: Add Secrets to GitHub

  1. Go to your GitHub repository
  2. Click Settings β†’ Secrets and variables β†’ Actions
  3. 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
Enter fullscreen mode Exit fullscreen mode

What happens next:

  1. πŸ”„ GitHub Actions workflow starts
  2. πŸ§ͺ Tests run automatically
  3. βœ… If tests pass β†’ Deploys to Netlify
  4. πŸŽ‰ 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_ID format

❌ Tests not running?

  • Make sure .github/workflows/deploy.yml is 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:

  1. Add more features to your portfolio
  2. Set up monitoring with Netlify Analytics
  3. Add a CMS like Netlify CMS
  4. Deploy a React app (I can write Part 2 if you want!)
  5. Add form handling with Netlify Forms

πŸ“š Resources


πŸ’¬ 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)