Docker container for continuous integration testing of baremetal microcontroller devices. This container includes self-hosted CI runners (GitHub Actions or GitLab CI) with Segger J-Link support for flashing and debugging MCU hardware, plus PEAK CAN support via SocketCAN for CAN bus testing.
This Docker image supports both GitHub Actions and GitLab CI. Select your platform in the .env file:
# In .env file
RUNNER_PLATFORM=github # for GitHub Actions
# or
RUNNER_PLATFORM=gitlab # for GitLab CIThen simply run:
docker-compose up -dFor detailed GitLab-specific documentation, see README.gitlab.md.
- GitHub Actions Self-Hosted Runner: Runs CI/CD workflows directly on hardware
- GitLab Runner Support: Alternative to GitHub Actions for GitLab CI/CD
- Segger J-Link Support: Full J-Link driver installation for MCU programming and debugging
- PEAK CAN Support: SocketCAN interface for CAN bus communication and testing
- USB Device Passthrough: All USB devices are automatically passed through to the container
- Automatic Runner Registration: Configures and registers with GitHub/GitLab on startup
- Persistent Workspace: Runner work directory is preserved across container restarts
- Docker Engine (20.10+)
- Docker Compose (2.0+)
- GitHub Personal Access Token with
repoandadmin:org(if using organization) permissions - Segger J-Link device connected via USB (optional)
- PEAK CAN USB device connected (optional, for CAN bus testing)
- Linux host required for USB device passthrough
Linux:
- Direct USB device passthrough supported
- Requires privileged container mode
- For PEAK CAN:
peak_usbkernel module must be available on host
git clone https://github.com/RoboticsHardwareSolutions/baremetal-ci-docker.git
cd baremetal-ci-dockerCopy the example environment file and fill in your details:
cp .env.example .envEdit .env and set:
For GitHub Actions (RUNNER_PLATFORM=github):
# Platform selection
RUNNER_PLATFORM=github
# GitHub token with repo and admin:org permissions
GITHUB_TOKEN=ghp_your_token_here
# For organization-level runner (recommended for multiple repos)
GITHUB_OWNER=your-org-name
# OR for repository-specific runner
GITHUB_REPOSITORY=owner/repo-name
# Runner configuration
RUNNER_NAME=baremetal-ci-runner
RUNNER_LABELS=baremetal,jlink,mcu
RUNNER_GROUP=defaultFor GitLab CI (RUNNER_PLATFORM=gitlab):
See README.gitlab.md for GitLab-specific configuration.
docker-compose up -dThe container will automatically start the appropriate runner based on your RUNNER_PLATFORM setting.
Check the logs to ensure the runner started successfully:
docker-compose logs -fYou should see messages indicating:
- J-Link device detection
- Runner configuration
- Connection to GitHub
Visit your repository or organization Settings → Actions → Runners to see the registered runner.
Once the runner is online, you can use it in your workflows by specifying the runner labels:
name: MCU Firmware CI
on: [push, pull_request]
jobs:
build-and-flash:
runs-on: [self-hosted, baremetal, jlink, mcu]
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Build firmware
run: |
# Your build commands here
make clean
make all
- name: Check J-Link connection
run: |
JLinkExe -CommanderScript <<EOF
connect
exit
EOF
- name: Flash firmware
run: |
# Flash using J-Link
JLinkExe -device STM32F407VG -if SWD -speed 4000 -autoconnect 1 -CommanderScript <<EOF
r
h
loadfile build/firmware.hex
r
g
exit
EOF
- name: Run tests
run: |
# Your test commands
python test/run_tests.pyThe container includes all Segger J-Link command-line tools:
JLinkExe- J-Link CommanderJLinkGDBServer- GDB Server for debuggingJLinkSWOViewer- SWO Viewer for trace dataJLinkRTTClient- RTT Client for real-time transfer
docker exec baremetal-ci-runner JLinkExe -CommanderScript /dev/nulldocker exec baremetal-ci-runner JLinkExe -device <MCU_NAME> -if SWD -speed 4000 -autoconnect 1 -CommanderScript flash.jlinkThe container automatically configures PEAK CAN USB devices as SocketCAN interfaces on startup. This provides a standard Linux CAN interface for testing.
When the container starts, it will:
- Detect PEAK CAN USB devices (vendor ID 0x0c72)
- Load required kernel modules (
peak_usb,can,can_raw) - Configure the CAN interface with the baudrate from
PCAN_BAUDRATE(default: 125000 bps) - Bring up the
can0interface automatically
Example GitHub Actions workflow:
name: CAN Bus Testing
on: [push, pull_request]
jobs:
can-test:
runs-on: [self-hosted, baremetal, socketcan]
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Check CAN interface
run: |
ip -details link show can0
# Should show: bitrate 125000 sample-point 0.875
- name: Monitor CAN traffic
run: |
# Start monitoring in background
candump can0 > can_traffic.log &
CANDUMP_PID=$!
# Your CAN test commands here
sleep 5
# Stop monitoring
kill $CANDUMP_PID
cat can_traffic.log
- name: Send CAN message
run: |
# Send a CAN message (ID 0x123, 8 bytes of data)
cansend can0 123#1122334455667788
- name: Python CAN example
run: |
python3 << 'EOF'
import can
# Open CAN bus
bus = can.Bus(interface='socketcan', channel='can0')
# Send message
msg = can.Message(arbitration_id=0x123,
data=[0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88],
is_extended_id=False)
bus.send(msg)
print(f"Sent: {msg}")
# Receive message (with timeout)
msg = bus.recv(timeout=1.0)
if msg:
print(f"Received: {msg}")
bus.shutdown()
EOFExample GitLab CI pipeline:
test_can_communication:
tags:
- baremetal
- socketcan
script:
- ip -details link show can0
- candump can0 &
- sleep 1
- cansend can0 123#DEADBEEF
- pkill candumpYou can change the CAN baudrate during CI execution:
# Stop the interface
sudo ip link set can0 down
# Change baudrate (e.g., to 500 kbit/s)
sudo ip link set can0 type can bitrate 500000
# Bring interface back up
sudo ip link set can0 up
# Verify
ip -details link show can0Common baudrates:
- 125000 (125 kbit/s) - default
- 250000 (250 kbit/s)
- 500000 (500 kbit/s)
- 1000000 (1 Mbit/s)
The container includes can-utils package with:
candump- Display CAN messagescansend- Send single CAN messagescangen- Generate random CAN trafficcansequence- Send and check sequence of CAN messagescansniffer- Interactive CAN traffic analyzercanplayer- Replay CAN log filescanlogger- Log CAN traffic to file
CAN interface not appearing:
-
Check if PEAK CAN device is connected:
docker exec baremetal-ci-runner lsusb | grep 0c72
-
Load
peak_usbmodule on host:sudo modprobe peak_usb
-
Check container logs:
docker-compose logs | grep -i can
Permission denied errors:
The container runs with limited sudo access. Only these commands are allowed:
sudo ip- for managing CAN interfacessudo modprobe- for loading kernel modulessudo chown- for file permissions
All configuration is done in the .env file. Copy .env.example to .env and configure:
| Variable | Required | Description | Default |
|---|---|---|---|
RUNNER_PLATFORM |
Yes | CI platform to use: github or gitlab |
github |
| Variable | Required | Description | Example |
|---|---|---|---|
GITHUB_TOKEN |
Conditional* | GitHub Personal Access Token with repo and admin:org permissions. Generate at GitHub Settings |
ghp_xxxxxxxxxxxx |
GITHUB_REGISTRATION_TOKEN |
Conditional* | Direct registration token (expires in 1 hour). Get from organization or repo runner settings | ALNAPSP4xxxx |
GITHUB_OWNER |
Conditional** | Organization name for org-level runner | your-org-name |
GITHUB_REPOSITORY |
Conditional** | Repository in format owner/repo for repo-level runner |
owner/repo-name |
RUNNER_NAME |
No | Runner name shown in GitHub | baremetal-ci-runner |
RUNNER_LABELS |
No | Comma-separated labels for workflow targeting | baremetal,jlink,mcu |
RUNNER_GROUP |
No | Runner group (org runners only) | default |
*Either GITHUB_TOKEN or GITHUB_REGISTRATION_TOKEN is required
**Either GITHUB_OWNER (org-level) or GITHUB_REPOSITORY (repo-level) is required
| Variable | Required | Description | Example |
|---|---|---|---|
GITLAB_URL |
No | GitLab instance URL | https://gitlab.com |
GITLAB_REGISTRATION_TOKEN |
Yes | Registration token from project/group/instance settings | GR1348941xxxx |
RUNNER_NAME |
No | Runner name shown in GitLab | baremetal-ci-runner |
RUNNER_TAGS |
No | Comma-separated tags for job targeting | baremetal,jlink,mcu |
RUNNER_EXECUTOR |
No | Executor type (use shell for hardware testing) |
shell |
Getting GitLab Registration Token:
- Project: Settings → CI/CD → Runners → Specific runners
- Group: Settings → CI/CD → Runners
- Instance: Admin Area → Overview → Runners
| Variable | Required | Description | Example |
|---|---|---|---|
CONTAINER_NAME |
No | Unique container name (change if cloning repo multiple times) | baremetal-ci-runner-1 |
ENABLE_JLINK |
No | Install Segger J-Link software (true/false) |
false |
PCAN_BAUDRATE |
No | CAN bus baudrate in bits/second for PEAK CAN device | 125000 |
ADDITIONAL_PACKAGES |
No | Space-separated apt packages to install during build | gdb-multiarch openocd |
- Check the container logs:
docker-compose logs -f - Verify your
GITHUB_TOKENhas correct permissions - Ensure the token hasn't expired
- Check if the device is connected:
lsusb | grep 1366 - Verify USB passthrough:
docker exec baremetal-ci-runner lsusb - Check container privileges: ensure
privileged: truein docker-compose.yml - Verify udev rules are loaded on host:
sudo udevadm control --reload-rules && sudo udevadm trigger
The container runs in privileged mode and should have access to all USB devices. If issues persist:
- Check host udev rules:
/etc/udev/rules.d/99-jlink.rules - Reload udev rules:
sudo udevadm control --reload-rules && sudo udevadm trigger - Verify the runner user has proper permissions inside the container
docker-compose downThe runner will automatically unregister from GitHub on shutdown.
docker-compose down
docker-compose build --no-cache
docker-compose up -ddocker-compose logs -fdocker exec -it baremetal-ci-runner bash- Store
GITHUB_TOKENsecurely (use secrets management) - Limit token permissions to only what's necessary
- Run container on trusted, isolated networks
- Regularly update the base image and J-Link software
- Monitor runner activity in GitHub Actions logs
This project is provided as-is for CI/CD automation purposes.
For issues related to:
- GitHub Actions Runner: actions/runner
- Segger J-Link: Segger Support
- Docker: Docker Documentation