GitHub 18 ๐ GitHub Actions CI/CD Pipeline Automation
A workflow that runs tests is continuous integration. A workflow that builds, tests, and deploys is continuous delivery. A workflow that deploys automatically on every merge to main is continuous deployment. GitHub Actions supports all three, but the difference between them is not the YAML syntaxโit is the gates, environments, and conditions that separate one stage from the next. This chapter covers the transition from a basic CI workflow to a production-ready pipeline with build artifacts, environment protections, secrets, and deployment gates.
Key point: CI/CD in Actions is built on the needs keyword, the environment key, and the separation of jobs. Each stage is a job. The dependencies between jobs define the pipeline. Environment protection rules define who can deploy and when.
Why CI/CD requires more than a single job
The shared-state problem. A single job runs every step on the same runner. If the test step modifies files, the deployment step sees those modifications. If the build step caches artifacts locally, the deployment step uses them. This is convenient for small projects but creates hidden dependencies and makes it difficult to retry a failed stage without re-running the entire pipeline. Splitting into jobs isolates each stage on a fresh runner, eliminates shared state, and enables stage-level retries.
The artifact problem. A build artifactโa compiled binary, a Docker image, a JavaScript bundleโmust move from the build job to the deployment job. Jobs do not share a filesystem. The actions/upload-artifact action stores files from the build job, and actions/download-artifact retrieves them in the deployment job. This is the mechanism that carries the output of one stage into the input of the next.
The environment problem. Production is not a test environment. Deploying to production requires credentials, approval, and often a waiting period. GitHub Environments are named targets (staging, production) with protection rules: required reviewers, wait timers, and deployment branch restrictions. A job that specifies environment: production cannot run until the rules are satisfied. This is the gate between CI and CD.
The secrets problem. Deployment credentialsโAPI keys, SSH keys, cloud provider tokensโmust not be stored in the repository. GitHub Secrets are encrypted values stored at the repository, organization, or environment level. Environment-level secrets are only accessible to jobs that specify that environment. A production deployment job with environment: production can access production secrets; a test job cannot.
The trade-off. A multi-job pipeline is slower than a single job because each job waits for a runner. It is also more complex to configure. The trade-off is correctness: each stage is isolated, each failure is attributable to a specific job, and the production gate is enforced by the platform, not by convention.
a. Separating build, test, and deploy into jobs
A CI/CD pipeline is a sequence of jobs. Each job has a purpose: build the artifact, run the tests, deploy to staging, deploy to production. The needs keyword creates the sequence. The environment keyword attaches protection rules.
name: Pipeline
on:
push:
branches: [main]
pull_request:
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- run: npm ci
- run: npm run build
- uses: actions/upload-artifact@v4
with:
name: dist
path: dist/
test:
needs: build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
- run: npm ci
- run: npm test
The build job produces the dist directory and uploads it as an artifact. The test job depends on build and re-runs npm ci because each job has a fresh filesystem. If the test job needs the built artifact, it uses actions/download-artifact instead of rebuilding.
b. Using environments for deployment gates
An environment is a named target with protection rules. The environment: key on a job attaches the job to that environment and enforces the rules before the job can start.
deploy-staging:
needs: test
runs-on: ubuntu-latest
environment: staging
steps:
- uses: actions/download-artifact@v4
with:
name: dist
- run: ./deploy.sh staging
env:
DEPLOY_TOKEN: ${{ secrets.STAGING_TOKEN }}
deploy-production:
needs: deploy-staging
runs-on: ubuntu-latest
environment: production
steps:
- uses: actions/download-artifact@v4
with:
name: dist
- run: ./deploy.sh production
env:
DEPLOY_TOKEN: ${{ secrets.PRODUCTION_TOKEN }}
The staging environment has no protection rulesโthe job runs immediately after test. The production environment has required reviewers, so the job pauses until an authorized user approves the deployment in the Actions tab. The secrets.PRODUCTION_TOKEN is only accessible to the job that specifies environment: production.
c. Conditions, concurrency, and caching
The if: condition controls whether a job or step runs. A deployment job should only run on the main branch, not on pull requests.
deploy-production:
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
needs: deploy-staging
environment: production
The concurrency block prevents two deployments from running simultaneously. This is critical when a rapid sequence of merges triggers multiple pipeline runs.
concurrency:
group: production-deploy
cancel-in-progress: false
The cancel-in-progress: false setting ensures that a running deployment is not canceled by a new oneโit waits for the current deployment to finish. For CI (test) jobs, cancel-in-progress: true cancels the older run to save resources.
Caching accelerates repeated installs. The actions/cache action stores dependency directories across runs, keyed by the lockfile hash.
- uses: actions/cache@v4
with:
path: ~/.npm
key: ${{ runner.os }}-node-${{ hashFiles('package-lock.json') }}
The cache key includes the lockfile hash, so the cache is invalidated when dependencies change. Without the lockfile in the key, stale dependencies would be restored.
Complete Example Session
# ============================================
# PART 1: DEFINE THE PIPELINE
# ============================================
name: CI/CD Pipeline
on:
push:
branches: [main]
pull_request:
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
# ============================================
# PART 2: BUILD JOB
# ============================================
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- uses: actions/cache@v4
with:
path: ~/.npm
key: ${{ runner.os }}-node-${{ hashFiles('package-lock.json') }}
- run: npm ci
- run: npm run build
- uses: actions/upload-artifact@v4
with:
name: dist
path: dist/
# ============================================
# PART 3: TEST JOB
# ============================================
test:
needs: build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
- run: npm ci
- run: npm test
# ============================================
# PART 4: STAGING DEPLOYMENT
# ============================================
deploy-staging:
needs: test
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
environment: staging
steps:
- uses: actions/download-artifact@v4
with:
name: dist
- run: ./deploy.sh staging
env:
DEPLOY_TOKEN: ${{ secrets.STAGING_TOKEN }}
# ============================================
# PART 5: PRODUCTION DEPLOYMENT
# ============================================
deploy-production:
needs: deploy-staging
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
environment: production
steps:
- uses: actions/download-artifact@v4
with:
name: dist
- run: ./deploy.sh production
env:
DEPLOY_TOKEN: ${{ secrets.PRODUCTION_TOKEN }}
# ============================================
# PART 6: CONFIGURE ENVIRONMENTS
# ============================================
# Settings โ Environments โ New environment: production
# Add required reviewers
# Add deployment branch restriction: main only
# ============================================
# PART 7: ADD SECRETS
# ============================================
# Settings โ Secrets and variables โ Actions
# Add STAGING_TOKEN and PRODUCTION_TOKEN
# For environment-specific: Settings โ Environments โ production โ Secrets
# ============================================
# PART 8: VERIFY PIPELINE
# ============================================
# Push to main โ pipeline runs
# build โ test โ deploy-staging โ pause at deploy-production
# Approve in Actions tab โ deploy-production runs
# ============================================
# PART 9: INSPECT LOGS
# ============================================
# Each job shows its own log.
# A failed stage can be re-run without re-running earlier stages.
The nine parts trace the pipeline: definition, build, test, staging, production, environment configuration, secrets, verification, and inspection.
Quick Reference
Pipeline Keywords
| Keyword | Purpose |
|---|---|
needs | Job dependency |
environment | Attach protection rules |
if | Conditional execution |
concurrency | Prevent parallel runs |
uses: actions/upload-artifact | Store files from build |
uses: actions/download-artifact | Retrieve in next job |
Environment Protection
| Rule | Effect |
|---|---|
| Required reviewers | Job pauses until approval |
| Wait timer | Job waits N minutes |
| Deployment branches | Only specified branches can deploy |
Concurrency Settings
| Setting | Effect |
|---|---|
group | Unique name for the concurrency group |
cancel-in-progress: true | Cancel older runs (CI) |
cancel-in-progress: false | Queue runs (CD) |
Best Practices
โ Do This:
if: github.ref == 'refs/heads/main'
<!-- Only deploy from main --> # โ
environment: production
<!-- Attach protection rules --> # โ
concurrency:
group: production-deploy
cancel-in-progress: false
<!-- Prevent concurrent deploys --> # โ
โ Don’t Do This:
# Deploy from pull requests
<!-- Allows unreviewed code into production --> # โ
# Hardcode deployment tokens
<!-- Use environment secrets --> # โ
# Allow concurrent deployments
<!-- Two deploys can race and corrupt state --> # โ
Common Pitfalls
| Pitfall | Why It Happens | Fix |
|---|---|---|
| Deployment runs on PR | No if condition | Add if: github.ref == 'refs/heads/main' |
| Artifact not found | Path mismatch | Verify upload-artifact path |
| Deployment secrets inaccessible | Secrets defined at repo level, not env | Add secrets under Environment settings |
| Two deploys race | No concurrency block | Add concurrency with cancel-in-progress: false |
| Environment not pausing | No required reviewers | Add protection rules in Settings |
Real-World Examples
1. Build and Upload
- uses: actions/upload-artifact@v4
with:
name: dist
path: dist/
2. Download in Deploy
- uses: actions/download-artifact@v4
with:
name: dist
3. Staging Environment
environment: staging
4. Production Gate
environment: production
5. Conditional Deploy
if: github.ref == 'refs/heads/main'
6. Concurrency Guard
concurrency:
group: production
cancel-in-progress: false
7. Cache Dependencies
uses: actions/cache@v4
8. Environment Secret
env:
TOKEN: ${{ secrets.PRODUCTION_TOKEN }}
9. Job Dependency
needs: deploy-staging
10. Manual Approval
Settings โ Environments โ Required reviewers
Visual
Pipeline Flow
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Push to main โ
โโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ build โ
โ โ upload-artifact: dist โ
โโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ needs
โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ test โ
โ โ npm test โ
โโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ needs + if
โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ deploy-staging โ
โ environment: staging โ
โโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ needs
โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ deploy-production โ
โ environment: production (requires approval) โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Environment Gate
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ deploy-production job โ
โโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Environment: production โ
โ โโ Required reviewer? โ
โ โ โโ YES โ PAUSE โ
โ โ โโ NO โ RUN โ
โ โโ Wait timer? โ
โ โ โโ YES โ WAIT โ
โ โ โโ NO โ RUN โ
โ โโ Branch restriction? โ
โ โโ main only โ RUN if on main โ
โ โโ else โ BLOCK โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
The environment is the gate. Nothing reaches production until the rules are satisfied.
Summary
| Item | Value |
|---|---|
| Pipeline stage | Job |
| Job dependency | needs |
| Artifact upload | actions/upload-artifact |
| Artifact download | actions/download-artifact |
| Environment gate | environment: |
| Required reviewers | Settings โ Environments |
| Concurrency guard | concurrency: group: |
| Conditional deploy | if: github.ref == 'refs/heads/main' |
Key takeaways:
- A CI/CD pipeline is a sequence of jobs. Each job runs on a fresh runner. The
needskeyword establishes the sequence, and artifacts carry output from one stage to the next. - Environments enforce production gates. A job with
environment: productioncannot run until required reviewers approve and any wait timer expires. This is the line between continuous delivery and continuous deployment. - Environment secrets are scoped. Secrets defined under an environment are only accessible to jobs that specify that environment. A test job cannot read production credentials.
- Concurrency prevents racing deploys. Two simultaneous deployments to the same environment can corrupt state. The
concurrencyblock withcancel-in-progress: falseserializes them. ifconditions gate deployments by branch. Deployments should not run on pull requests. The conditiongithub.ref == 'refs/heads/main'ensures that only merged code reaches production.
Remember: CI/CD is not a single workflow fileโit is a sequence of jobs with gates. The build job produces artifacts, the test job validates them, the staging job deploys automatically, and the production job waits for approval. The environment is the gate, the secrets are the credentials, and the concurrency block is the safety. Without these, the pipeline is just a sequence of commands that happen to run in order.
Stop using slow, ad-bloated tool sites! ๐คฎ
๐ Search “KandZ Tools” on Google to use many professional utilities for free.
KandZ.me is the ultimate minimalist hub for:
โ
Finance (Mortgage, Interest, Inflation)
โ
Tech (Base64, JSON, Dev Suite, IP)
โ
Health (BMI, BMR, TDEE)
โ
Productivity (Timer, Workspace, QR)
โก๏ธ Fast & Private
๐ No data leaves your device
๐ 100% Free
๐ Use it now: https://tools.kandz.me
๐ Bookmark itโyouโll need it later!