By the end of this lesson, you will be able to:
GitHub Actions is a powerful automation platform that enables you to create custom workflows directly in your GitHub repository. From continuous integration and continuous deployment (CI/CD) to automating any aspect of your development workflow, GitHub Actions makes it possible to build, test, and deploy your code right from GitHub.
:bulb: :bulb: Pro Tip GitHub Actions is free for public repositories and includes 2,000 minutes/month for private repos. Many companies save thousands of dollars by migrating from other CI/CD platforms.
Continuous Integration is the practice of:
Continuous Deployment is the practice of:
Scenario: A fintech startup needs to deploy updates multiple times per day while ensuring security compliance.
Solution: Implement CI/CD pipeline with:
Impact:
Benefit | Traditional Approach | CI/CD Approach | Business Impact |
---|---|---|---|
Bug Detection | Found in production | Found in minutes | 85% reduction in production bugs |
Deployment Time | Hours to days | Minutes | 10x faster feature delivery |
Quality Assurance | Manual testing | Automated + manual | Consistent quality standards |
Risk Management | Big bang releases | Small increments | 75% reduction in rollbacks |
Team Productivity | Waiting for deployments | Continuous flow | 40% more features delivered |
Component | Description | Example |
---|---|---|
Workflow | Automated process in YAML | .github/workflows/ci.yml |
Event | Trigger that starts workflow | push , pull_request , schedule |
Job | Set of steps on same runner | build , test , deploy |
Step | Individual task in a job | Checkout code, Run tests |
Action | Reusable unit of code | actions/checkout@v3 |
Runner | Server executing workflows | ubuntu-latest , windows-latest |
name: Workflow Name
on: [trigger events]
jobs:
job-name:
runs-on: runner-type
steps:
- name: Step name
uses: action-name
- name: Another step
run: shell command
:bulb: :bulb: Pro Tip Always pin your action versions (e.g.,
actions/checkout@v3.5.2
) in production workflows to avoid breaking changes. Use Dependabot to keep them updated safely.
Create .github/workflows/hello-world.yml
:
name: Hello World Workflow
# Triggers
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
# Jobs
jobs:
hello:
runs-on: ubuntu-latest
steps:
# Check out repository
- name: Checkout code
uses: actions/checkout@v3
# Run a simple command
- name: Say hello
run: echo "Hello, GitHub Actions!"
# Run multiple commands
- name: System information
run: |
echo "Runner OS: $RUNNER_OS"
echo "Repository: $GITHUB_REPOSITORY"
echo "Commit SHA: $GITHUB_SHA"
on:
push:
branches:
- main
- develop
- 'feature/**'
tags:
- v*
paths:
- 'src/**'
- '!src/tests/**'
on:
pull_request:
types: [opened, synchronize, reopened]
branches:
- main
on:
schedule:
# Run at 2 AM UTC every day
- cron: '0 2 * * *'
# Run every Monday at 9 AM UTC
- cron: '0 9 * * 1'
on:
workflow_dispatch:
inputs:
environment:
description: 'Deployment environment'
required: true
default: 'staging'
type: choice
options:
- staging
- production
on:
# When issues are opened or labeled
issues:
types: [opened, labeled]
# When releases are published
release:
types: [published]
# When other workflows complete
workflow_run:
workflows: ["Build"]
types: [completed]
name: Node.js CI/CD Pipeline
on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main ]
# Limit concurrent deployments
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
# Security scanning job
security:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Run security audit
run: npm audit --audit-level=moderate
- name: Run Snyk security scan
uses: snyk/actions/node@master
env:
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
with:
args: --severity-threshold=high
# Testing job with coverage
test:
runs-on: ubuntu-latest
needs: security
strategy:
matrix:
node-version: [16.x, 18.x, 20.x]
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Setup Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
cache: 'npm'
- name: Cache dependencies
uses: actions/cache@v3
with:
path: ~/.npm
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-node-
- name: Install dependencies
run: npm ci
- name: Run linter
run: npm run lint
- name: Run tests with coverage
run: npm test -- --coverage --watchAll=false
- name: Upload coverage reports
uses: codecov/codecov-action@v3
with:
token: ${{ secrets.CODECOV_TOKEN }}
fail_ci_if_error: true
- name: SonarCloud Scan
uses: SonarSource/sonarcloud-github-action@master
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
- name: Build project
run: npm run build
- name: Upload build artifacts
uses: actions/upload-artifact@v3
with:
name: build-${{ matrix.node-version }}
path: dist/
retention-days: 1
# Deploy to staging
deploy-staging:
if: github.ref == 'refs/heads/develop'
needs: test
runs-on: ubuntu-latest
environment:
name: staging
url: https://staging.example.com
steps:
- uses: actions/checkout@v3
- name: Download build artifacts
uses: actions/download-artifact@v3
with:
name: build-18.x
- name: Deploy to staging
run: |
# Your deployment script here
echo "Deploying to staging..."
- name: Run smoke tests
run: |
npm run test:e2e -- --env=staging
# Deploy to production
deploy-production:
if: github.ref == 'refs/heads/main'
needs: test
runs-on: ubuntu-latest
environment:
name: production
url: https://example.com
steps:
- uses: actions/checkout@v3
- name: Download build artifacts
uses: actions/download-artifact@v3
with:
name: build-18.x
- name: Deploy to production
run: |
# Your deployment script here
echo "Deploying to production..."
- name: Notify team
uses: 8398a7/action-slack@v3
with:
status: ${{ job.status }}
text: 'Production deployment completed!'
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK }}
:bulb: :bulb: Pro Tip Use GitHub Environments to add manual approval requirements for production deployments. This adds an extra safety layer for critical deployments.
name: Python Application CI/CD
on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main ]
env:
PYTHON_VERSION: '3.11'
POETRY_VERSION: '1.4.2'
jobs:
quality:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: ${{ env.PYTHON_VERSION }}
- name: Install Poetry
uses: snok/install-poetry@v1
with:
version: ${{ env.POETRY_VERSION }}
virtualenvs-create: true
virtualenvs-in-project: true
- name: Load cached venv
id: cached-poetry-dependencies
uses: actions/cache@v3
with:
path: .venv
key: venv-${{ runner.os }}-${{ hashFiles('**/poetry.lock') }}
- name: Install dependencies
if: steps.cached-poetry-dependencies.outputs.cache-hit != 'true'
run: poetry install --no-interaction --no-root
- name: Install project
run: poetry install --no-interaction
- name: Run black formatter
run: poetry run black . --check
- name: Run isort
run: poetry run isort . --check-only
- name: Run flake8
run: poetry run flake8 .
- name: Run mypy type checking
run: poetry run mypy .
- name: Run security checks with bandit
run: poetry run bandit -r . -ll
- name: Run tests with pytest
run: |
poetry run pytest \
--cov=src \
--cov-report=xml \
--cov-report=html \
--junitxml=junit.xml
- name: Upload coverage
uses: codecov/codecov-action@v3
with:
files: ./coverage.xml
fail_ci_if_error: true
docker:
needs: quality
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Log in to Docker Hub
if: github.event_name != 'pull_request'
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Build and push Docker image
uses: docker/build-push-action@v4
with:
context: .
push: ${{ github.event_name != 'pull_request' }}
tags: |
${{ secrets.DOCKER_USERNAME }}/app:latest
${{ secrets.DOCKER_USERNAME }}/app:${{ github.sha }}
cache-from: type=gha
cache-to: type=gha,mode=max
- name: Run Trivy vulnerability scanner
uses: aquasecurity/trivy-action@master
with:
image-ref: ${{ secrets.DOCKER_USERNAME }}/app:${{ github.sha }}
format: 'sarif'
output: 'trivy-results.sarif'
- name: Upload Trivy scan results
uses: github/codeql-action/upload-sarif@v2
with:
sarif_file: 'trivy-results.sarif'
Strategy | Description | Best For |
---|---|---|
Blue-Green | Two identical environments, switch traffic | Zero-downtime deployments |
Canary | Gradual rollout to subset of users | Risk mitigation |
Rolling | Update instances one at a time | Large applications |
Feature Flags | Toggle features without deployment | A/B testing |
name: AWS Production Deployment
on:
push:
tags:
- 'v*'
env:
AWS_REGION: us-east-1
ECR_REPOSITORY: myapp
ECS_SERVICE: myapp-service
ECS_CLUSTER: production-cluster
CONTAINER_NAME: myapp
jobs:
deploy:
runs-on: ubuntu-latest
environment:
name: production
url: https://app.example.com
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v2
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: ${{ env.AWS_REGION }}
- name: Login to Amazon ECR
id: login-ecr
uses: aws-actions/amazon-ecr-login@v1
- name: Build, tag, and push image to Amazon ECR
id: build-image
env:
ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
IMAGE_TAG: ${{ github.sha }}
run: |
docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG .
docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
echo "image=$ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG" >> $GITHUB_OUTPUT
- name: Fill in the new image ID in the Amazon ECS task definition
id: task-def
uses: aws-actions/amazon-ecs-render-task-definition@v1
with:
task-definition: task-definition.json
container-name: ${{ env.CONTAINER_NAME }}
image: ${{ steps.build-image.outputs.image }}
- name: Deploy Amazon ECS task definition
uses: aws-actions/amazon-ecs-deploy-task-definition@v1
with:
task-definition: ${{ steps.task-def.outputs.task-definition }}
service: ${{ env.ECS_SERVICE }}
cluster: ${{ env.ECS_CLUSTER }}
wait-for-service-stability: true
- name: Run post-deployment tests
run: |
npm run test:production
- name: Notify deployment status
if: always()
uses: 8398a7/action-slack@v3
with:
status: ${{ job.status }}
text: |
Deployment to production ${{ job.status }}
Version: ${{ github.ref }}
Commit: ${{ github.sha }}
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK }}
name: Kubernetes GitOps Deployment
on:
push:
branches: [ main ]
paths:
- 'src/**'
- 'Dockerfile'
- 'k8s/**'
jobs:
build-and-update:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Log in to Container Registry
uses: docker/login-action@v2
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push Docker image
uses: docker/build-push-action@v4
with:
context: .
push: true
tags: |
ghcr.io/${{ github.repository }}:latest
ghcr.io/${{ github.repository }}:${{ github.sha }}
cache-from: type=gha
cache-to: type=gha,mode=max
- name: Update Kubernetes manifests
run: |
sed -i "s|image: .*|image: ghcr.io/${{ github.repository }}:${{ github.sha }}|g" k8s/deployment.yaml
- name: Commit and push manifest changes
uses: EndBug/add-and-commit@v9
with:
add: 'k8s/deployment.yaml'
message: 'Update image to ${{ github.sha }}'
default_author: github_actions
# ArgoCD will automatically sync and deploy the changes
name: Deploy Serverless Application
on:
push:
branches: [ main ]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '18'
- name: Install Serverless Framework
run: npm install -g serverless
- name: Install dependencies
run: npm ci
- name: Run tests
run: npm test
- name: Deploy to AWS Lambda
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
run: |
serverless deploy --stage production --region us-east-1
- name: Run integration tests
run: |
npm run test:integration -- --stage=production
- name: Output function URLs
run: |
serverless info --stage production --verbose
:bulb: :bulb: Pro Tip Always implement health checks and automated rollback mechanisms in your deployment pipelines. This ensures quick recovery from failed deployments.
steps:
# Checkout code
- uses: actions/checkout@v3
# Setup programming languages
- uses: actions/setup-node@v3
- uses: actions/setup-python@v4
- uses: actions/setup-java@v3
# Cache dependencies
- uses: actions/cache@v3
with:
path: ~/.npm
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
# Upload/Download artifacts
- uses: actions/upload-artifact@v3
with:
name: build-files
path: dist/
- uses: actions/download-artifact@v3
with:
name: build-files
# Create releases
- uses: softprops/action-gh-release@v1
with:
files: dist/*
tag_name: v${{ github.run_number }}
env:
# Workflow-level environment variables
NODE_ENV: production
CI: true
jobs:
build:
runs-on: ubuntu-latest
env:
# Job-level environment variables
API_ENDPOINT: ${{ secrets.API_ENDPOINT }}
steps:
- name: Use environment variables
env:
# Step-level environment variables
CUSTOM_VAR: "Hello"
run: |
echo "NODE_ENV is $NODE_ENV"
echo "API endpoint is $API_ENDPOINT"
echo "Custom var is $CUSTOM_VAR"
steps:
- name: Run only on main branch
if: github.ref == 'refs/heads/main'
run: echo "This is main branch"
- name: Run only on pull requests
if: github.event_name == 'pull_request'
run: echo "This is a pull request"
- name: Run only if previous step failed
if: failure()
run: echo "Previous step failed"
- name: Always run
if: always()
run: echo "This always runs"
- name: Complex condition
if: |
github.event_name == 'push' &&
contains(github.ref, 'refs/tags/') &&
github.actor != 'dependabot[bot]'
run: echo "Complex condition met"
strategy:
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
node: [14, 16, 18]
include:
- os: ubuntu-latest
node: 18
experimental: true
exclude:
- os: windows-latest
node: 14
runs-on: ${{ matrix.os }}
steps:
- uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node }}
- name: Run tests
continue-on-error: ${{ matrix.experimental == true }}
run: npm test
Create .github/workflows/reusable-build.yml
:
name: Reusable Build Workflow
on:
workflow_call:
inputs:
node-version:
required: false
type: string
default: '16'
secrets:
npm-token:
required: true
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: ${{ inputs.node-version }}
registry-url: 'https://registry.npmjs.org'
- run: npm ci
env:
NODE_AUTH_TOKEN: ${{ secrets.npm-token }}
- run: npm run build
Use the reusable workflow:
name: Main Workflow
on: push
jobs:
call-reusable:
uses: ./.github/workflows/reusable-build.yml
with:
node-version: '18'
secrets:
npm-token: ${{ secrets.NPM_TOKEN }}
action.yml
:
name: 'Hello World Action'
description: 'Greets someone and records time'
inputs:
who-to-greet:
description: 'Who to greet'
required: true
default: 'World'
outputs:
time:
description: 'The time we greeted you'
runs:
using: 'node16'
main: 'index.js'
index.js
:
const core = require('@actions/core');
const github = require('@actions/github');
try {
const nameToGreet = core.getInput('who-to-greet');
console.log(`Hello ${nameToGreet}!`);
const time = (new Date()).toTimeString();
core.setOutput("time", time);
const payload = JSON.stringify(github.context.payload, undefined, 2);
console.log(`The event payload: ${payload}`);
} catch (error) {
core.setFailed(error.message);
}
Dockerfile
:
FROM alpine:3.10
COPY entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"]
action.yml
:
name: 'Docker Action'
description: 'Run a Docker container'
inputs:
myInput:
description: 'Input to use'
required: true
runs:
using: 'docker'
image: 'Dockerfile'
args:
- ${{ inputs.myInput }}
Issue: Workflow doesn't run when expected
# Check these common causes:
# 1. Branch protection rules
on:
push:
branches: [ main ] # Ensure branch name is correct
# 2. Path filters too restrictive
on:
push:
paths:
- 'src/**' # Won't trigger for docs changes
- '!src/tests/**' # Excludes test files
# 3. Workflow file location
# Must be in .github/workflows/ directory
Solution:
act
tool locallyIssue: "Resource not accessible by integration"
# Solution: Add explicit permissions
permissions:
contents: read
packages: write
pull-requests: write
issues: write
Issue: "Error: Input required and not supplied: token"
# Debug secrets:
steps:
- name: Check if secret exists
run: |
if [ -z "${{ secrets.MY_SECRET }}" ]; then
echo "Secret MY_SECRET is not set"
exit 1
fi
Issue: Cache miss despite no changes
# Solution: Use restore-keys for fallback
- uses: actions/cache@v3
with:
path: ~/.npm
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-node-
${{ runner.os }}-
Issue: Job cancelled after 6 hours
jobs:
long-running:
runs-on: ubuntu-latest
timeout-minutes: 360 # Set custom timeout (max 360)
steps:
- name: Long operation
timeout-minutes: 60 # Step-level timeout
run: ./long-script.sh
# Enable debug logging
env:
ACTIONS_STEP_DEBUG: true
ACTIONS_RUNNER_DEBUG: true
steps:
# Add debug output
- name: Debug context
run: |
echo "Event: ${{ github.event_name }}"
echo "Ref: ${{ github.ref }}"
echo "SHA: ${{ github.sha }}"
echo "Actor: ${{ github.actor }}"
# Use tmate for interactive debugging
- name: Setup tmate session
if: ${{ failure() }}
uses: mxschmitt/action-tmate@v3
timeout-minutes: 15
:bulb: :bulb: Pro Tip Use the GitHub Actions VS Code extension to validate workflow syntax before pushing. It catches most syntax errors and provides IntelliSense for actions.
# Use OIDC for cloud authentication (no long-lived secrets)
- uses: aws-actions/configure-aws-credentials@v2
with:
role-to-assume: ${{ secrets.AWS_ROLE_ARN }}
aws-region: us-east-1
# Scan for secrets before commit
- name: Scan for secrets
uses: trufflesecurity/trufflehog@main
with:
path: ./
base: ${{ github.event.repository.default_branch }}
# Use least privilege permissions
permissions:
contents: read # Only what's needed
# Pin actions to commit SHA for security
steps:
- uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2
# Use job outputs to share data
jobs:
setup:
outputs:
version: ${{ steps.version.outputs.version }}
steps:
- id: version
run: echo "version=$(cat version.txt)" >> $GITHUB_OUTPUT
build:
needs: setup
env:
VERSION: ${{ needs.setup.outputs.version }}
# Optimize Docker builds
- uses: docker/build-push-action@v4
with:
cache-from: type=gha
cache-to: type=gha,mode=max
platforms: linux/amd64,linux/arm64
# Use sparse checkout for large repos
- uses: actions/checkout@v3
with:
sparse-checkout: |
src
tests
sparse-checkout-cone-mode: false
# Use composite actions for reusability
# .github/actions/setup/action.yml
name: 'Setup Environment'
runs:
using: "composite"
steps:
- uses: actions/setup-node@v3
with:
node-version: '18'
- run: npm ci
shell: bash
# Use workflow_call for reusable workflows
on:
workflow_call:
inputs:
environment:
required: true
type: string
# Cancel outdated runs
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ github.ref != 'refs/heads/main' }}
# Use path filters to avoid unnecessary runs
on:
push:
paths:
- 'src/**'
- 'package*.json'
- '.github/workflows/ci.yml'
# Use self-hosted runners for heavy workloads
runs-on: [self-hosted, linux, x64]
Add to README.md:

steps:
- name: Enable debug logging
run: echo "::debug::Debug message"
- name: Set output
id: myStep
run: echo "::set-output name=myOutput::Hello"
- name: Use output
run: echo "Output was ${{ steps.myStep.outputs.myOutput }}"
Use specific triggers
on:
push:
paths:
- 'src/**'
- 'package.json'
Cancel redundant workflows
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
Use self-hosted runners for intensive workloads
Create a production-ready CI/CD pipeline for a Node.js application with the following requirements:
Requirements:
Starter Template:
name: Full-Stack CI/CD Pipeline
on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main ]
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
jobs:
# TODO: Add security scanning job
test:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [16.x, 18.x, 20.x]
steps:
# TODO: Complete test job
- uses: actions/checkout@v3
build:
needs: [test] # Add security job here
runs-on: ubuntu-latest
steps:
# TODO: Build and push Docker image
deploy-staging:
# TODO: Add deployment logic for staging
deploy-production:
# TODO: Add deployment logic for production with manual approval
name: Full-Stack CI/CD Pipeline
on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main ]
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
security:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Run Trivy security scan
uses: aquasecurity/trivy-action@master
with:
scan-type: 'fs'
scan-ref: '.'
format: 'sarif'
output: 'trivy-results.sarif'
- name: Upload Trivy scan results
uses: github/codeql-action/upload-sarif@v2
with:
sarif_file: 'trivy-results.sarif'
test:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [16.x, 18.x, 20.x]
steps:
- uses: actions/checkout@v3
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run linter
run: npm run lint
- name: Run tests with coverage
run: npm test -- --coverage
- name: Upload coverage
if: matrix.node-version == '18.x'
uses: codecov/codecov-action@v3
build:
needs: [security, test]
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
outputs:
image: ${{ steps.image.outputs.image }}
steps:
- uses: actions/checkout@v3
- name: Log in to Container Registry
uses: docker/login-action@v2
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Build and push Docker image
id: build
uses: docker/build-push-action@v4
with:
context: .
push: true
tags: |
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}
cache-from: type=gha
cache-to: type=gha,mode=max
- id: image
run: echo "image=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}" >> $GITHUB_OUTPUT
deploy-staging:
if: github.ref == 'refs/heads/develop' && github.event_name == 'push'
needs: build
runs-on: ubuntu-latest
environment:
name: staging
url: https://staging.example.com
steps:
- uses: actions/checkout@v3
- name: Deploy to staging
run: |
echo "Deploying image ${{ needs.build.outputs.image }} to staging"
# Add your deployment script here
- name: Run smoke tests
run: |
curl -f https://staging.example.com/health || exit 1
- name: Notify Slack
uses: 8398a7/action-slack@v3
if: always()
with:
status: ${{ job.status }}
text: 'Staging deployment ${{ job.status }}'
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK }}
deploy-production:
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
needs: build
runs-on: ubuntu-latest
environment:
name: production
url: https://example.com
steps:
- uses: actions/checkout@v3
- name: Deploy to production
run: |
echo "Deploying image ${{ needs.build.outputs.image }} to production"
# Add your deployment script here
- name: Run health check
run: |
curl -f https://example.com/health || exit 1
- name: Create release
uses: softprops/action-gh-release@v1
with:
tag_name: release-${{ github.run_number }}
generate_release_notes: true
- name: Notify Slack
uses: 8398a7/action-slack@v3
if: always()
with:
status: ${{ job.status }}
text: |
Production deployment ${{ job.status }}
Version: ${{ github.sha }}
Actor: ${{ github.actor }}
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK }}
You've mastered GitHub Actions and CI/CD concepts! You can now:
Continue practicing with real projects to solidify these skills. Remember, the best way to learn CI/CD is by implementing it in your own projects!
Congratulations! :tada: You've completed the GitHub Workshop. You now have professional-level skills in:
Apply these skills to build impressive projects for your portfolio!