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
| Term | What it is |
|---|---|
| Workflow | A YAML file in .github/workflows/ that defines automation |
| Event | What triggers the workflow (push, pull_request, schedule, etc.) |
| Job | A set of steps that run on the same runner |
| Step | A single command or Action within a job |
| Action | A reusable unit of work (from Marketplace or your own repo) |
| Runner | The 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:
| Condition | When 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
| Pattern | Use case |
|---|---|
on: push + pull_request | Run tests on every change |
if: github.ref == 'refs/heads/main' | Deploy only from main |
needs: [test] | Require tests to pass before deploy |
strategy.matrix | Test across multiple versions in parallel |
environment: production | Gate deployments with required approvals |
workflow_dispatch | Manual trigger with optional inputs |
cache: 'npm' | Speed up builds with dependency caching |
upload-artifact | Pass build output between jobs |
Summary
- Workflows live in
.github/workflows/— YAML files that trigger on events - Jobs run in parallel by default — use
needsto create dependencies - Use Secrets for API keys, tokens, and passwords — never hardcode them
- Cache dependencies to cut build times from minutes to seconds
- 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.
Related Articles (Internal Links)
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.