CI/CD is an engineering practice that completes code inspection, testing, building and deployment through automated processes.
1. CI and CD
| concept | full name | Main function |
|---|---|---|
| CI | Continuous Integration | Continuous integration, automated testing and validation of code |
| CD | Continuous Delivery | Continuous delivery ensures that code can be released at any time |
| CD | Continuous Deployment | Continuous 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:
| concept | meaning |
|---|---|
| Workflow | Complete automated process, defined using YAML |
| Event | Event triggering Workflow |
| Job | A set of tasks executed on the same Runner |
| Step | A single execution step within Job |
| Runner | The machine executing Job |
| Action | Reusable automated steps |
| Artifact | Workflow generated files or build results |
| Secret | Sensitive 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
| Field | function |
|---|---|
name | Workflow or Step name |
on | Trigger condition |
jobs | Definition Job |
runs-on | Select Runner |
steps | Define execution steps |
uses | Call existing Action |
run | Execute Shell command |
with | Pass the Action parameter |
env | Set environment variables |
if | conditional execution |
needs | Dependencies between Job |
permissions | GITHUB_TOKEN permissions |
concurrency | Control 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
| Tools | Check content |
|---|---|
| Ruff | Python Lint / Format |
| pytest | Django business and API test |
| drf-spectacular | OpenAPI Schema Verification |
| TypeScript | static type checking |
| ESLint | JavaScript / React code quality |
| Vite | Front-end production build |
| Docker Build | Can 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:
| concept | Main purpose |
|---|---|
| Cache | Speed up follow-up tasks |
| Artifact | Save or transfer workflow artifacts |
| Container Registry | Storing 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
- Use least privilege
GITHUB_TOKEN. - Secrets does not write to the repository or Dockerfile.
- Prevent untrusted PR from accessing production credentials.
- Third-party Actions is first fixed to complete Commit SHA to improve supply chain security.
- Do not arbitrarily use
pull_request_targetwith high privileges Token to execute untrusted code in PR. - 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.
13. Recommended practices for CI/CD
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:
| Classification | core content |
|---|---|
| infrastructure | Workflow、Event、Job、Step、Runner |
| YAML configuration | on、uses、run、needs、if、env |
| CI | Lint、Test、Type Check、Build |
| Performance | Cache、Artifact |
| safe | Secrets、GITHUB_TOKEN、Permissions |
| CD | Registry、Docker Build/Push、Deployment |
| Production guarantee | Environment、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:
- How to trigger Workflow based on Push / PR.
- How to write YAML containing multiple Job.
- How to perform pytest, TypeScript and Vite builds.
- How to use Cache, Secrets and environment variables.
- How to build Docker image and publish to GHCR.
- How to deploy and rollback on VPS.