Skip to main content

Overview

SkillRise uses GitHub Actions for continuous integration and deployment. The pipeline consists of two workflows:

Build Workflow

Runs on pull requests to validate code quality

Deploy Workflow

Builds and pushes Docker images on main branch

Build Workflow

The build workflow runs on every pull request to ensure code quality before merging.

Configuration

.github/workflows/build.yml
name: CI pipeline to Lint, Format & Build Client and Lint & Format Server

on:
    pull_request:
        branches:
            - main
            - dev

jobs:
    client:
        name: Lint, Format & Build Client
        runs-on: ubuntu-latest
        defaults:
            run:
                working-directory: client

        steps:
            - uses: actions/checkout@v3

            - name: Use Node.js
              uses: actions/setup-node@v3
              with:
                  node-version: '20'

            - name: Install Dependencies
              run: npm ci

            - name: Run ESLint
              run: npm run lint

            - name: Check Prettier Formatting
              run: npm run format:check

            - name: Run Build
              run: npm run build
              env:
                  VITE_CLERK_PUBLISHABLE_KEY: ${{ secrets.VITE_CLERK_PUBLISHABLE_KEY }}
                  VITE_STRIPE_PUBLISHABLE_KEY: ${{ secrets.VITE_STRIPE_PUBLISHABLE_KEY }}
                  VITE_BACKEND_URL: ${{ secrets.VITE_BACKEND_URL }}

    server:
        name: Lint & Format Check Server
        runs-on: ubuntu-latest
        defaults:
            run:
                working-directory: server

        steps:
            - uses: actions/checkout@v3

            - name: Use Node.js
              uses: actions/setup-node@v3
              with:
                  node-version: '20'

            - name: Install Dependencies
              run: npm ci

            - name: Run ESLint
              run: npm run lint

            - name: Check Prettier Formatting
              run: npm run format:check

Workflow Stages

1

Client Validation

The client job validates the React frontend:
npm ci
Uses npm ci for clean, reproducible installs based on package-lock.json.
npm run lint
Checks for code quality issues, potential bugs, and style violations.Configuration: eslint.config.js
  • React hooks rules
  • React refresh plugin
  • Prettier integration
npm run format:check
Verifies consistent code formatting without modifying files.Configuration: .prettierrc
  • 2-space indentation
  • Single quotes
  • Semicolons
  • Trailing commas
npm run build
Compiles the React app with Vite to ensure no build errors.Build-time environment variables:
  • VITE_CLERK_PUBLISHABLE_KEY
  • VITE_STRIPE_PUBLISHABLE_KEY
  • VITE_BACKEND_URL
These are read from GitHub Secrets and embedded in the build.
2

Server Validation

The server job validates the Node.js backend:
npm ci
Installs all dependencies including devDependencies for linting.
npm run lint
Checks Express server code for issues.Configuration: eslint.config.js
  • Node.js globals
  • ES2022 syntax
  • Prettier integration
npm run format:check
Ensures consistent formatting across all server files.

Running Locally

You can run the same checks locally before pushing:
cd client
npm ci
npm run lint
npm run format:check
npm run build
Use npm run lint:fix and npm run format to automatically fix issues.

Deploy Workflow

The deploy workflow automatically builds and pushes Docker images when code is merged to the main branch.

Configuration

.github/workflows/deploy.yml
name: Build and Deploy to Docker Hub

on:
    push:
        branches:
            - main

jobs:
    build-and-push-server:
        name: Build & Push Server Image
        runs-on: ubuntu-latest

        steps:
            - name: Check Out Repo
              uses: actions/checkout@v4

            - name: Log in to Docker Hub
              uses: docker/login-action@v3
              with:
                  username: ${{ secrets.DOCKER_USERNAME }}
                  password: ${{ secrets.DOCKER_PASSWORD }}

            - name: Build and Push Server Image
              uses: docker/build-push-action@v5
              with:
                  context: ./server
                  file: ./server/Dockerfile
                  push: true
                  tags: pushkarverma/skillrise-server:latest

    build-and-push-client:
        name: Build & Push Client Image
        runs-on: ubuntu-latest

        steps:
            - name: Check Out Repo
              uses: actions/checkout@v4

            - name: Log in to Docker Hub
              uses: docker/login-action@v3
              with:
                  username: ${{ secrets.DOCKER_USERNAME }}
                  password: ${{ secrets.DOCKER_PASSWORD }}

            - name: Build and Push Client Image
              uses: docker/build-push-action@v5
              with:
                  context: ./client
                  file: ./client/Dockerfile
                  push: true
                  tags: pushkarverma/skillrise-client:latest
                  build-args: |
                      VITE_CLERK_PUBLISHABLE_KEY=${{ secrets.VITE_CLERK_PUBLISHABLE_KEY }}
                      VITE_STRIPE_PUBLISHABLE_KEY=${{ secrets.VITE_STRIPE_PUBLISHABLE_KEY }}
                      VITE_BACKEND_URL=${{ secrets.VITE_BACKEND_URL }}

