← ProgrammingBack to top

CI/CD:GitHub Actions

CI/CD is an engineering practice that completes code inspection, testing, building and deployment through automated processes.

1. CI and CD

conceptfull nameMain function
CIContinuous IntegrationContinuous integration, automated testing and validation of code
CDContinuous DeliveryContinuous delivery ensures that code can be released at any time
CDContinuous DeploymentContinuous deployment, automatically released to the production environment after verification

Typical process:

Developer
    ↓
Git Push / Pull Request
    ↓
GitHub Actions
    │
    ├── Lint / Type Check
    ├── Unit / Integration Tests
    └── Build
           ↓
        CI Passed
           ↓
      Docker Image
           ↓
     Image Registry
           ↓
    Deployment Server
           ↓
    Running Application

CI is responsible for ensuring code quality, and CD is responsible for delivery and deployment.

CI does not require automatic deployment; you can implement CI first and then gradually add CD.

2. GitHub Actions core model

GitHub Actions is an automated workflow platform provided by GitHub.

GitHub Event
     ↓
Workflow
     ↓
Jobs
     ↓
Runner
     ↓
Steps
     ├── uses: Action
     └── run: Shell Command

Core concepts:

conceptmeaning
WorkflowComplete automated process, defined using YAML
EventEvent triggering Workflow
JobA set of tasks executed on the same Runner
StepA single execution step within Job
RunnerThe machine executing Job
ActionReusable automated steps
ArtifactWorkflow generated files or build results
SecretSensitive configuration for encryption management

Workflow files are saved at:

.github/
└── workflows/
    ├── ci.yml
    └── deploy.yml

A repository can define multiple Workflow.

3. Basic Workflow

name: CI

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

permissions:
  contents: read

jobs:
  test:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Setup Python
        uses: actions/setup-python@v5
        with:
          python-version: "3.12"

      - name: Install dependencies
        run: pip install -r requirements.txt

      - name: Run tests
        run: pytest

Execution process:

Push to main / PR to main
             ↓
      Start Workflow
             ↓
   Create Ubuntu Runner
             ↓
      Checkout Code
             ↓
      Install Python
             ↓
   Install Dependencies
             ↓
          pytest
             ↓
       Pass / Fail

It is assumed here that the project dependencies and test environment have been configured correctly. If Django uses PostgreSQL, a test database service also needs to be provided.

Important YAML fields

Fieldfunction
nameWorkflow or Step name
onTrigger condition
jobsDefinition Job
runs-onSelect Runner
stepsDefine execution steps
usesCall existing Action
runExecute Shell command
withPass the Action parameter
envSet environment variables
ifconditional execution
needsDependencies between Job
permissionsGITHUB_TOKEN permissions
concurrencyControl concurrent execution

The difference between uses and run:

- uses: actions/checkout@v4

Call the already implemented Action.

- run: pytest

Execute the command directly.

4. Event: Workflow trigger condition

Push

on:
  push:
    branches: [main]

Fires every time a tag is pushed to main.

Pull Request

on:
  pull_request:
    branches: [main]

Triggered when a PR whose target is main is created or updated.

manual trigger

on:
  workflow_dispatch:

It can be run manually on the GitHub Actions page.

Tag / Release

on:
  push:
    tags:
      - "v*"

For example, publish when v1.2.0 is pushed.

scheduled tasks

on:
  schedule:
    - cron: "0 2 * * *"

Using UTC time, it is scheduled to fire at 02:00 every day. Timing Workflow may be delayed and is not guaranteed to be strictly on time.

5. Job, Step and Runner

A Workflow can contain multiple Job:

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - run: echo "Testing"

  build:
    runs-on: ubuntu-latest
    steps:
      - run: echo "Building"

By default, different Job can be executed in parallel.

Steps within the same Job are usually executed sequentially.

needs: Job dependency

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - run: pytest

  build:
    needs: test
    runs-on: ubuntu-latest
    steps:
      - run: docker build -t myapp .

Indicates that test must complete first, and usually needs to succeed, before build can be executed.

Test
  ↓
Build
  ↓
Deploy

Job isolation

Job on each GitHub-hosted Runner usually runs in a separate environment.

Therefore, unlike Job, local files are not automatically shared.

If you need to deliver build products, you can use Artifact or a mirror repository.

6. Actual CI: Django + React

For front-end and back-end separation projects, Backend and Frontend Job can be defined respectively.

