본문으로 건너뛰기 GitHub Actions Complete Guide | CI/CD· Workflows

GitHub Actions Complete Guide | CI/CD· Workflows

GitHub Actions Complete Guide | CI/CD· Workflows

이 글의 핵심

GitHub Actions brings CI/CD directly into your GitHub repository — no external servers, no complex setup. This guide walks you through workflows, testing, deployment, caching, and Secrets with real examples.

What is GitHub Actions?

GitHub Actions is GitHub’s built-in CI/CD and automation platform. You describe what should happen in a YAML file, and GitHub runs it automatically when events occur — on push, pull request, schedule, or manual trigger.

Developer pushes code
  → GitHub detects the push
  → Workflow triggers automatically
  → Jobs run in parallel on GitHub's cloud
  → Tests pass → auto-deploy to production

Key advantages over alternatives:

  • Zero infrastructure — GitHub manages the runners
  • Free for public repos — unlimited minutes
  • Marketplace — thousands of ready-made Actions
  • Native GitHub integration — status checks, PR comments, deployments

Core Concepts

TermWhat it is
WorkflowA YAML file in .github/workflows/ that defines automation
EventWhat triggers the workflow (push, pull_request, schedule, etc.)
JobA set of steps that run on the same runner
StepA single command or Action within a job
ActionA reusable unit of work (from Marketplace or your own repo)
RunnerThe VM that executes jobs (ubuntu-latest, macos-latest, windows-latest)

Your First Workflow

Create .github/workflows/hello.yml:

name: Hello World

on:
  push:
    branches: [main]

jobs:
  greet:
    runs-on: ubuntu-latest
    steps:
      - name: Say hello
        run: echo "Hello, GitHub Actions!"

      - name: Show environment
        run: |
          echo "Branch: ${{ github.ref_name }}"
          echo "Commit: ${{ github.sha }}"
          echo "Actor: ${{ github.actor }}"

Push this file and watch it run under the Actions tab in your repository.


CI Workflow — Test on Every Push

This workflow runs tests on Node.js 18 and 20 in parallel using a matrix strategy:

# .github/workflows/ci.yml
name: CI

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  test:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        node-version: [18, 20]

    steps:
      - uses: actions/checkout@v4

      - name: Setup Node.js ${{ matrix.node-version }}
        uses: actions/setup-node@v4
        with:
          node-version: ${{ matrix.node-version }}
          cache: 'npm'

      - name: Install dependencies
        run: npm ci

      - name: Run linter
        run: npm run lint

      - name: Run tests
        run: npm test

      - name: Build
        run: npm run build

The matrix creates two parallel jobs — one for Node 18, one for Node 20. Both must pass for the workflow to succeed.


Deployment Workflows

Deploy to Vercel

# .github/workflows/deploy-vercel.yml
name: Deploy to Vercel

on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Install Vercel CLI
        run: npm install -g vercel

      - name: Deploy to Vercel
        run: vercel --prod --token=${{ secrets.VERCEL_TOKEN }}
        env:
          VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }}
          VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }}

Build and Push Docker Image

# .github/workflows/docker.yml
name: Build and Push Docker Image

on:
  push:
    branches: [main]
    tags: ['v*']

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

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

      - name: Extract metadata
        id: meta
        uses: docker/metadata-action@v5
        with:
          images: myuser/myapp
          tags: |
            type=ref,event=branch
            type=semver,pattern={{version}}

      - name: Build and push
        uses: docker/build-push-action@v5
        with:
          context: .
          push: true
          tags: ${{ steps.meta.outputs.tags }}
          cache-from: type=gha
          cache-to: type=gha,mode=max

Secrets Management

Store sensitive values in GitHub → Settings → Secrets and variables → Actions. Never hardcode them in YAML.

steps:
  - name: Deploy
    run: ./deploy.sh
    env:
      API_KEY: ${{ secrets.API_KEY }}
      DATABASE_URL: ${{ secrets.DATABASE_URL }}
      AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
      AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}

Environment secrets — scoped to specific deployment environments (staging, production):

jobs:
  deploy-production:
    runs-on: ubuntu-latest
    environment: production   # Uses secrets from "production" environment
    steps:
      - name: Deploy
        run: ./deploy.sh
        env:
          API_KEY: ${{ secrets.API_KEY }}  # From the "production" environment

Conditional Execution

Use if to control when jobs or steps run:

jobs:
  deploy:
    runs-on: ubuntu-latest
    # Only deploy on pushes to main (not PRs)
    if: github.ref == 'refs/heads/main' && github.event_name == 'push'
    steps:
      - name: Deploy
        run: ./deploy.sh

  notify-failure:
    runs-on: ubuntu-latest
    needs: [test, deploy]
    # Run this job only if any previous job failed
    if: failure()
    steps:
      - name: Send Slack notification
        run: ./notify-failure.sh
        env:
          SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }}

Common if conditions:

ConditionWhen it runs
github.ref == 'refs/heads/main'Only on main branch
github.event_name == 'pull_request'Only on PRs
success()Previous steps succeeded (default)
failure()Any previous step failed
always()Regardless of previous steps
contains(github.event.pull_request.labels.*.name, 'deploy')PR has ‘deploy’ label