Workflow Stages

1

Server Image Build

The build-and-push-server job creates the backend Docker image:
- name: Check Out Repo
  uses: actions/checkout@v4
Clones the repository with full git history.
- name: Log in to Docker Hub
  uses: docker/login-action@v3
  with:
      username: ${{ secrets.DOCKER_USERNAME }}
      password: ${{ secrets.DOCKER_PASSWORD }}
Authenticates with Docker Hub using repository secrets.
- name: Build and Push Server Image
  uses: docker/build-push-action@v5
  with:
      context: ./server
      file: ./server/Dockerfile
      push: true
      tags: pushkarverma/skillrise-server:latest
Process:
  1. Builds image from server/Dockerfile
  2. Tags as pushkarverma/skillrise-server:latest
  3. Pushes to Docker Hub
  4. Replaces previous latest tag
Image size: ~150MB
Build time: ~2-3 minutes
2

Client Image Build

The build-and-push-client job creates the frontend Docker image:
Same as server: checks out code and authenticates with Docker Hub.
- name: Build and Push Client Image
  uses: docker/build-push-action@v5
  with:
      context: ./client
      file: ./client/Dockerfile
      push: true
      tags: pushkarverma/skillrise-client:latest
      build-args: |
          VITE_CLERK_PUBLISHABLE_KEY=${{ secrets.VITE_CLERK_PUBLISHABLE_KEY }}
          VITE_STRIPE_PUBLISHABLE_KEY=${{ secrets.VITE_STRIPE_PUBLISHABLE_KEY }}
          VITE_BACKEND_URL=${{ secrets.VITE_BACKEND_URL }}
Build arguments:
  • VITE_CLERK_PUBLISHABLE_KEY - Embedded in build for authentication
  • VITE_STRIPE_PUBLISHABLE_KEY - Embedded in build for payments
  • VITE_BACKEND_URL - API endpoint URL
Multi-stage build:
  1. Stage 1: Compiles React app with Vite
  2. Stage 2: Copies build to Nginx image
Image size: ~25MB
Build time: ~3-4 minutes

Job Execution

Both jobs run in parallel for faster deployment. They are independent and don’t wait for each other.

Required Secrets

Configure these secrets in your GitHub repository settings:
DOCKER_USERNAME
Your Docker Hub username
DOCKER_PASSWORD
Docker Hub access token (recommended) or password
Use an access token instead of your password for better security. Generate one at Docker Hub Security Settings.
VITE_CLERK_PUBLISHABLE_KEY
Clerk publishable key for authentication
Example: pk_test_... or pk_live_...
VITE_STRIPE_PUBLISHABLE_KEY
Stripe publishable key for payments
Example: pk_test_... or pk_live_...
VITE_BACKEND_URL
Backend API URL
Example: https://api.yourdomain.com or http://localhost:3000
These are public keys safe to embed in the client bundle. Never add secret keys here.

Adding Secrets

1

Navigate to Repository Settings

Go to your GitHub repository → SettingsSecrets and variablesActions
2

Add New Secret

Click New repository secret
3

Enter Secret Details

  • Name: Use exact names from the list above (case-sensitive)
  • Value: Paste the secret value
  • Click Add secret
4

Verify

Secrets should appear in the list but values remain hidden

Triggering Deployments

Automatic Deployment

Deployment triggers automatically when:
git push origin main
Or when you merge a pull request to main:
gh pr merge 123 --merge

Manual Deployment

To trigger manually without code changes:
1

Go to Actions Tab

Navigate to Actions in your GitHub repository
2

Select Deploy Workflow

Click on Build and Deploy to Docker Hub
3

Run Workflow

Click Run workflow → Select main branch → Run workflow

Monitoring Workflow Runs