name: CI

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

permissions:
  contents: read

jobs:
  backend:
    runs-on: ubuntu-latest

    defaults:
      run:
        working-directory: backend

    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-python@v5
        with:
          python-version: "3.12"
          cache: pip
          cache-dependency-path: backend/requirements.txt

      - run: pip install -r requirements.txt

      - name: Run tests
        run: pytest

      - name: Validate OpenAPI
        run: >
          python manage.py spectacular
          --file openapi.yaml
          --validate
          --fail-on-warn

  frontend:
    runs-on: ubuntu-latest

    defaults:
      run:
        working-directory: frontend

    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-node@v4
        with:
          node-version: "24"
          cache: pnpm
          cache-dependency-path: frontend/pnpm-lock.yaml

      - run: corepack enable

      - run: pnpm install --frozen-lockfile

      - name: Type Check
        run: pnpm exec tsc -b

      - name: Build
        run: pnpm build

This is a simplified example. Actual use needs to be adjusted according to the directory structure, database and environment variables. Especially when pytest depends on PostgreSQL, services or other test database configurations need to be added.

In addition, the front-end Orval generation code should be consistent with the latest OpenAPI Schema, and generation result checks can be added in CI.

Common CI checks

ToolsCheck content
RuffPython Lint / Format
pytestDjango business and API test
drf-spectacularOpenAPI Schema Verification
TypeScriptstatic type checking
ESLintJavaScript / React code quality
ViteFront-end production build
Docker BuildCan the container image be built successfully?

It is not necessary to enable all checks initially. It is recommended to ensure that pytest, TypeScript and production builds can pass stably.

7. Cache and Artifact

Cache: speed up the build

For example:

- uses: actions/setup-node@v4
  with:
    node-version: "24"
    cache: pnpm

It can cache dependencies downloaded by the package manager to speed up subsequent Workflow.

Also available:

- uses: actions/cache@v4
  with:
    path: ~/.cache/pip
    key: pip-${{ runner.os }}-${{ hashFiles('requirements.txt') }}

Note: Cache is used for performance optimization and should not rely on the existence of cache to ensure correct build.

Artifact: Save build results

For example:

- uses: actions/upload-artifact@v4
  with:
    name: frontend-dist
    path: frontend/dist

Can be saved:

  • Front-end build results.
  • Test report.
  • Coverage report.
  • Other build products.

Difference:

conceptMain purpose
CacheSpeed up follow-up tasks
ArtifactSave or transfer workflow artifacts
Container RegistryStoring and distributing Docker images

8. Secrets, Variables and Contexts

Secrets

In the GitHub repository:

Settings
  ↓
Secrets and variables
  ↓
Actions

Can be configured:

DEPLOY_HOST
DEPLOY_USER
SSH_PRIVATE_KEY

Workflow reads:

env:
  DEPLOY_HOST: ${{ secrets.DEPLOY_HOST }}

Secrets is suitable for passwords, Token, SSH Private Key, etc.

Variables

Non-sensitive configurations can use Variables:

env:
  APP_ENV: ${{ vars.APP_ENV }}

Contexts

GitHub Actions provides built-in context:

${{ github.ref }}
${{ github.sha }}
${{ github.actor }}
${{ github.repository }}
${{ github.event_name }}
${{ secrets.MY_SECRET }}
${{ vars.APP_ENV }}

${{ ... }} is the expression syntax of GitHub Actions, not the variable syntax of Bash.

And:

$APP_ENV

Usually the Shell environment variable.

GITHUB_TOKEN

GitHub provides Workflow with temporary GITHUB_TOKEN for accessing GitHub API and some repository resources.

It is recommended to follow the principle of least privilege:

permissions:
  contents: read

If you need to push the GHCR image:

permissions:
  contents: read
  packages: write

9. CD: Docker mirror release

For Docker Compose deployment, common CD process:

Merge to main
      ↓
CI Pass
      ↓
Docker Build
      ↓
Push Image to GHCR
      ↓
VPS Pull Image
      ↓
Docker Compose Up
      ↓
Health Check

GHCR

GitHub Container Registry:

ghcr.io/username/sorivel-backend:1.2.0

You can use GitHub Actions to build and push the Docker image.

Example:

name: Publish Backend

on:
  push:
    tags:
      - "v*"

permissions:
  contents: read
  packages: write

