We use cookies to enhance your experience on the site
CodeWorlds

CI/CD with GitHub Actions — Cursus Publicus

In the Roman Empire there existed the cursus publicus — a system of roads and postal stations ensuring rapid transport of messages and goods throughout the Empire. The modern equivalent is CI/CD (Continuous Integration / Continuous Deployment) — an automated system that builds, tests, and deploys our application.

What is CI/CD?

1const cicdDefinitions = {
2  CI: {
3    name: 'Continuous Integration',
4    description: 'Automatic building and testing of code after every push',
5    analogy: 'Inspection of legions before an expedition',
6  },
7  CD_Delivery: {
8    name: 'Continuous Delivery',
9    description: 'Automatic preparation for deployment (requires manual approval)',
10    analogy: 'Legion ready to march — awaiting the Emperor\'s order',
11  },
12  CD_Deployment: {
13    name: 'Continuous Deployment',
14    description: 'Automatic deployment to production after passing tests',
15    analogy: 'Legion marches automatically when ready',
16  },
17};

GitHub Actions — Basics

GitHub Actions is a CI/CD platform built into GitHub. We configure it with YAML files in the

.github/workflows/
directory.

Key Concepts

1const githubActionsConcepts = {
2  workflow: 'The entire CI/CD process (YAML file)',
3  trigger: 'Event that triggers the workflow (push, PR)',
4  job: 'Set of steps executed on a single runner',
5  step: 'Single action in a job (checkout, test, build)',
6  runner: 'Machine executing the job (ubuntu-latest)',
7  action: 'Reusable step (actions/checkout)',
8  secret: 'Secure variable (passwords, API keys)',
9};

CI Workflow for NestJS

Let us create a complete CI workflow:

1# .github/workflows/ci.yml
2name: Roman Imperium CI
3
4on:
5  push:
6    branches: [main, develop]
7  pull_request:
8    branches: [main]
9
10env:
11  NODE_VERSION: '20'
12
13jobs:
14  # ===========================================
15  # Job 1: Lint and Formatting
16  # ===========================================
17  lint:
18    name: Code Inspection
19    runs-on: ubuntu-latest
20    steps:
21      - name: Checkout code
22        uses: actions/checkout@v4
23
24      - name: Setup Node.js
25        uses: actions/setup-node@v4
26        with:
27          node-version: 20
28          cache: 'yarn'
29
30      - name: Install dependencies
31        run: yarn install --frozen-lockfile
32
33      - name: Linting
34        run: yarn lint
35
36      - name: Check formatting
37        run: yarn format:check
38
39  # ===========================================
40  # Job 2: Tests
41  # ===========================================
42  test:
43    name: Unit Tests
44    runs-on: ubuntu-latest
45    needs: lint
46    steps:
47      - name: Checkout code
48        uses: actions/checkout@v4
49
50      - name: Setup Node.js
51        uses: actions/setup-node@v4
52        with:
53          node-version: 20
54          cache: 'yarn'
55
56      - name: Install dependencies
57        run: yarn install --frozen-lockfile
58
59      - name: Run tests
60        run: yarn test --coverage
61
62      - name: Upload coverage
63        uses: actions/upload-artifact@v4
64        with:
65          name: coverage-report
66          path: coverage/
67
68  # ===========================================
69  # Job 3: Build
70  # ===========================================
71  build:
72    name: Build Application
73    runs-on: ubuntu-latest
74    needs: test
75    steps:
76      - name: Checkout code
77        uses: actions/checkout@v4
78
79      - name: Setup Node.js
80        uses: actions/setup-node@v4
81        with:
82          node-version: 20
83          cache: 'yarn'
84
85      - name: Install dependencies
86        run: yarn install --frozen-lockfile
87
88      - name: Build
89        run: yarn build
90
91      - name: Upload build
92        uses: actions/upload-artifact@v4
93        with:
94          name: dist
95          path: dist/
96
97  # ===========================================
98  # Job 4: Docker Build & Push
99  # ===========================================
100  docker:
101    name: Docker Image
102    runs-on: ubuntu-latest
103    needs: build
104    if: github.ref == 'refs/heads/main'
105    steps:
106      - name: Checkout code
107        uses: actions/checkout@v4
108
109      - name: Login to Docker Hub
110        uses: docker/login-action@v3
111        with:
112          username: imperator
113          password: roma_secret_token
114
115      - name: Build and push image
116        uses: docker/build-push-action@v5
117        with:
118          context: .
119          push: true
120          tags: |
121            imperator/roman-imperium:latest
122            imperator/roman-imperium:1.0.0

Secrets in GitHub Actions

We store passwords and API keys as GitHub Secrets:

1# Using secrets in workflow
2- name: Login to Docker Hub
3  uses: docker/login-action@v3
4  with:
5    username: imperator_username
6    password: docker_hub_token
7
8- name: Deploy
9  env:
10    DATABASE: db_connection_string
11    JWT_SECRET: jwt_secret_key

Secrets are configured in the repository settings: Settings > Secrets and variables > Actions.

Branching Strategy

1const branchStrategy = {
2  main: 'Production — automatic deploy after merge',
3  develop: 'Development — CI without deploy',
4  'feature/*': 'New features — CI on pull request',
5  'hotfix/*': 'Critical fixes — fast deploy to production',
6};

CI/CD is the cursus publicus of our Empire — it automates the transport of code from developer to production, ensuring that every change is tested and secure.

Go to CodeWorlds