Migrating from GitHub Actions to CNB
About 1939 wordsAbout 6 min
Introduction
Both GitHub Actions and CNB allow you to create workflows that automatically build, test, publish, release, and deploy code. CNB and GitHub Actions workflow configurations have some similarities:
- Workflow configuration files are written in YAML and stored in the repository. (In CNB, workflows are called Pipelines)
- Workflows include one or more jobs. (In CNB, jobs correspond to Stages)
- Jobs include one or more steps or individual commands. (In CNB, steps correspond to Jobs, and each job can execute a series of commands or plugins)
This guide will focus on the differences between the two to help you migrate GitHub Actions to CNB.
Workflow Configuration
GitHub Actions configures each workflow as a separate YAML file stored in the
.github/workflows
directory.CNB stores all workflow configuration files in a file named .cnb.yml in the repository root directory (you can also import other configuration files using the include keyword).
The following are the main syntax differences between the two:
GitHub Actions Format
name: Node.js CI/CD
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Run tests
run: npm test
CNB Format
main:
push: &build
- name: Node.js CI/CD
stages:
- name: build
image: ubuntu:latest
jobs:
- name: Run tests
script: npm test
pull_request: *build
Tips
Note: &
and *
are YAML syntax used for configuration reuse. See Advanced YAML Syntax for details.
Build Trigger Rules
In GitHub Actions, workflow trigger rules are defined by specifying the on
field. In CNB, you can define the rules for triggering build pipelines by writing trigger branches
and trigger events
. See Build Trigger Rules for details.
GitHub Actions Format
name: Node.js CI/CD
on:
push:
branches: [main]
jobs:
build:
steps:
- name: echo
run: echo "do some job"
CNB Format
# .cnb.yml
main: # Trigger branch
push: # Trigger event, corresponds to a build that can contain multiple Pipelines. Can be either an array or an object.
- stages: # Pipeline 1
- name: echo
script: echo "do some job"
Runners
- In GitHub Actions, a Runner refers to a virtual machine that executes jobs, such as
macOS
,Windows
,Linux
, etc. - In CNB, a Runner refers to a build node (by default, a node running docker containers). Currently, the official hosted build nodes provided by CNB only support Linux system docker containers as Runners. The enterprise version (private deployment) of CNB can connect to different machines (
macOS
,Windows
,Linux
). See Build Cluster for details.
GitHub Actions uses the runs-on
field to specify the Runner, while CNB uses the runner
field to specify the build node architecture (amd64
or arm64
, etc.) and cpu
and memory
configuration.
GitHub Actions Format
linux_job:
runs-on: ubuntu-latest
steps:
- run: echo "Hello, $USER!"
CNB Format
main:
push:
- runner:
tags: cnb:arch:amd64 # Specify execution on amd64 architecture build cluster
cpus: 16 # Specify allocated CPU cores as 16 (memory is automatically allocated as cores*2 GiB)
docker:
image: ubuntu:latest # Specify using ubuntu:latest image as pipeline runtime environment
stages:
- name: echo
script: echo "Hello, $USER!"
Build Environment
- In GitHub Actions, workflows run in a virtual machine environment, so any software needed in the build environment must either be pre-installed on the virtual machine or manually installed.
- In CNB, workflows run in one or more docker containers where you can specify docker images. During the build process, you only need to specify the required image or Dockerfile in the configuration file to complete the build environment setup. See Build Environment for details.
GitHub Actions Format
linux_job:
runs-on: ubuntu-latest
steps:
- name: Use Node.js 21
uses: actions/setup-node@v4
with:
node-version: 21
- run: npm ci
- run: npm run build --if-present
- run: npm test
CNB Format
main:
push:
- docker:
image: node:21 # Use node:21 as pipeline runtime environment
stages:
- name: npm ci
script: npm ci
- name: build
script: npm run build --if-present
- name: npm test
script: npm test
Repository Code Preparation
- In GitHub Actions, workflows use
actions/checkout
to pull repository code - In CNB, workflows pull repository code by default without explicit declaration. See pipeline grammar-Repository Configuration for detailed configuration.
GitHub Actions Format
linux_job:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: ls -al .
CNB Format
main:
push:
- stages:
- name: ls
script: ls -al .
Conditional Trigger
- In GitHub Actions, workflows use the
if
field to set step execution conditions. - In CNB, workflows use fields like
if
,ifModify
,ifNewBranch
to set step execution conditions. See pipeline grammar-if for details.
GitHub Actions Format
jobs:
deploy_prod:
if: contains( github.ref, 'master')
runs-on: ubuntu-latest
steps:
- run: echo "Deploy to production server"
CNB Format
$:
push:
- if: echo "$CNB_BRANCH" | grep -q 'master'
stages:
- name: Deploy to production server
script: echo "Deploy to production server"
Job Dependencies
- In GitHub Actions, workflows use the
needs
field to define workflow dependencies. - In CNB, workflows use two Built-in Plugin
cnb:await
andcnb:resolve
to define workflow dependencies and pass variables. See await-resolve Built-in Plugin for details.
GitHub Actions Format
jobs:
build_a:
runs-on: ubuntu-latest
steps:
- run: echo "This job will be run first."
build_b:
runs-on: ubuntu-latest
steps:
- run: echo "This job will be run first, in parallel with build_a"
test_ab:
runs-on: ubuntu-latest
needs: [build_a, build_b]
steps:
- run: echo "This job will run after build_a and build_b have finished"
deploy_ab:
runs-on: ubuntu-latest
needs: [test_ab]
steps:
- run: echo "This job will run after test_ab is complete"
CNB Format
main:
push:
- name: build_a
docker:
image: ubuntu:latest
stages:
- name: build_a
script: echo "This job will be run first."
- name: resolve for test_ab
type: cnb:resolve
options:
key: build_a
- name: build_b
docker:
image: ubuntu:latest
stages:
- name: build_b
script: echo "This job will be run first, in parallel with build_a"
- name: resolve for test_ab
type: cnb:resolve
options:
key: build_b
- name: test_ab
docker:
image: ubuntu:latest
stages:
- name: wait for build_a
type: cnb:await
options:
key: build_a
- name: wait for build_b
type: cnb:await
options:
key: build_b
- name: build_a
script: echo "This job will run after build_a and build_b have finished"
- name: resolve for test_ab
type: cnb:resolve
options:
key: test_ab
- name: deploy_ab
docker:
image: ubuntu:latest
stages:
- name: wait for test_ab
type: cnb:await
options:
key: test_ab
- name: deploy_ab
script: echo "This job will run after test_ab is complete"
Variables and Secrets
- In GitHub Actions, workflows use the
env
field to specify variables and usesecrets
variables to reference secrets. - In CNB, workflows use the
env
field to specify variables and use theimports
field to reference external repositories or secret repository files as environment variables. See Reference Variables and Secret Repository for details.
GitHub Actions Format
jobs:
deploy_prod:
runs-on: ubuntu-latest
env:
DEPLOYMENT_ENV: production
steps:
- run: echo "This job will deploy to the $DEPLOYMENT_ENV server"
CNB Format
#env.yml
DEPLOYMENT_ENV: "production"
#.cnb.yml
main:
push:
- imports:
- cnb.cool/<your-repo-slug>/-/blob/main/xxx/env.yml
stages:
- name: Deploy to the $DEPLOYMENT_ENV server
script: echo "This job will deploy to the $DEPLOYMENT_ENV server"
Matrix Strategy
- In GitHub Actions, workflows can use matrix strategy to automatically create multiple job runs based on variable combinations using variables in a single job definition.
- In CNB, workflows do not support this strategy. However, you can use YAML's anchor feature to simulate matrix strategy. See Advanced YAML Syntax for reference.
GitHub Actions Format
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [15.x, 16.x]
steps:
- uses: actions/checkout@v3
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
- name: Install node modules
run: npm install
- name: Run a one-line script
run: npm run build
- name: Run a multi-line script
run: npm run test
CNB Format
main:
push:
- docker:
image: node:14
stages: &BVT-tesing-stages
- name: Install node modules
script: npm install
- name: Run a one-line script
script: npm run build
- name: Run a multi-line script
script: npm run test
- docker:
image: node:16
stages: *BVT-tesing-stages
Cache
- In GitHub Actions, workflows use caching strategies to temporarily store intermediate artifacts generated during the build process in a cache area for use in subsequent builds.
- In CNB, workflows use
pipeline.runner.volumes
to declarelocal node cache
or use thedocker:cache
Built-in Plugin to declareremote docker image cache
to store dependency caches or intermediate artifacts during the build process. See Pipeline Cache for details.
GitHub Actions Format
jobs:
deploy_prod:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Cache node modules
uses: actions/cache@v2
with:
path: ~/.npm
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-node-
- name: Build project
run: npm ci && npm run build
CNB Format
# Use `pipeline.runner.volumes` to declare `local node cache`
main:
push:
- docker:
image: node:14
volumes:
- /root/.npm # Declare local cache directory
stages:
- name: Build project
script: npm ci && npm run build
# Use `docker:cache` Built-in Plugin to declare `remote docker image cache`
main:
push:
- docker:
image: node:14
stages:
- name: build cache image
type: docker:cache
options:
dockerfile: cache.dockerfile
# by supports the following two forms: array, string
by:
- package.json
- package-lock.json
# versionBy: package-lock.json
versionBy:
- package-lock.json
exports:
name: DOCKER_CACHE_IMAGE_NAME
- name: use cache
image: $DOCKER_CACHE_IMAGE_NAME
# Copy files from cache for use
commands:
- cp -r "$NODE_PATH" ./node_modules
- name: Build project
script: npm ci && npm run build
Where cache.dockerfile is a Dockerfile used to build cache images, example:
# Choose a Base image
FROM node:14
# Set working directory
WORKDIR /space
# COPY the file list from by
COPY . .
# Install dependencies based on the COPY files
RUN npm ci
# Set required environment variables
ENV NODE_PATH=/space/node_modules
Artifacts
- In Github Actions, workflows use the
actions/upload-artifact
action to upload artifacts - In CNB, workflows use the
cnbcool/attachments:latest
plugin to upload artifacts. See Attachment Plugin for details.
GitHub Actions Format
jobs:
deploy_prod:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Upload a Build Artifact
uses: actions/upload-artifact@v2
with:
name: my-artifact
path: /path/to/artifact
CNB Format
main:
push:
- docker:
image: ubuntu:latest
stages:
- name: Upload a Build Artifact
image: cnbcool/attachments:latest
settings:
attachments:
- /path/to/artifact
Database and Service Containers
- In GitHub Actions, workflows use the
services
field to orchestrate database and service containers. - In CNB, workflows can declare
docker
services and directly executedocker
ordocker compose
commands in jobs to start database and service containers. See docker service for details.
GitHub Actions Format
jobs:
container-job:
runs-on: ubuntu-latest
container: node:20-bookworm-slim
services:
postgres:
image: postgres
env:
POSTGRES_PASSWORD: postgres
steps:
- name: Check out repository code
uses: actions/checkout@v4
# Performs a clean installation of all dependencies
# in the `package.json` file
- name: Install dependencies
run: npm ci
- name: Connect to PostgreSQL
# Runs a script that creates a PostgreSQL client,
# populates the client with data, and retrieves data
run: node client.js
env:
# The hostname used to communicate with the
# PostgreSQL service container
POSTGRES_HOST: postgres
# The default PostgreSQL port
POSTGRES_PORT: 5432
CNB Format
# Directly use `docker compose` commands in jobs to orchestrate database and service containers
main:
push:
- docker:
image: ubuntu:latest
services:
- docker # After declaration, the pipeline container will automatically start dind service and actively inject docker cli tools
stages:
- name: Start database and service
script: docker-compose up -d
- name: Runs a script that creates a PostgreSQL client, populates the client with data, and retrieves data
script: node client.js
env:
POSTGRES_HOST: 127.0.0.1
POSTGRES_PORT: 5432