jobs:
  publish:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v4

      - uses: docker/login-action@v3
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - uses: docker/build-push-action@v6
        with:
          context: ./backend
          push: true
          tags: ghcr.io/your-org/sorivel-backend:${{ github.ref_name }}

Here you need to replace your-org and ensure that the image naming and publishing permissions are correct. The release of Workflow should actually be associated with the success condition of CI to avoid code that fails the test from being released.

VPS deployment

Server Compose uses mirroring:

services:
  backend:
    image: ghcr.io/your-org/sorivel-backend:v1.2.0
    restart: unless-stopped

Update:

docker compose pull backend
docker compose up -d backend

This way the image build is placed on GitHub Actions instead of rebuilding on VPS each time.

More complex automated deployments can be achieved through SSH, Self-hosted Runner, or other deployment systems, but executing untrusted PR code directly on production servers is not recommended.

10. Environment and deployment protection

GitHub Actions supports Deployment Environment.

For example:

jobs:
  deploy:
    runs-on: ubuntu-latest
    environment: production

Can be configured for Production environment:

  • Environmental grade Secrets.
  • Environmental grade Variables.
  • Approval rules.
  • branch or Tag limit.

Some protection features depend on repository type and GitHub Plan.

For production deployments, it is recommended to distinguish:

Development
Staging(Optional)
Production

Even if you only have one VPS, you can use Production Environment to manage deployment credentials and publishing permissions.

11. Concurrency: Prevent concurrent deployment

concurrency:
  group: production-deploy
  cancel-in-progress: false

Make sure there are not multiple deployments running simultaneously in the same Concurrency Group.

cancel-in-progress: false means not to actively cancel already running deployments, but the newly triggered Pending Run may still replace the old Pending Run; it does not provide a strict FIFO deployment queue.

For CI, you can use:

concurrency:
  group: ci-${{ github.ref }}
  cancel-in-progress: true

New submissions can cancel the old unfinished CI on the same branch, reducing resource waste.

12. CI/CD Security and Rollback

safety principles

  1. Use least privilege GITHUB_TOKEN.
  2. Secrets does not write to the repository or Dockerfile.
  3. Prevent untrusted PR from accessing production credentials.
  4. Third-party Actions is first fixed to complete Commit SHA to improve supply chain security.
  5. Do not arbitrarily use pull_request_target with high privileges Token to execute untrusted code in PR.
  6. Perform necessary testing and build checks before deployment.

Rollback (Rollback)

Don't rely solely on the latest tag.

It is recommended to use the explicit version:

sorivel-backend:v1.2.0
sorivel-backend:v1.2.1

If there is a problem with the new version:

Current: v1.2.1
      ↓
Rollback
      ↓
Previous: v1.2.0

Modify the image version and redeploy:

docker compose pull backend
docker compose up -d backend

Also note: application mirror rollback does not equal database rollback. If your deployment also performs destructive Migration, database compatibility and recovery strategies must be designed separately.

For a single VPS + Docker Compose project, three phases can be adopted.

Phase 1: CI

PR / Push
   ↓
pytest
   ↓
Type Check
   ↓
Build

Goal: Automatically check whether code is reliable.

Phase 2: Automatically publish images

Main / Release Tag
       ↓
CI Passed
       ↓
Docker Build
       ↓
Push to GHCR

Goal: Build a deployable and trackable version of the image.

Phase Three: Automatic Deployment

Approved Release
       ↓
Deploy to VPS
       ↓
docker compose pull
       ↓
docker compose up -d
       ↓
Health Check

Goal: Make deployments repeatable, verifiable, and rollable.

Summary

GitHub Actions The most important concepts:

Classificationcore content
infrastructureWorkflow、Event、Job、Step、Runner
YAML configurationon、uses、run、needs、if、env
CILint、Test、Type Check、Build
PerformanceCache、Artifact
safeSecrets、GITHUB_TOKEN、Permissions
CDRegistry、Docker Build/Push、Deployment
Production guaranteeEnvironment、Concurrency、Health Check、Rollback

The core function of GitHub Actions is to automatically execute predefined workflows in Runner when a specific event occurs.

For daily development, give priority to mastering:

  1. How to trigger Workflow based on Push / PR.
  2. How to write YAML containing multiple Job.
  3. How to perform pytest, TypeScript and Vite builds.
  4. How to use Cache, Secrets and environment variables.
  5. How to build Docker image and publish to GHCR.
  6. How to deploy and rollback on VPS.

Reference documentation