Answer:
-
Add caching:
- uses: actions/setup-node@v4 with: cache: 'npm'
-
Use matrix for parallel jobs:
strategy: matrix: test-suite: [unit, integration, e2e]
-
Only run on relevant changes:
on: push: paths: - 'src/**' - 'package.json'
-
Use faster runners (if budget allows):
runs-on: ubuntu-latest-4-cores
-
Skip unnecessary steps:
- name: Build if: github.event_name != 'pull_request' run: npm run build
Answer:
name: Full Stack CI
on: [push, pull_request]
jobs:
frontend:
runs-on: ubuntu-latest
defaults:
run:
working-directory: ./frontend
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '22'
cache: 'npm'
cache-dependency-path: frontend/package-lock.json
- run: npm ci
- run: npm test
- run: npm run build
backend:
runs-on: ubuntu-latest
defaults:
run:
working-directory: ./backend
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '22'
cache: 'npm'
cache-dependency-path: backend/package-lock.json
- run: npm ci
- run: npm testTwo separate jobs for frontend and backend, running in parallel.
Answer:
-
Check the logs:
- Go to Actions tab → Failed workflow → Click on failed step
- Read error messages carefully
-
Run locally:
npm ci npm test npm run build -
Enable debug logging:
- Settings → Secrets → Add
ACTIONS_STEP_DEBUG=true - Re-run workflow for verbose logs
- Settings → Secrets → Add
-
Add debugging steps:
- name: Debug info run: | node --version npm --version ls -la cat package.json
-
Use tmate for SSH access:
- name: Setup tmate session uses: mxschmitt/action-tmate@v3
-
Check common issues:
- Node version mismatch
- Missing environment variables
- Cache corruption (clear cache)
- Secrets not set correctly
Answer:
Setting secrets:
- Repository Settings → Secrets and variables → Actions
- New repository secret
- Name:
API_KEY, Value:secret-value
Using secrets:
steps:
- name: Deploy
env:
API_KEY: ${{ secrets.API_KEY }}
DB_PASSWORD: ${{ secrets.DB_PASSWORD }}
run: ./deploy.shBest practices:
- ✅ Never echo secrets:
echo ${{ secrets.API_KEY }}(visible in logs) - ✅ Use environment variables
- ✅ Mask secrets automatically in logs
- ✅ Use environment-specific secrets for dev/staging/prod
- ✅ Rotate secrets regularly
- ✅ Use minimum required permissions
Secret scopes:
- Repository secrets - Available to all workflows
- Environment secrets - Only in specific environments
- Organization secrets - Shared across repos
- GitHub Actions Docs: https://docs.github.com/en/actions
- Workflow Syntax: https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions
- Actions Marketplace: https://github.com/marketplace?type=actions
- Caching Dependencies: https://docs.github.com/en/actions/using-workflows/caching-dependencies-to-speed-up-workflows
Answer:
needs creates job dependencies in GitHub Actions.
jobs:
build:
# ... build steps
deploy:
needs: build # Won't run until build completesHow it works:
- Jobs run in parallel by default
needsenforces sequential execution- Deploy waits for build to finish
- If build fails, deploy is skipped
Benefits:
- Prevents deploying broken code
- Logical workflow order
- Resource optimization (no wasted deploy if build fails)
Multiple dependencies:
deploy:
needs: [build, test, lint] # Waits for all threeAnswer:
Separation benefits:
-
Different concerns:
- Build: Create production artifacts
- Deploy: Publish artifacts to hosting
-
Reusability:
build: # ... one build deploy-staging: needs: build # Deploy same artifact to staging deploy-production: needs: build # Deploy same artifact to production
-
Failure isolation:
- Build fails → Clear feedback, no deployment attempted
- Deploy fails → Can retry without rebuilding
-
Security:
- Build job: Limited permissions
- Deploy job: Elevated permissions (pages:write)
- Principle of least privilege
-
Debugging:
- Can download build artifact to inspect
- Can redeploy without rebuilding
Single job approach (not recommended):
jobs:
build-and-deploy:
steps:
- run: npm run build
- run: deploy.sh
# ❌ Harder to reuse, debug, and manage permissionsAnswer:
| Feature | upload-artifact@v4 |
upload-pages-artifact@v3 |
|---|---|---|
| Purpose | General file storage | GitHub Pages deployment |
| Validation | None | Validates web content |
| File types | Any | Web files (HTML, CSS, JS) |
| Integration | Generic | GitHub Pages specific |
| Download | Manual or via actions | Automatic in deploy job |
upload-artifact@v4 (generic):
- uses: actions/upload-artifact@v4
with:
name: test-results
path: ./coverage
retention-days: 7Use for: Coverage reports, logs, test results, any files.
upload-pages-artifact@v3 (Pages-specific):
- uses: actions/upload-pages-artifact@v3
with:
path: ./buildUse for: Deploying to GitHub Pages only.
Why use Pages-specific?
- Validates content is suitable for web hosting
- Optimizes for Pages deployment
- Integrates with
deploy-pagesaction - Follows Pages best practices
Answer:
deploy:
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}What are environments? Named deployment targets that provide:
- Deployment tracking - History of all deployments
- Protection rules - Required approvals, wait timers
- Secrets - Environment-specific secrets
- URLs - Live deployment URLs
Built-in github-pages environment:
- Automatically created for Pages deployments
- Tracks deployment history
- Shows current deployment status
- Provides rollback capability
Custom environments:
environment:
name: production
url: https://myapp.comProtection rules (Enterprise):
environment:
name: production
# Settings in GitHub UI:
# - Required reviewers: @senior-devs
# - Wait timer: 5 minutes
# - Deployment branches: main onlyBenefits:
- See deployment history in UI
- Track which code is deployed where
- Prevent accidental production deployments
- Require manual approval for sensitive environments
Answer:
concurrency:
group: "pages"
cancel-in-progress: falsePurpose: Prevent multiple workflows from running simultaneously.
How it works:
group: Identifies related workflows- Workflows in the same group can't run concurrently
- New workflows wait for current one to finish
cancel-in-progress options:
false (queue new runs):
concurrency:
group: "pages"
cancel-in-progress: false- Current deployment: Completes
- New deployment: Waits in queue
- Use for: Production deployments (avoid partial deploys)
true (cancel old runs):
concurrency:
group: "pages"
cancel-in-progress: true- Current deployment: Cancelled
- New deployment: Starts immediately
- Use for: Preview environments (latest code matters most)
Real-world examples:
Production deployment:
concurrency:
group: production-deploy
cancel-in-progress: false # Don't interrupt deploymentsPreview environments:
concurrency:
group: preview-${{ github.ref }}
cancel-in-progress: true # Cancel old, deploy latestDatabase migrations:
concurrency:
group: db-migration
cancel-in-progress: false # Never interrupt migrations!Answer:
- name: Build
run: npm run build
env:
CI: falseWhat is CI environment variable?
- When
CI=true, Create React App treats warnings as errors - Build fails if there are any ESLint warnings
- Default in CI environments
CI=true behavior:
Warning: 'useState' is defined but never used
❌ Build failed! (treats warning as error)CI=false behavior:
Warning: 'useState' is defined but never used
✅ Build succeeded (warning is just a warning)When to use CI=false:
- Production deployments (warnings shouldn't block deployment)
- You have some warnings but code still works
- Warnings will be fixed later
When to use CI=true:
- Pull request checks (enforce strict quality)
- Staging deployments (catch issues early)
- Zero-warning policy in your team
Best practice:
# Strict in PRs
- name: Build (PR)
if: github.event_name == 'pull_request'
run: npm run build
env:
CI: true # Fail on warnings
# Lenient in production
- name: Build (Production)
if: github.event_name == 'push'
run: npm run build
env:
CI: false # Allow warningsAnswer:
Approach 1: Separate workflows
.github/workflows/deploy-staging.yml:
name: Deploy Staging
on:
push:
branches: [ develop ]
jobs:
deploy:
environment:
name: staging
url: https://staging.myapp.com
steps:
- uses: actions/checkout@v4
- run: npm run build
- run: deploy-to-staging.sh.github/workflows/deploy-production.yml:
name: Deploy Production
on:
push:
branches: [ main ]
jobs:
deploy:
environment:
name: production
url: https://myapp.com
steps:
- uses: actions/checkout@v4
- run: npm run build
- run: deploy-to-production.shApproach 2: Single workflow with environments
name: Deploy
on:
push:
branches: [ main, develop ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- run: npm run build
- uses: actions/upload-artifact@v4
with:
name: build
path: ./build
deploy-staging:
if: github.ref == 'refs/heads/develop'
needs: build
environment:
name: staging
url: https://staging.myapp.com
steps:
- uses: actions/download-artifact@v4
- run: deploy-to-staging.sh
deploy-production:
if: github.ref == 'refs/heads/main'
needs: build
environment:
name: production
url: https://myapp.com
steps:
- uses: actions/download-artifact@v4
- run: deploy-to-production.shApproach 3: Reusable workflow
.github/workflows/deploy-reusable.yml:
name: Deploy (Reusable)
on:
workflow_call:
inputs:
environment:
required: true
type: string
url:
required: true
type: string
jobs:
deploy:
environment:
name: ${{ inputs.environment }}
url: ${{ inputs.url }}
steps:
- run: deploy.sh ${{ inputs.environment }}.github/workflows/deploy-staging.yml:
name: Deploy Staging
on:
push:
branches: [ develop ]
jobs:
deploy:
uses: ./.github/workflows/deploy-reusable.yml
with:
environment: staging
url: https://staging.myapp.comAnswer:
Option 1: Via GitHub UI
- Go to repository → Environments → github-pages
- Click on previous successful deployment
- Click "Re-run jobs"
Option 2: Git revert
# Find the commit that broke deployment
git log
# Revert it
git revert <bad-commit-sha>
git push origin main
# Triggers new deployment with previous working codeOption 3: Manual workflow dispatch with specific commit
on:
workflow_dispatch:
inputs:
git-ref:
description: 'Git ref to deploy'
required: true
default: 'main'
jobs:
deploy:
steps:
- uses: actions/checkout@v4
with:
ref: ${{ inputs.git-ref }}
# ... rest of deploymentThen manually trigger with a previous working commit SHA.
Option 4: Tag-based deployment
# Tag last working version
git tag v1.2.3-working
git push origin v1.2.3-working
# Deploy specific tagon:
push:
tags:
- 'v*'Best practice for rollback:
- Have automated tests to catch issues before deploy
- Use environment protection rules for production
- Monitor deployments (analytics, error tracking)
- Keep deployment artifacts for easy rollback
- Practice rollback procedures regularly