View Run Status

  1. Go to the Actions tab in your repository
  2. Click on a workflow run to see details
  3. Expand jobs to view step-by-step logs

Status Indicators

Success

All jobs completed successfully

In Progress

Workflow is currently running

Failed

One or more jobs failed

Common Failure Reasons

ESLint errors:
Error: Process completed with exit code 1.
Fix: Run npm run lint:fix locally and commit fixesPrettier errors:
Code style issues found in the above file(s).
Fix: Run npm run format locally and commit changesBuild errors:
Error: Process completed with exit code 1.
Fix: Run npm run build locally to identify the issue
Docker login failed:
Error: Unable to locate credentials
Fix: Verify DOCKER_USERNAME and DOCKER_PASSWORD secretsBuild context error:
Error: failed to solve: failed to read dockerfile
Fix: Ensure Dockerfile exists in the correct directoryPush failed:
Error: denied: requested access to the resource is denied
Fix: Check Docker Hub repository permissions and credentialsBuild args missing:
Error: missing value for build arg
Fix: Verify all client build secrets are configured

Workflow Optimization

Caching Dependencies

Add caching to speed up builds:
- name: Use Node.js
  uses: actions/setup-node@v3
  with:
      node-version: '20'
      cache: 'npm'
      cache-dependency-path: '**/package-lock.json'

Docker Layer Caching

Enable BuildKit cache:
- name: Build and Push Server Image
  uses: docker/build-push-action@v5
  with:
      context: ./server
      file: ./server/Dockerfile
      push: true
      tags: pushkarverma/skillrise-server:latest
      cache-from: type=registry,ref=pushkarverma/skillrise-server:latest
      cache-to: type=inline

Matrix Builds

Test multiple Node versions:
jobs:
    test:
        runs-on: ubuntu-latest
        strategy:
            matrix:
                node-version: [18, 20, 22]
        steps:
            - uses: actions/checkout@v3
            - name: Use Node.js ${{ matrix.node-version }}
              uses: actions/setup-node@v3
              with:
                  node-version: ${{ matrix.node-version }}

Advanced Workflows

Environment-Specific Deployments

Deploy to staging and production:
name: Deploy to Environments

on:
    push:
        branches:
            - main
            - staging

jobs:
    deploy:
        runs-on: ubuntu-latest
        steps:
            - uses: actions/checkout@v4
            
            - name: Set environment
              run: |
                  if [[ "${{ github.ref }}" == "refs/heads/main" ]]; then
                      echo "ENV=production" >> $GITHUB_ENV
                      echo "TAG=latest" >> $GITHUB_ENV
                  else
                      echo "ENV=staging" >> $GITHUB_ENV
                      echo "TAG=staging" >> $GITHUB_ENV
                  fi
            
            - name: Build and Push
              uses: docker/build-push-action@v5
              with:
                  context: ./server
                  push: true
                  tags: pushkarverma/skillrise-server:${{ env.TAG }}

Slack Notifications

Send deployment status to Slack:
- name: Notify Slack
  if: always()
  uses: 8398a7/action-slack@v3
  with:
      status: ${{ job.status }}
      text: 'Deployment ${{ job.status }}'
      webhook_url: ${{ secrets.SLACK_WEBHOOK }}

Automated Rollbacks

Revert to previous image on failure:
- name: Health Check
  run: |
      curl --fail http://localhost:3000/ || exit 1

- name: Rollback on Failure
  if: failure()
  run: |
      docker pull pushkarverma/skillrise-server:previous
      docker tag pushkarverma/skillrise-server:previous pushkarverma/skillrise-server:latest
      docker push pushkarverma/skillrise-server:latest

Best Practices

Version Tagging

Use semantic versioning alongside latest:
tags: |
  pushkarverma/skillrise-server:latest
  pushkarverma/skillrise-server:v1.2.3
  pushkarverma/skillrise-server:v1.2
  pushkarverma/skillrise-server:v1

Branch Protection

Require CI checks to pass before merging:
  • Go to SettingsBranches
  • Add rule for main
  • Enable “Require status checks”
  • Select CI jobs

Secrets Rotation

Regularly rotate credentials:
  • Docker Hub tokens every 90 days
  • API keys when team members leave
  • Use different keys for staging/production

Monitoring

Track deployment metrics:
  • Build duration trends
  • Image size changes
  • Deployment frequency
  • Failure rates

Next Steps