This guide explains how to reuse and adapt the CI/CD pipeline from this repository for your own ROS 2 projects.
- Overview
- Prerequisites
- GitHub Secrets Configuration
- Workflow Components
- Adapting for Your Project
- Badge Setup
- Local Verification
- Troubleshooting
The CI/CD pipeline provides:
- ✅ Automated Docker image building on every push/PR
- 🧪 Comprehensive testing (C++ GoogleTest + Python pytest)
- 📊 Code coverage tracking with HTML reports and badges
- 📦 Docker Hub deployment from protected branches
- 🏷️ Dynamic badges for build status, tests, and coverage
Pipeline Flow:
Push/PR → Build Docker → Build Packages → Run Tests → Generate Coverage → Create Badges → Push to Docker Hub
Before setting up the CI/CD pipeline, ensure you have:
- A GitHub repository with ROS 2 packages
- A Dockerfile for your development environment
- Package tests (C++ with GoogleTest, Python with pytest)
- Docker Hub account for image hosting
- Docker Hub organization or personal namespace
- Admin access to repository settings
- Ability to create/manage GitHub Actions secrets
Navigate to your repository: Settings → Secrets and variables → Actions → New repository secret
| Secret Name | Description | Example |
|---|---|---|
DOCKERHUB_USERNAME |
Your Docker Hub username | myusername |
DOCKERHUB_TOKEN |
Docker Hub access token (create here) | dckr_pat_xxxxx... |
DOCKERHUB_ORG |
Docker Hub org or username for image push | myorganization |
Creating Docker Hub Access Token:
- Go to https://hub.docker.com/settings/security
- Click "New Access Token"
- Name it (e.g., "GitHub Actions")
- Select permissions: Read & Write
- Copy the token (shown only once!)
- Add to GitHub secrets as
DOCKERHUB_TOKEN
| Secret Name | Purpose | When Needed |
|---|---|---|
PRIVATE_ACTIONS_DEPLOY |
SSH key for private repos | If cloning private dependencies during build |
After adding secrets, verify they appear in: Settings → Secrets and variables → Actions → Repository secrets
The workflow file is located at .github/workflows/docker-build.yml.
- name: Build Docker image
run: cd ./Docker && ./build_container.sh- Builds your Docker image using your Dockerfile
- Verifies the image was created successfully
- name: Build & test workspace
run: |
colcon build --symlink-install \
--packages-select audio_stream_manager \
--cmake-args -DCMAKE_CXX_FLAGS='--coverage' ...
colcon test --event-handlers console_direct+ ...- Builds C++ packages with coverage flags
- Builds Python packages
- Runs all tests with verbose output
- name: Generate coverage reports
run: docker run --rm ... bash -c "/ci_cd_coverage.sh"- Generates HTML coverage reports
- Creates LCOV format for badge generation
- Converts to Cobertura XML for PR comments
- name: Create test badge
run: # Parses test results and creates badge JSON
- name: Create coverage badge
run: # Extracts coverage percentage and creates badge JSON- Parses test results for pass/fail counts
- Extracts coverage percentages from LCOV files
- Generates shields.io-compatible JSON files
- name: Publish badges to badges branch
run: # Commits badge JSONs to 'badges' branch- Creates orphan
badgesbranch - Stores badge JSON files per source branch
- Enables shields.io endpoint badges in README
- name: Push to Docker Hub
if: github.ref == 'refs/heads/jazzy-devel'
run: docker push ...- Tags image with org/repo:tag format
- Pushes to Docker Hub on jazzy-devel branch only
Please note: Same tag does not entail you are deleting old images in dockerhub pushed before: you should take care of deleting them in the panel from time to time or upgrade the github action workflow ;-)
# Copy the workflow file to your repo
cp .github/workflows/docker-build.yml YOUR_REPO/.github/workflows/Edit the workflow to match your branch strategy:
on:
push:
branches:
- develop # Your dev branch
pull_request:
branches:
- jazzy-develPackage names are now centralized! Edit the environment variables at the top of the workflow file:
env:
# Package Configuration - Define your ROS2 packages here
CPP_PACKAGES: "YOUR_CPP_PACKAGE"
PY_PACKAGES: "YOUR_PY_PACKAGE"For multiple packages (space-separated):
env:
CPP_PACKAGES: "pkg1 pkg2 pkg3"
PY_PACKAGES: "py_pkg1 py_pkg2"The workflow automatically uses these variables throughout all steps (build, test, coverage). No need to update package names in multiple places!
Similarly, update Docker/ci_cd_coverage.sh:
# PACKAGE CONFIGURATION
CPP_PACKAGES=("YOUR_CPP_PACKAGE")
PY_PACKAGES=("YOUR_PY_PACKAGE")Update Docker image verification:
- name: Verify image was built
run: |
if docker images | grep -q "YOUR_IMAGE_NAME"; then
echo "✅ Image YOUR_IMAGE_NAME successfully built"If your coverage script has a different name or location:
- name: Generate coverage reports
run: |
docker run --rm ... bash -c "/path/to/your_coverage_script.sh"Update badge publishing conditions to match your branches:
- name: Publish badges to badges branch
if: always() && (github.ref == 'refs/heads/jazzy-devel' || github.ref == 'refs/heads/develop')
run: |
if [ "${{ github.ref }}" == "refs/heads/jazzy-devel" ]; then
BRANCH_NAME="jazzy-devel"
elif [ "${{ github.ref }}" == "refs/heads/develop" ]; then
BRANCH_NAME="develop"
fiControl which branches can push to Docker Hub:
- name: Push to Docker Hub
if: github.ref == 'refs/heads/jazzy-devel' # Only from jazzy-devel
run: docker push ${DOCKERHUB_ORG}/your-image:${TAG}Options:
refs/heads/jazzy-devel- Only jazzy-devel branchrefs/heads/*- All branchesgithub.ref == 'refs/heads/jazzy-devel' || github.ref == 'refs/heads/develop'- Multiple branches
The workflow automatically creates an orphan badges branch on first run. No manual action needed!
Add to your README.md:
# Your Project Name
[](https://github.com/YOUR_ORG/YOUR_REPO/actions/workflows/docker-build.yml)
[](https://github.com/YOUR_ORG/YOUR_REPO/actions/workflows/docker-build.yml)
[](https://github.com/YOUR_ORG/YOUR_REPO/actions/workflows/docker-build.yml)Replace:
YOUR_ORG→ Your GitHub organization/usernameYOUR_REPO→ Your repository namejazzy-devel→ Your branch name (if different)
For multiple branches:
## Main Branch
[](...)
## Develop Branch
[](...)Build status only (no custom badges):
[](...)Copy the verification script to your container:
# In your Dockerfile
COPY Docker/quick_test_coverage.sh /quick_test_coverage.sh
RUN chmod +x /quick_test_coverage.sh# Inside your container
/quick_test_coverage.sh --cpp YOUR_CPP_PKG --python YOUR_PY_PKG
# Test all packages
/quick_test_coverage.sh --all
# Clean build before testing
/quick_test_coverage.sh --all --clean- ✅ Mirrors GitHub Actions workflow exactly
- 🧪 Runs full build → test → coverage cycle
- 📊 Generates coverage reports locally
- ⚡ Fast feedback before pushing
Cause: Badge JSON files not generated or wrong branch name
Solution:
- Check workflow ran successfully on your branch
- Verify badges branch exists:
git ls-remote origin badges - Check badge URL matches branch name in workflow
- Wait ~5 min for GitHub CDN to update
Cause: Missing or incorrect secrets
Solution:
# Verify secrets are set
# Settings → Secrets → Actions → Repository secrets
- DOCKERHUB_USERNAME ✓
- DOCKERHUB_TOKEN ✓
- DOCKERHUB_ORG ✓
# Test locally
docker login -u $DOCKERHUB_USERNAME
docker push ${DOCKERHUB_ORG}/image:tagCause: Coverage files not generated in expected location
Solution:
- Check "Generate coverage reports" step logs
- Verify
/ci_cd_coverage.shexists in container - Check Python package uses pytest-cov:
[tool.pytest.ini_options] addopts = "--cov=YOUR_PACKAGE --cov-report=lcov"
- Verify C++ built with coverage flags
Cause: Environment differences
Solution:
- Run tests in Docker container locally:
docker run --rm -v $(pwd):/workspace -w /workspace YOUR_IMAGE \ bash -c "colcon test --packages-select YOUR_PKG"
- Check for hardcoded paths
- Verify all dependencies in Dockerfile
- Check ROS environment sourced correctly
Cause: Branch filter mismatch
Solution:
Update on.push.branches and on.pull_request.branches:
on:
push:
branches:
- jazzy-devel
- your-branch-nameEnable workflow debugging:
- Repository Settings → Secrets → Actions
- Add repository secret:
ACTIONS_STEP_DEBUG=true - Re-run workflow for detailed logs
If issues persist:
- Check GitHub Actions logs
- Review workflow file
- Compare with this reference implementation
- Open an issue with:
- Workflow run link
- Error messages
- Your workflow customizations
- ✅ Never commit secrets to code
- ✅ Use GitHub Secrets for sensitive data
- ✅ Limit Docker Hub token to Read & Write only
- ✅ Rotate tokens periodically
- ⚡ Use
--symlink-installfor faster builds - ⚡ Cache Docker layers efficiently
- ⚡ Run only affected package tests when possible
- 📝 Document custom workflow changes
- 🏷️ Use semantic versioning for Docker tags
- 🔄 Keep workflow in sync with local scripts
- ✅ Test workflow changes in feature branches first
- GitHub Actions Documentation
- Docker Hub Access Tokens
- Shields.io Endpoint Badges
- ROS 2 Testing Guide
.github/workflows/docker-build.yml # Main workflow
Docker/quick_test_coverage.sh # Local verification script
Docker/ci_cd_coverage.sh # Coverage generation script
DOCKERHUB_USERNAME
DOCKERHUB_TOKEN
DOCKERHUB_ORG
- Package configuration (
env.CPP_PACKAGES,env.PY_PACKAGES) - Start here! - Branch names (
on.push.branches) - Docker image names
- Badge branch names
- Docker Hub push conditions
- Coverage script packages (
Docker/ci_cd_coverage.sh)
Build: https://github.com/ORG/REPO/actions/workflows/WORKFLOW.yml/badge.svg?branch=BRANCH
Tests: https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/ORG/REPO/badges/BRANCH/test-badge.json
Coverage: https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/ORG/REPO/badges/BRANCH/coverage-badge.json
Questions or issues? Feel free to open an issue or refer to the jazzy-devel README for testing details.