Migrating from GitHub Actions to CNB
About 1800 wordsAbout 6 min
Introduction
Both GitHub Actions and CNB support defining automated build, test, publish, and deploy workflows via YAML configuration files. They share some similarities:
- Workflow configuration files are written in YAML and stored in the repository (CNB workflows are called Pipelines)
- Workflows include one or more jobs (CNB jobs correspond to Stages)
- Jobs include one or more steps or individual commands (CNB steps correspond to Jobs, each executing 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: Each workflow is a separate YAML file stored in the
.github/workflowsdirectory - CNB: All workflow configuration is stored in a file named .cnb.yml in the repository root (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 testCNB Format
main:
push: &build
- name: Node.js CI/CD
stages:
- name: build
image: ubuntu:latest
jobs:
- name: Run tests
script: npm test
pull_request: *buildTips
Note: & and * are YAML syntax for configuration reuse. See Advanced YAML Syntax for details.
Build Trigger Rules
GitHub Actions defines trigger rules via the on field. CNB defines them via 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
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
- GitHub Actions: A Runner is a virtual machine that executes jobs (e.g.,
macOS,Windows,Linux) - CNB: A Runner is a build node (by default, a node running Docker containers), currently supporting only Linux Docker containers. Enterprise Edition (private deployment) can connect to
macOS,Windows,Linuxmachines. See Build Node for details.
GitHub Actions uses the runs-on field to specify a Runner, while CNB uses the runner field to specify the build node architecture and resource 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
- GitHub Actions: Workflows run in a virtual machine; required software must be pre-installed or manually installed
- CNB: Workflows run in Docker containers; simply specify the image or Dockerfile. 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 testCNB 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 testRepository Code Preparation
- GitHub Actions: Uses
actions/checkoutto pull repository code - CNB: Repository code is pulled by default without explicit declaration. See syntax guide-Repository Configuration for details
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
- GitHub Actions: Uses the
iffield to set step execution conditions - CNB: Uses fields like
if,ifModify,ifNewBranchto set step execution conditions. See syntax guide-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
- GitHub Actions: Uses the
needsfield to define dependencies - CNB: Uses
cnb:awaitandcnb:resolvebuilt-in tasks to define dependencies and pass variables. See await-resolve Built-in Task 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
- GitHub Actions: Uses
envto specify variables andsecretsto reference secrets - CNB: Uses
envto specify variables andimportsto reference external repository 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
DEPLOYMENT_ENV: "production"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
- GitHub Actions: Supports matrix strategy to automatically create multiple job runs based on variable combinations
- CNB: Does not natively support matrix strategy, but you can simulate it using YAML anchors. See Advanced YAML Syntax for details
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 testCNB Format
main:
push:
- docker:
image: node:14
stages: &BVT-testing-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-testing-stagesCache
- GitHub Actions: Uses caching strategies to temporarily store intermediate build artifacts for subsequent builds
- CNB: Use
pipeline.runner.volumesfor local node cache, ordocker:cachebuilt-in task for remote Docker image cache. 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 buildCNB 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 buildWhere 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_modulesArtifacts
- GitHub Actions: Uses
actions/upload-artifactto upload artifacts - CNB: Uses
cnbcool/attachments:latestplugin 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/artifactCNB Format
main:
push:
- docker:
image: ubuntu:latest
stages:
- name: Upload a Build Artifact
image: cnbcool/attachments:latest
settings:
attachments:
- /path/to/artifactDatabase and Service Containers
- GitHub Actions: Uses the
servicesfield to orchestrate database and service containers - CNB: Declare a
dockerservice and executedockerordocker composecommands directly. 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: 5432CNB 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