Caching Dependencies

Cache node_modules or pip packages to dramatically speed up workflows:

steps:
  - uses: actions/checkout@v4

  - uses: actions/setup-node@v4
    with:
      node-version: 20
      cache: 'npm'    # Built-in npm cache — restores node_modules automatically

  - run: npm ci

For custom paths:

steps:
  - uses: actions/cache@v4
    with:
      path: |
        ~/.npm
        .next/cache
      key: ${{ runner.os }}-nextjs-${{ hashFiles('**/package-lock.json') }}
      restore-keys: |
        ${{ runner.os }}-nextjs-

The key includes a hash of package-lock.json — the cache is invalidated automatically when dependencies change.


Useful Marketplace Actions

steps:
  # Checkout your code
  - uses: actions/checkout@v4

  # Set up language runtimes
  - uses: actions/setup-node@v4
    with:
      node-version: 20

  - uses: actions/setup-python@v5
    with:
      python-version: '3.12'

  # Upload build artifacts (accessible in the Actions UI)
  - uses: actions/upload-artifact@v4
    with:
      name: build-output
      path: dist/
      retention-days: 7

  # Download artifacts in a later job
  - uses: actions/download-artifact@v4
    with:
      name: build-output

  # Send a Slack notification
  - uses: slackapi/slack-github-action@v1
    with:
      payload: '{"text": "Deployment successful! :rocket:"}'
    env:
      SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK }}

Monorepo CI — Run Jobs Only for Changed Packages

When only apps/web changes, skip the api tests and vice versa:

# .github/workflows/monorepo-ci.yml
name: Monorepo CI

on: [push, pull_request]

jobs:
  # Detect which packages changed
  changes:
    runs-on: ubuntu-latest
    outputs:
      web: ${{ steps.filter.outputs.web }}
      api: ${{ steps.filter.outputs.api }}
    steps:
      - uses: actions/checkout@v4
      - uses: dorny/paths-filter@v3
        id: filter
        with:
          filters: |
            web:
              - 'apps/web/**'
            api:
              - 'apps/api/**'

  test-web:
    needs: changes
    if: needs.changes.outputs.web == 'true'
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: npm ci && npm test --workspace=apps/web

  test-api:
    needs: changes
    if: needs.changes.outputs.api == 'true'
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: npm ci && npm test --workspace=apps/api

Scheduled Workflows (Cron)

Run workflows on a schedule — useful for nightly builds, database backups, or report generation:

on:
  schedule:
    - cron: '0 2 * * *'   # Every day at 2:00 AM UTC
  workflow_dispatch:        # Also allow manual trigger
Cron format:
┌─ minute (0-59)
│ ┌─ hour (0-23)
│ │ ┌─ day of month (1-31)
│ │ │ ┌─ month (1-12)
│ │ │ │ ┌─ day of week (0-6, 0=Sunday)
│ │ │ │ │
0 2 * * *

Reusable Workflows

Define a workflow once and call it from multiple repositories:

# .github/workflows/reusable-deploy.yml
on:
  workflow_call:
    inputs:
      environment:
        required: true
        type: string
    secrets:
      deploy-token:
        required: true

jobs:
  deploy:
    runs-on: ubuntu-latest
    environment: ${{ inputs.environment }}
    steps:
      - uses: actions/checkout@v4
      - name: Deploy
        run: ./deploy.sh ${{ inputs.environment }}
        env:
          TOKEN: ${{ secrets.deploy-token }}

Call it from another workflow:

jobs:
  deploy-staging:
    uses: myorg/shared-workflows/.github/workflows/reusable-deploy.yml@main
    with:
      environment: staging
    secrets:
      deploy-token: ${{ secrets.STAGING_TOKEN }}

Essential Workflow Patterns

PatternUse case
on: push + pull_requestRun tests on every change
if: github.ref == 'refs/heads/main'Deploy only from main
needs: [test]Require tests to pass before deploy
strategy.matrixTest across multiple versions in parallel
environment: productionGate deployments with required approvals
workflow_dispatchManual trigger with optional inputs
cache: 'npm'Speed up builds with dependency caching
upload-artifactPass build output between jobs

Summary

  1. Workflows live in .github/workflows/ — YAML files that trigger on events
  2. Jobs run in parallel by default — use needs to create dependencies
  3. Use Secrets for API keys, tokens, and passwords — never hardcode them
  4. Cache dependencies to cut build times from minutes to seconds
  5. Browse the Marketplace — most common tasks already have a maintained Action

Related posts:


Frequently Asked Questions (FAQ)

Q. When would I use this in practice?

A. Master GitHub Actions for CI/CD automation.

Q. What should I read before this?

A. Follow the previous article or related articles links at the bottom of each post to learn in sequence.

Q. Where can I study this more deeply?

A. Check cppreference and the relevant library’s official documentation. The reference links at the end of the article are also worth using.


Other articles related to this topic.


Keywords Covered in This Article (Related Search Terms)

This article covers GitHub Actions, CI/CD, Automation, Workflow, DevOps, Deployment, Testing.