├── .github └── workflows │ ├── github.yml │ └── gitlab.yml ├── .gitlab-ci └── docker-ros.yml ├── CITATION.cff ├── LICENSE ├── README.md ├── action.yml ├── assets ├── banner.png ├── logo-midjourney.png └── logo.png ├── docker ├── Dockerfile ├── entrypoint.sh └── recursive_vcs_import.py └── scripts ├── build.sh ├── ci.sh └── utils.sh /.github/workflows/github.yml: -------------------------------------------------------------------------------- 1 | name: GitHub 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | branches: 9 | - main 10 | 11 | jobs: 12 | 13 | trigger-ros2: 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: convictional/trigger-workflow-and-wait@v1.6.5 17 | with: 18 | owner: ika-rwth-aachen 19 | repo: docker-ros-ci 20 | ref: main 21 | workflow_file_name: ros2.yml 22 | client_payload: '{"docker-ros-git-ref": "${{ github.sha }}"}' 23 | github_token: ${{ secrets.DOCKER_ROS_CI_TRIGGER_GITHUB_TOKEN }} 24 | -------------------------------------------------------------------------------- /.github/workflows/gitlab.yml: -------------------------------------------------------------------------------- 1 | name: GitLab 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | branches: 9 | - main 10 | 11 | jobs: 12 | 13 | trigger-ros2: 14 | runs-on: ubuntu-latest 15 | steps: 16 | - name: Trigger pipeline 17 | run: | 18 | curl --silent --fail --request POST --form "token=${{ secrets.DOCKER_ROS_CI_TRIGGER_GITLAB_TOKEN }}" --form "ref=main" --form "variables[DOCKER_ROS_GIT_REF]=${{ github.sha }}" "https://gitlab.ika.rwth-aachen.de/api/v4/projects/1886/trigger/pipeline" | jq -r .id > id 19 | - name: Upload pipeline ID 20 | uses: actions/upload-artifact@v4 21 | with: 22 | name: id_ros2 23 | path: id 24 | 25 | watch-ros2: 26 | runs-on: ubuntu-latest 27 | needs: trigger-ros2 28 | steps: 29 | - name: Get pipeline ID 30 | uses: actions/download-artifact@v4 31 | with: 32 | name: id_ros2 33 | - name: Wait for pipeline completion 34 | run: | 35 | PIPELINE_ID=$(cat id) 36 | while true; do 37 | sleep 30 38 | PIPELINE_STATUS=$(curl --silent --header "PRIVATE-TOKEN: ${{ secrets.DOCKER_ROS_CI_READ_PIPELINE_GITLAB_TOKEN }}" "https://gitlab.ika.rwth-aachen.de/api/v4/projects/1886/pipelines/$PIPELINE_ID" | jq -r .status) 39 | echo "Pipeline status: $PIPELINE_STATUS (https://gitlab.ika.rwth-aachen.de/fb-fi/ops/docker-ros-ci/-/pipelines/$PIPELINE_ID)" 40 | if [[ $PIPELINE_STATUS == "success" ]]; then 41 | break 42 | elif [[ $PIPELINE_STATUS == "failed" ]]; then 43 | exit 1 44 | elif [[ $PIPELINE_STATUS == "canceled" ]]; then 45 | exit 1 46 | fi 47 | done 48 | -------------------------------------------------------------------------------- /.gitlab-ci/docker-ros.yml: -------------------------------------------------------------------------------- 1 | workflow: 2 | rules: 3 | - if: $CI_PIPELINE_SOURCE == "parent_pipeline" # run child pipeline if triggered by parent pipeline 4 | - if: $CI_PIPELINE_SOURCE == "merge_request_event" # run merge request pipeline if triggered by merge request 5 | - if: $CI_COMMIT_BRANCH && $CI_OPEN_MERGE_REQUESTS # don't run branch pipeline if open merge request exists 6 | when: never 7 | - if: $CI_COMMIT_BRANCH # run branch pipeline if triggered by commit 8 | - if: $CI_COMMIT_TAG # run tag pipeline with specific image tags if triggered by tag 9 | 10 | 11 | variables: 12 | ADDITIONAL_DEBS_FILE: docker/additional-debs.txt # Relative filepath to file containing additional apt deb packages to install 13 | ADDITIONAL_FILES_DIR: docker/additional-files # Relative path to directory containing additional files to copy into image" 14 | ADDITIONAL_PIP_FILE: docker/additional-pip-requirements.txt # Relative filepath to file containing additional pip packages to install 15 | AFTER_DEPENDENCY_INSTALLATION_SCRIPT: docker/custom.sh # Relative filepath to script containing commands to run after dependency installation 16 | BASE_IMAGE: '' # Base image name:tag (required) 17 | BEFORE_DEPENDENCY_IDENTIFICATION_SCRIPT: docker/before_dependency_identification.sh # Relative filepath to script containing commands to run before dependency identification 18 | BEFORE_DEPENDENCY_INSTALLATION_SCRIPT: docker/before_dependency_installation.sh # Relative filepath to script containing commands to run before dependency installation 19 | BLACKLISTED_PACKAGES_FILE: docker/blacklisted-packages.txt # Relative filepath to file containing the blacklisted packages 20 | BUILD_CONTEXT: . # Build context of Docker build process 21 | COMMAND: '' # Launch command of run image (required if target=run) 22 | CMAKE_ARGS: '-DCMAKE_BUILD_TYPE=Release' # CMake arguments to pass to `colcon build` 23 | DISABLE_ROS_INSTALLATION: 'false' # Disable automatic installation of `ros-$ROS_DISTRO-ros-core` package, e.g., if ROS is already installed in `base-image` and package is not available for the OS 24 | DOCKER_ROS_GIT_REF: main # Git ref of *docker-ros* to run in CI 25 | ENABLE_INDUSTRIAL_CI: 'false' # Enable industrial_ci 26 | ENABLE_PUSH_AS_LATEST: 'false' # Push images with tag `latest`/`latest-dev` in addition to the configured image names 27 | ENABLE_RECURSIVE_ADDITIONAL_DEBS: 'false' # Enable recursive discovery of files named `additional-debs-file` 28 | ENABLE_RECURSIVE_ADDITIONAL_PIP: 'false' # Enable recursive discovery of files named `additional-pip-file` 29 | ENABLE_RECURSIVE_AFTER_DEPENDENCY_INSTALLATION_SCRIPT: 'false' # Enable recursive discovery of files named `after-dependency-installation-script` 30 | ENABLE_RECURSIVE_BEFORE_DEPENDENCY_INSTALLATION_SCRIPT: 'false' # Enable recursive discovery of files named `before-dependency-installation-script` 31 | ENABLE_RECURSIVE_BLACKLISTED_PACKAGES: 'false' # Enable recursive discovery of files named `blacklisted-packages-file` 32 | ENABLE_RECURSIVE_VCS_IMPORT: 'true' # Enable recursive discovery of files named `*.repos` 33 | ENABLE_SINGLEARCH_PUSH: 'false' # Enable push of single arch images with [-amd64|-arm64] postfix 34 | ENABLE_SLIM: 'true' # Enable an extra slimmed run image via slim (only if run stage is targeted) 35 | GIT_HTTPS_PASSWORD: ${CI_JOB_TOKEN} # Password for cloning private Git repositories via HTTPS 36 | GIT_HTTPS_SERVER: ${CI_SERVER_HOST} # Server URL (without protocol) for cloning private Git repositories via HTTPS 37 | GIT_HTTPS_USER: gitlab-ci-token # Username for cloning private Git repositories via HTTPS 38 | GIT_SSH_KNOWN_HOST_KEYS: '' # Known SSH host keys for cloning private Git repositories via SSH (may be obtained using `ssh-keyscan`) 39 | GIT_SSH_PRIVATE_KEY: '' # SSH private key for cloning private Git repositories via SSH 40 | IMAGE_NAME: ${CI_REGISTRY_IMAGE} # Image name of run image 41 | IMAGE_TAG: latest # Image tag of run image 42 | PLATFORM: amd64 # Target platform architecture (comma-separated list) [amd64|arm64|...] 43 | REGISTRY_PASSWORD: ${CI_REGISTRY_PASSWORD} # Docker registry password 44 | REGISTRY_USER: ${CI_REGISTRY_USER} # Docker registry username 45 | REGISTRY: ${CI_REGISTRY} # Docker registry to push images to 46 | RMW_IMPLEMENTATION: 'rmw_fastrtps_cpp' # RMW implementation to use (only for ROS 2) 47 | ROS_DISTRO: '' # ROS Distro (required if ROS is not installed in `base-image`) 48 | SLIM_BUILD_ARGS: '--sensor-ipc-mode proxy --continue-after=10 --show-clogs --http-probe=false --include-path /opt/ros --include-path /docker-ros/ws/install' # Arguments to `slim build` (except for `--target` and `--tag`) 49 | TARGET: run # Target stage of Dockerfile (comma-separated list) [dev|run] 50 | VCS_IMPORT_FILE: .repos # Relative filepath to file containing additional repos to install via vcstools (only relevant if ENABLE_RECURSIVE_VCS_IMPORT=false) 51 | 52 | DEV_IMAGE_NAME: ${IMAGE_NAME} # Image name of dev image 53 | DEV_IMAGE_TAG: ${IMAGE_TAG}-dev # Image tag of dev image 54 | SLIM_IMAGE_NAME: ${IMAGE_NAME} # Image name of slim run image 55 | SLIM_IMAGE_TAG: ${IMAGE_TAG}-slim # Image tag of slim run image 56 | 57 | # -------------------- 58 | 59 | _RUN_IMAGE: ${IMAGE_NAME}:${IMAGE_TAG} 60 | _DEV_IMAGE: ${DEV_IMAGE_NAME}:${DEV_IMAGE_TAG} 61 | _SLIM_IMAGE: ${SLIM_IMAGE_NAME}:${SLIM_IMAGE_TAG} 62 | 63 | _IMAGE_DEV_CI: ${_DEV_IMAGE}_${CI_COMMIT_REF_SLUG}_ci 64 | _IMAGE_RUN_CI: ${_RUN_IMAGE}_${CI_COMMIT_REF_SLUG}_ci 65 | _IMAGE_SLIM_CI: ${_SLIM_IMAGE}_${CI_COMMIT_REF_SLUG}_ci 66 | _IMAGE_DEV_CI_AMD64: ${_IMAGE_DEV_CI}-amd64 67 | _IMAGE_DEV_CI_ARM64: ${_IMAGE_DEV_CI}-arm64 68 | _IMAGE_RUN_CI_AMD64: ${_IMAGE_RUN_CI}-amd64 69 | _IMAGE_RUN_CI_ARM64: ${_IMAGE_RUN_CI}-arm64 70 | _IMAGE_SLIM_CI_AMD64: ${_IMAGE_SLIM_CI}-amd64 71 | _IMAGE_SLIM_CI_ARM64: ${_IMAGE_SLIM_CI}-arm64 72 | _IMAGE_DEV_LATEST: ${DEV_IMAGE_NAME}:latest-dev 73 | _IMAGE_RUN_LATEST: ${IMAGE_NAME}:latest 74 | _IMAGE_SLIM_LATEST: ${SLIM_IMAGE_NAME}:latest-slim 75 | _IMAGE_DEV_TARGET_TAG: ${_DEV_IMAGE}-${CI_COMMIT_TAG} 76 | _IMAGE_RUN_TARGET_TAG: ${_RUN_IMAGE}-${CI_COMMIT_TAG} 77 | _IMAGE_SLIM_TARGET_TAG: ${_SLIM_IMAGE}-${CI_COMMIT_TAG} 78 | _IMAGE_DEV_TAG: ${DEV_IMAGE_NAME}:${CI_COMMIT_TAG}-dev 79 | _IMAGE_RUN_TAG: ${IMAGE_NAME}:${CI_COMMIT_TAG} 80 | _IMAGE_SLIM_TAG: ${SLIM_IMAGE_NAME}:${CI_COMMIT_TAG}-slim 81 | 82 | GIT_SUBMODULE_STRATEGY: recursive 83 | DOCKER_DRIVER: overlay2 84 | DOCKER_TLS_CERTDIR: /certs 85 | DOCKER_BUILDKIT: 1 86 | 87 | 88 | stages: 89 | - Build dev Images 90 | - Build run Images 91 | - Test ROS Industrial CI 92 | - Slim Images 93 | - Push Multi-Arch Images 94 | 95 | 96 | default: 97 | image: docker:20.10.22-git 98 | services: 99 | - docker:20.10.22-dind 100 | tags: 101 | - privileged 102 | - amd64 103 | before_script: 104 | - echo -e "section_start:`date +%s`:setup_section[collapsed=true]\r\e[0K[docker-ros] Setup docker-ros" 105 | - apk add --update bash 106 | - cd ${BUILD_CONTEXT} 107 | - |- 108 | if [[ ! -d docker/docker-ros ]]; then 109 | mkdir -p docker 110 | git clone --depth=1 https://github.com/ika-rwth-aachen/docker-ros.git docker/docker-ros 111 | cd docker/docker-ros 112 | git fetch origin ${DOCKER_ROS_GIT_REF} 113 | git checkout FETCH_HEAD 114 | cd - 115 | fi 116 | - docker login -u ${REGISTRY_USER} -p ${REGISTRY_PASSWORD} ${REGISTRY} 117 | - docker context create buildx-context 118 | - docker buildx create --use buildx-context 119 | - echo -e "section_end:`date +%s`:setup_section\r\e[0K" 120 | 121 | .build: 122 | script: 123 | - |- 124 | if [[ ${CI_RUNNER_EXECUTABLE_ARCH} != ${_PLATFORM} && ${CI_RUNNER_EXECUTABLE_ARCH} != linux/${_PLATFORM} ]]; then 125 | docker run --rm --privileged multiarch/qemu-user-static --reset -p yes 126 | fi 127 | TARGET=${_TARGET} PLATFORM=${_PLATFORM} ./docker/docker-ros/scripts/ci.sh 128 | echo -e "section_start:`date +%s`:push_section[collapsed=true]\r\e[0K[docker-ros] Push ${IMAGE}" 129 | docker push ${IMAGE} 130 | echo -e "section_end:`date +%s`:push_section\r\e[0K" 131 | 132 | dev-amd64: 133 | stage: Build dev Images 134 | extends: .build 135 | rules: 136 | - if: $PLATFORM =~ /.*amd64.*/ && $TARGET =~ /.*dev.*/ 137 | variables: 138 | _PLATFORM: amd64 139 | _TARGET: dev 140 | IMAGE: ${_IMAGE_DEV_CI_AMD64} 141 | ENABLE_SINGLEARCH_PUSH: 'true' 142 | ENABLE_SLIM: 'false' 143 | _IMAGE_POSTFIX: _${CI_COMMIT_REF_SLUG}_ci 144 | 145 | dev-arm64: 146 | stage: Build dev Images 147 | extends: .build 148 | tags: [privileged, arm64] 149 | rules: 150 | - if: $PLATFORM =~ /.*arm64.*/ && $TARGET =~ /.*dev.*/ 151 | variables: 152 | _PLATFORM: arm64 153 | _TARGET: dev 154 | IMAGE: ${_IMAGE_DEV_CI_ARM64} 155 | ENABLE_SINGLEARCH_PUSH: 'true' 156 | ENABLE_SLIM: 'false' 157 | _IMAGE_POSTFIX: _${CI_COMMIT_REF_SLUG}_ci 158 | 159 | run-amd64: 160 | stage: Build run Images 161 | extends: .build 162 | needs: 163 | - job: dev-amd64 164 | optional: true 165 | rules: 166 | - if: $PLATFORM =~ /.*amd64.*/ && $TARGET =~ /.*run.*/ 167 | variables: 168 | _PLATFORM: amd64 169 | _TARGET: run 170 | IMAGE: ${_IMAGE_RUN_CI_AMD64} 171 | ENABLE_SINGLEARCH_PUSH: 'true' 172 | ENABLE_SLIM: 'false' 173 | _IMAGE_POSTFIX: _${CI_COMMIT_REF_SLUG}_ci 174 | 175 | run-arm64: 176 | stage: Build run Images 177 | extends: .build 178 | tags: [privileged, arm64] 179 | needs: 180 | - job: dev-arm64 181 | optional: true 182 | rules: 183 | - if: $PLATFORM =~ /.*arm64.*/ && $TARGET =~ /.*run.*/ 184 | variables: 185 | _PLATFORM: arm64 186 | _TARGET: run 187 | IMAGE: ${_IMAGE_RUN_CI_ARM64} 188 | ENABLE_SINGLEARCH_PUSH: 'true' 189 | ENABLE_SLIM: 'false' 190 | _IMAGE_POSTFIX: _${CI_COMMIT_REF_SLUG}_ci 191 | 192 | 193 | .test: 194 | stage: Test ROS Industrial CI 195 | variables: 196 | UPSTREAM_WORKSPACE: ${BUILD_CONTEXT}/.repos 197 | TARGET_WORKSPACE: ${BUILD_CONTEXT} 198 | ADDITIONAL_DEBS: git 199 | AFTER_INIT_EMBED: git config --global url.https://${GIT_HTTPS_USER}:${GIT_HTTPS_PASSWORD}@${GIT_HTTPS_SERVER}.insteadOf https://${GIT_HTTPS_SERVER} 200 | DOCKER_RUN_OPTS: -u root:root 201 | before_script: 202 | - docker login -u ${REGISTRY_USER} -p ${REGISTRY_PASSWORD} ${REGISTRY} 203 | - apk add --update bash coreutils grep tar 204 | - |- 205 | if [[ ${CI_RUNNER_EXECUTABLE_ARCH} != ${_PLATFORM} && ${CI_RUNNER_EXECUTABLE_ARCH} != linux/${_PLATFORM} ]]; then 206 | docker run --rm --privileged multiarch/qemu-user-static --reset -p yes 207 | fi 208 | - git clone --branch master --depth 1 https://github.com/ros-industrial/industrial_ci.git .industrial_ci 209 | - test -f ${BUILD_CONTEXT}/.repos || echo "repositories:" > ${BUILD_CONTEXT}/.repos 210 | script: .industrial_ci/gitlab.sh 211 | 212 | Test dev-amd64: 213 | extends: .test 214 | needs: 215 | - job: dev-amd64 216 | rules: 217 | - if: $ENABLE_INDUSTRIAL_CI == 'true' && $PLATFORM =~ /.*amd64.*/ && $TARGET =~ /.*dev.*/ 218 | variables: 219 | DOCKER_IMAGE: ${_IMAGE_DEV_CI_AMD64} 220 | _PLATFORM: amd64 221 | 222 | Test dev-arm64: 223 | extends: .test 224 | tags: [privileged, arm64] 225 | needs: 226 | - job: dev-arm64 227 | rules: 228 | - if: $ENABLE_INDUSTRIAL_CI == 'true' && $PLATFORM =~ /.*arm64.*/ && $TARGET =~ /.*dev.*/ 229 | variables: 230 | DOCKER_IMAGE: ${_IMAGE_DEV_CI_ARM64} 231 | _PLATFORM: arm64 232 | 233 | Test run-amd64: 234 | extends: .test 235 | needs: 236 | - job: run-amd64 237 | rules: 238 | - if: $ENABLE_INDUSTRIAL_CI == 'true' && $PLATFORM =~ /.*amd64.*/ && $TARGET !~ /.*dev.*/ 239 | variables: 240 | DOCKER_IMAGE: ${_IMAGE_RUN_CI_AMD64} 241 | _PLATFORM: amd64 242 | 243 | Test run-arm64: 244 | extends: .test 245 | needs: 246 | - job: run-arm64 247 | rules: 248 | - if: $ENABLE_INDUSTRIAL_CI == 'true' && $PLATFORM =~ /.*arm64.*/ && $TARGET !~ /.*dev.*/ 249 | variables: 250 | DOCKER_IMAGE: ${_IMAGE_RUN_CI_ARM64} 251 | _PLATFORM: arm64 252 | 253 | 254 | .slim: 255 | stage: Slim Images 256 | before_script: 257 | - apk add --update --upgrade curl tar 258 | - curl -L -o ds.tar.gz ${SLIM_DOWNLOAD_URL} 259 | - tar -xvf ds.tar.gz 260 | - cd dist_linux* 261 | - docker login -u ${REGISTRY_USER} -p ${REGISTRY_PASSWORD} ${REGISTRY} 262 | - docker pull ${FAT_IMAGE} 263 | script: 264 | - ./slim build --target ${FAT_IMAGE} --tag ${SLIM_IMAGE} ${SLIM_BUILD_ARGS} 265 | - docker push ${SLIM_IMAGE} 266 | 267 | Slim run-amd64: 268 | stage: Slim Images 269 | extends: .slim 270 | needs: 271 | - job: run-amd64 272 | optional: true 273 | - job: Test run-amd64 274 | optional: true 275 | rules: 276 | - if: $ENABLE_SLIM == 'true' && $PLATFORM =~ /.*amd64.*/ && $TARGET =~ /.*run.*/ 277 | variables: 278 | FAT_IMAGE: ${_IMAGE_RUN_CI_AMD64} 279 | SLIM_IMAGE: ${_IMAGE_SLIM_CI_AMD64} 280 | SLIM_DOWNLOAD_URL: "https://github.com/slimtoolkit/slim/releases/download/1.40.11/dist_linux.tar.gz" 281 | 282 | Slim run-arm64: 283 | stage: Slim Images 284 | extends: .slim 285 | tags: [privileged, arm64] 286 | needs: 287 | - job: run-arm64 288 | optional: true 289 | - job: Test run-arm64 290 | optional: true 291 | rules: 292 | - if: $ENABLE_SLIM == 'true' && $PLATFORM =~ /.*arm64.*/ && $TARGET =~ /.*run.*/ 293 | variables: 294 | FAT_IMAGE: ${_IMAGE_RUN_CI_ARM64} 295 | SLIM_IMAGE: ${_IMAGE_SLIM_CI_ARM64} 296 | SLIM_DOWNLOAD_URL: "https://github.com/slimtoolkit/slim/releases/download/1.40.11/dist_linux_arm64.tar.gz" 297 | 298 | 299 | .push: 300 | needs: 301 | - job: dev-amd64 302 | optional: true 303 | - job: dev-arm64 304 | optional: true 305 | - job: run-amd64 306 | optional: true 307 | - job: run-arm64 308 | optional: true 309 | - job: Test dev-amd64 310 | optional: true 311 | - job: Test dev-arm64 312 | optional: true 313 | - job: Test run-amd64 314 | optional: true 315 | - job: Test run-arm64 316 | optional: true 317 | - job: Slim run-amd64 318 | optional: true 319 | - job: Slim run-arm64 320 | optional: true 321 | rules: 322 | - if: $PLATFORM == '' || $TARGET == '' 323 | when: never 324 | script: 325 | - |- 326 | if [[ "${PLATFORM}" =~ amd64 && "${PLATFORM}" =~ arm64 ]]; then 327 | if [[ "${TARGET}" =~ dev ]]; then 328 | docker manifest create ${IMG_DEV} --amend ${_IMAGE_DEV_CI_AMD64} --amend ${_IMAGE_DEV_CI_ARM64} 329 | docker manifest push ${IMG_DEV} 330 | fi 331 | if [[ "${TARGET}" =~ run ]]; then 332 | docker manifest create ${IMG_RUN} --amend ${_IMAGE_RUN_CI_AMD64} --amend ${_IMAGE_RUN_CI_ARM64} 333 | docker manifest push ${IMG_RUN} 334 | fi 335 | if [[ "${ENABLE_SLIM}" == 'true' && "${TARGET}" =~ run ]]; then 336 | docker manifest create ${IMG_SLIM} --amend ${_IMAGE_SLIM_CI_AMD64} --amend ${_IMAGE_SLIM_CI_ARM64} 337 | docker manifest push ${IMG_SLIM} 338 | fi 339 | elif [[ "${PLATFORM}" =~ amd64 ]]; then 340 | if [[ "${TARGET}" =~ dev ]]; then 341 | docker pull ${_IMAGE_DEV_CI_AMD64} 342 | docker tag ${_IMAGE_DEV_CI_AMD64} ${IMG_DEV} 343 | docker push ${IMG_DEV} 344 | fi 345 | if [[ "${TARGET}" =~ run ]]; then 346 | docker pull ${_IMAGE_RUN_CI_AMD64} 347 | docker tag ${_IMAGE_RUN_CI_AMD64} ${IMG_RUN} 348 | docker push ${IMG_RUN} 349 | fi 350 | if [[ "${ENABLE_SLIM}" == 'true' && "${TARGET}" =~ run ]]; then 351 | docker pull ${_IMAGE_SLIM_CI_AMD64} 352 | docker tag ${_IMAGE_SLIM_CI_AMD64} ${IMG_SLIM} 353 | docker push ${IMG_SLIM} 354 | fi 355 | elif [[ "${PLATFORM}" =~ arm64 ]]; then 356 | if [[ "${TARGET}" =~ dev ]]; then 357 | docker pull ${_IMAGE_DEV_CI_ARM64} 358 | docker tag ${_IMAGE_DEV_CI_ARM64} ${IMG_DEV} 359 | docker push ${IMG_DEV} 360 | fi 361 | if [[ "${TARGET}" =~ run ]]; then 362 | docker pull ${_IMAGE_RUN_CI_ARM64} 363 | docker tag ${_IMAGE_RUN_CI_ARM64} ${IMG_RUN} 364 | docker push ${IMG_RUN} 365 | fi 366 | if [[ "${ENABLE_SLIM}" == 'true' && "${TARGET}" =~ run ]]; then 367 | docker pull ${_IMAGE_SLIM_CI_ARM64} 368 | docker tag ${_IMAGE_SLIM_CI_ARM64} ${IMG_SLIM} 369 | docker push ${IMG_SLIM} 370 | fi 371 | fi 372 | 373 | Push CI: 374 | stage: Push Multi-Arch Images 375 | extends: .push 376 | rules: 377 | - !reference [.push, rules] 378 | - if: $CI_COMMIT_TAG 379 | when: never 380 | - if: $CI_COMMIT_BRANCH != $CI_DEFAULT_BRANCH 381 | variables: 382 | IMG_DEV: ${_IMAGE_DEV_CI} 383 | IMG_RUN: ${_IMAGE_RUN_CI} 384 | IMG_SLIM: ${_IMAGE_SLIM_CI} 385 | 386 | Push: 387 | stage: Push Multi-Arch Images 388 | extends: .push 389 | rules: 390 | - !reference [.push, rules] 391 | - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH 392 | variables: 393 | IMG_DEV: ${_DEV_IMAGE} 394 | IMG_RUN: ${_RUN_IMAGE} 395 | IMG_SLIM: ${_SLIM_IMAGE} 396 | 397 | Push latest: 398 | stage: Push Multi-Arch Images 399 | extends: .push 400 | rules: 401 | - !reference [.push, rules] 402 | - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH && $ENABLE_PUSH_AS_LATEST == 'true' 403 | variables: 404 | IMG_DEV: ${_IMAGE_DEV_LATEST} 405 | IMG_RUN: ${_IMAGE_RUN_LATEST} 406 | IMG_SLIM: ${_IMAGE_SLIM_LATEST} 407 | 408 | Push target tag: 409 | stage: Push Multi-Arch Images 410 | extends: .push 411 | rules: 412 | - !reference [.push, rules] 413 | - if: $CI_COMMIT_TAG 414 | variables: 415 | IMG_DEV: ${_IMAGE_DEV_TARGET_TAG} 416 | IMG_RUN: ${_IMAGE_RUN_TARGET_TAG} 417 | IMG_SLIM: ${_IMAGE_SLIM_TARGET_TAG} 418 | 419 | Push tag: 420 | stage: Push Multi-Arch Images 421 | extends: .push 422 | rules: 423 | - !reference [.push, rules] 424 | - if: $CI_COMMIT_TAG && $ENABLE_PUSH_AS_LATEST == 'true' 425 | variables: 426 | IMG_DEV: ${_IMAGE_DEV_TAG} 427 | IMG_RUN: ${_IMAGE_RUN_TAG} 428 | IMG_SLIM: ${_IMAGE_SLIM_TAG} 429 | -------------------------------------------------------------------------------- /CITATION.cff: -------------------------------------------------------------------------------- 1 | cff-version: 1.2.0 2 | message: "We hope that our tools can help your research. If this is the case, please cite it using the following metadata." 3 | 4 | title: dorotos 5 | type: software 6 | repository-code: "https://github.com/ika-rwth-aachen/docker-ros" 7 | date-released: 2023-05-28 8 | authors: 9 | - given-names: Jean-Pierre 10 | family-names: Busch 11 | - given-names: Lennart 12 | family-names: Reiher 13 | 14 | preferred-citation: 15 | title: "Enabling the Deployment of Any-Scale Robotic Applications in Microservice-Based Service-Oriented Architectures through Automated Containerization" 16 | type: conference-paper 17 | conference: 18 | name: "2024 IEEE International Conference on Robotics and Automation (ICRA)" 19 | year: 2024 20 | pages: "17650-17656" 21 | doi: "10.1109/ICRA57147.2024.10611586" 22 | url: "https://ieeexplore.ieee.org/document/10611586" 23 | authors: 24 | - given-names: Jean-Pierre 25 | family-names: Busch 26 | orcid: "https://orcid.org/0009-0000-1417-0463" 27 | - given-names: Lennart 28 | family-names: Reiher 29 | orcid: "https://orcid.org/0000-0002-7309-164X" 30 | - given-names: Lutz 31 | family-names: Eckstein 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023-2024 Institute for Automotive Engineering (ika), RWTH Aachen University 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # *docker-ros* – Automated Containerization of ROS Apps 4 | 5 |

6 | 7 | 8 | 9 | 10 | 11 |

12 | 13 | *docker-ros* automatically builds minimal container images of ROS applications. 14 | 15 | > [!IMPORTANT] 16 | > This repository is open-sourced and maintained by the [**Institute for Automotive Engineering (ika) at RWTH Aachen University**](https://www.ika.rwth-aachen.de/). 17 | > **DevOps, Containerization and Orchestration of Software-Defined Vehicles** are some of many research topics within our [*Vehicle Intelligence & Automated Driving*](https://www.ika.rwth-aachen.de/en/competences/fields-of-research/vehicle-intelligence-automated-driving.html) domain. 18 | > If you would like to learn more about how we can support your advanced driver assistance and automated driving efforts, feel free to reach out to us! 19 | > :email: ***opensource@ika.rwth-aachen.de*** 20 | 21 | - [About](#about) 22 | - [Prerequisites](#prerequisites) 23 | - [Usage](#usage) 24 | - [Build a minimal image for deployment](#build-a-minimal-image-for-deployment) 25 | - [Build development and deployment images](#build-development-and-deployment-images) 26 | - [Build multi-arch images](#build-multi-arch-images) 27 | - [Build deployment image with additional industrial\_ci check](#build-deployment-image-with-additional-industrial_ci-check) 28 | - [Build multi-arch images on arch-specific self-hosted runners in parallel](#build-multi-arch-images-on-arch-specific-self-hosted-runners-in-parallel) 29 | - [Build images locally](#build-images-locally) 30 | - [Advanced Dependencies](#advanced-dependencies) 31 | - [Recursion](#recursion) 32 | - [Package Blacklist](#package-blacklist) 33 | - [Extra System Dependencies (*apt*)](#extra-system-dependencies-apt) 34 | - [Extra System Dependencies (*pip*)](#extra-system-dependencies-pip) 35 | - [Custom Installation Scripts](#custom-installation-scripts) 36 | - [Extra Image Files](#extra-image-files) 37 | - [Additional Information](#additional-information) 38 | - [User Setup](#user-setup) 39 | - [Slim Deployment Image](#slim-deployment-image) 40 | - [Configuration Variables](#configuration-variables) 41 | 42 | We recommend to use *docker-ros* in combination with our other tools for Docker and ROS. 43 | - [*docker-ros-ml-images*](https://github.com/ika-rwth-aachen/docker-ros-ml-images) provides machine learning-enabled ROS Docker images 44 | - [*docker-run*](https://github.com/ika-rwth-aachen/docker-run) is a CLI tool for simplified interaction with Docker images 45 | 46 | 47 | ## About 48 | 49 | *docker-ros* provides a generic [Dockerfile](docker/Dockerfile) that can be used to build development and deployment Docker images for arbitrary ROS packages or package stacks. Building such images can easily be automated by integrating *docker-ros* into CI through the provided [GitHub action](action.yml) or [GitLab CI template](.gitlab-ci/docker-ros.yml). The development image built by *docker-ros* contains all required dependencies and the source code of your ROS-based repository. The deployment image only contains dependencies and the compiled binaries created by building the ROS packages in the repository. *docker-ros* is also able to build multi-arch Docker images for *amd64* and *arm64* architectures. In addition, [*slim*](https://github.com/slimtoolkit/slim) is integrated for slimming Docker image size of the deployment image by up to 30x (see [*Slim Deployment Image*](#slim-deployment-image)). 50 | 51 | The Dockerfile performs the following steps to build these images: 52 | 1. All dependency repositories that are defined in a `.repos` file anywhere in the repository are cloned using [*vcstool*](https://github.com/dirk-thomas/vcstool). 53 | 2. *(optional)* Packages blacklisted in a special file `blacklisted-packages.txt` are removed from the workspace (see [*Advanced Dependencies*](#package-blacklist)). 54 | 3. *(optional)* A special script `before_dependency_installation.sh` is executed to perform arbitrary installation commands, if needed (see [*Advanced Dependencies*](#custom-installation-scripts)). 55 | 4. The ROS dependencies listed in each package's `package.xml` are installed by [*rosdep*](https://docs.ros.org/en/independent/api/rosdep/html/). 56 | 5. *(optional)* Additional apt dependencies from a special file `additional-debs.txt` are installed, if needed (see [*Advanced Dependencies*](#extra-system-dependencies-apt)). 57 | 6. *(optional)* Additional pip requirements from a special file `additional-pip-requirements.txt` are installed, if needed (see [*Advanced Dependencies*](#extra-system-dependencies-pip)). 58 | 7. *(optional)* A special folder `additional-files/` is copied into the images, if needed (see [*Advanced Dependencies*](#extra-image-files)). 59 | 8. *(optional)* A special script `custom.sh` is executed to perform arbitrary installation commands, if needed (see [*Advanced Dependencies*](#custom-installation-scripts)). 60 | 9. *(deployment)* All ROS packages are built using `colcon` (ROS2). 61 | 10. *(deployment)* A custom launch command is configured to run on container start. 62 | 63 | ### Prerequisites 64 | 65 | *docker-ros* is made for automated execution in GitHub or GitLab CI pipelines. For local execution, see [*Build images locally*](#build-images-locally). 66 | 67 |
GitHub 68 | 69 | GitHub offers free minutes on GitHub-hosted runners executing GitHub Actions, [see here](https://docs.github.com/en/billing/managing-billing-for-github-actions/about-billing-for-github-actions). No further setup is required other than integrating *docker-ros* into your repository, see [*Usage*](#usage). 70 | 71 | Note that GitHub is currently only offering Linux runners based on the *amd64* architecture. *docker-ros* can also build multi-arch Docker images solely on the *amd64* platform through emulation, but performance can be improved greatly by deploying [self-hosted runners](https://docs.github.com/en/actions/hosting-your-own-runners/managing-self-hosted-runners/about-self-hosted-runners) for the *arm64* platform. 72 | 73 |
74 | 75 |
GitLab 76 | 77 | > [!NOTE] 78 | > - GitLab runners must be based on the Docker executor, [see here](https://docs.gitlab.com/runner/executors/docker.html) 79 | > - GitLab runners must run in privileged mode for Docker-in-Docker, [see here](https://docs.gitlab.com/runner/executors/docker.html#use-docker-in-docker-with-privileged-mode) 80 | > - GitLab runners must be tagged with tags `privileged` and either `amd64` or `arm64` depending on their architecture 81 | 82 | GitLab offers free minutes on GitLab-hosted runners executing GitLab CI pipelines on [gitlab.com](https://gitlab.com), [see here](https://docs.gitlab.com/runner/#use-gitlabcom-saas-runners). On self-hosted GitLab instances, you can set up self-hosted runners, [see here](https://docs.gitlab.com/runner/#use-self-managed-runners). 83 | 84 | Note that GitLab is currently only offering Linux runners based on the *amd64* architecture. *docker-ros* can also build multi-arch Docker images solely on the *amd64* platform through emulation, but performance can be improved greatly by deploying [self-hosted runners](https://docs.gitlab.com/runner/#use-self-managed-runners) for the *arm64* platform. 85 | 86 |
87 | 88 | 89 | ## Usage 90 | 91 | *docker-ros* can easily be integrated into any GitHub or GitLab repository containing ROS packages. Example integrations can be found in the following sections. Configuration options can be found [here](#configuration-variables). For local execution, see [*Build images locally*](#build-images-locally). 92 | 93 |
GitHub 94 | 95 | *docker-ros* provides a [GitHub action](action.yml) that can simply be added to a job via the [`jobs..steps[*].uses` keyword](https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsuses). A quick start for GitHub Actions is found [here](https://docs.github.com/en/actions/quickstart). 96 | 97 |
98 | 99 |
GitLab 100 | 101 | *docker-ros* provides a [GitLab CI template](.gitlab-ci/docker-ros.yml) that can simply be included in a [`.gitlab-ci.yml`](https://docs.gitlab.com/ee/ci/yaml/gitlab_ci_yaml.html) file. A quick start for GitLab CI is found [here](https://docs.gitlab.com/ee/ci/quick_start/). 102 | 103 |
104 | 105 | ### Build a minimal image for deployment 106 | 107 |
GitHub 108 | 109 | ```yml 110 | on: push 111 | jobs: 112 | docker-ros: 113 | runs-on: ubuntu-latest 114 | steps: 115 | - uses: ika-rwth-aachen/docker-ros@v1.7.0 116 | with: 117 | base-image: rwthika/ros2:humble 118 | command: ros2 run my_pkg my_node 119 | ``` 120 | 121 |
122 | 123 |
GitLab 124 | 125 | ```yml 126 | include: 127 | - remote: https://raw.githubusercontent.com/ika-rwth-aachen/docker-ros/v1.7.0/.gitlab-ci/docker-ros.yml 128 | 129 | variables: 130 | BASE_IMAGE: rwthika/ros2:humble 131 | COMMAND: ros2 run my_pkg my_node 132 | ``` 133 | 134 |
135 | 136 | ### Build development and deployment images 137 | 138 |
GitHub 139 | 140 | ```yml 141 | on: push 142 | jobs: 143 | docker-ros: 144 | runs-on: ubuntu-latest 145 | steps: 146 | - uses: ika-rwth-aachen/docker-ros@v1.7.0 147 | with: 148 | base-image: rwthika/ros2:humble 149 | command: ros2 run my_pkg my_node 150 | target: dev,run 151 | ``` 152 | 153 |
154 | 155 |
GitLab 156 | 157 | ```yml 158 | include: 159 | - remote: https://raw.githubusercontent.com/ika-rwth-aachen/docker-ros/v1.7.0/.gitlab-ci/docker-ros.yml 160 | 161 | variables: 162 | BASE_IMAGE: rwthika/ros2:humble 163 | COMMAND: ros2 run my_pkg my_node 164 | TARGET: dev,run 165 | ``` 166 | 167 |
168 | 169 | ### Build multi-arch images 170 | 171 |
GitHub 172 | 173 | ```yml 174 | on: push 175 | jobs: 176 | docker-ros: 177 | runs-on: ubuntu-latest 178 | steps: 179 | - uses: ika-rwth-aachen/docker-ros@v1.7.0 180 | with: 181 | base-image: rwthika/ros2:humble 182 | command: ros2 run my_pkg my_node 183 | target: dev,run 184 | platform: amd64,arm64 185 | ``` 186 | 187 |
188 | 189 |
GitLab 190 | 191 | ```yml 192 | include: 193 | - remote: https://raw.githubusercontent.com/ika-rwth-aachen/docker-ros/v1.7.0/.gitlab-ci/docker-ros.yml 194 | 195 | variables: 196 | BASE_IMAGE: rwthika/ros2:humble 197 | COMMAND: ros2 run my_pkg my_node 198 | TARGET: dev,run 199 | PLATFORM: amd64,arm64 200 | ``` 201 | 202 |
203 | 204 | ### Build deployment image with additional industrial_ci check 205 | 206 |
GitHub 207 | 208 | ```yml 209 | on: push 210 | jobs: 211 | docker-ros: 212 | runs-on: ubuntu-latest 213 | steps: 214 | - uses: ika-rwth-aachen/docker-ros@v1.7.0 215 | with: 216 | base-image: rwthika/ros2:humble 217 | command: ros2 run my_pkg my_node 218 | enable-industrial-ci: 'true' 219 | ``` 220 | 221 |
222 | 223 |
GitLab 224 | 225 | ```yml 226 | include: 227 | - remote: https://raw.githubusercontent.com/ika-rwth-aachen/docker-ros/v1.7.0/.gitlab-ci/docker-ros.yml 228 | 229 | variables: 230 | BASE_IMAGE: rwthika/ros2:humble 231 | COMMAND: ros2 run my_pkg my_node 232 | ENABLE_INDUSTRIAL_CI: 'true' 233 | ``` 234 | 235 |
236 | 237 | ### Build multi-arch images on arch-specific self-hosted runners in parallel 238 | 239 |
GitHub 240 | 241 | ```yml 242 | on: push 243 | jobs: 244 | docker-ros: 245 | strategy: 246 | matrix: 247 | target: [dev, run] 248 | platform: [amd64, arm64] 249 | runs-on: [self-hosted, "${{ matrix.platform }}"] 250 | steps: 251 | - uses: ika-rwth-aachen/docker-ros@v1.7.0 252 | with: 253 | base-image: rwthika/ros2:humble 254 | command: ros2 run my_pkg my_node 255 | target: ${{ matrix.target }} 256 | platform: ${{ matrix.platform }} 257 | enable-singlearch-push: true 258 | # TODO: manifest 259 | ``` 260 | 261 |
262 | 263 |
GitLab 264 | 265 | ```yml 266 | # TODO 267 | ``` 268 | 269 |
270 | 271 | ### Build images locally 272 | 273 | *docker-ros* can build Docker images locally by executing the [`build.sh`](scripts/build.sh) script. 274 | 275 | 1. Clone *docker-ros* as a Git submodule to `docker/docker-ros` in your repository. 276 | ```bash 277 | # ros-repository/ 278 | mkdir -p docker 279 | git submodule add https://github.com/ika-rwth-aachen/docker-ros.git docker/docker-ros 280 | ``` 281 | 2. Configure the build using the same [environment variables](#configuration-variables) as used for GitLab CI and run [`build.sh`](scripts/build.sh), e.g.: 282 | ```bash 283 | # ros-repository/ 284 | BASE_IMAGE="rwthika/ros2:humble" \ 285 | COMMAND="ros2 run my_pkg my_node" \ 286 | IMAGE="my-image:latest" \ 287 | ./docker/docker-ros/scripts/build.sh 288 | ``` 289 | > [!NOTE] 290 | > You can alternatively store your environment variable configuration in a `.env` file: 291 | > ```bash 292 | > # .env 293 | > BASE_IMAGE="rwthika/ros2:humble" 294 | > COMMAND="ros2 run my_pkg my_node" 295 | > IMAGE="my-image:latest" 296 | > ``` 297 | 298 | 299 | ## Advanced Dependencies 300 | 301 | In order to keep things organized, we recommend to place all *docker-ros* related files in a `docker` folder on top repository level. 302 | 303 | ### Recursion 304 | 305 | Most of the steps listed in [*About*](#about) and below can be toggled between recursive and non-recursive mode, see [*Configuration Variables*](#configuration-variables). This usually means that not only special files on the top-level are considered (e.g., `docker/additional-requirements.txt`), but also files with the same name (e.g., `additional-requirements.txt`) that are found anywhere in the workspace, even after cloning the upstream repositories in step 1. 306 | 307 | ### Package Blacklist 308 | 309 | If your ROS-based repository (or any of your repository's upstream dependencies, see `.repos`) contains ROS packages that should neither be built nor be used for determining dependencies, you can blacklist those in a special `blacklisted-packages.txt` file. 310 | 311 | Create a file `blacklisted-packages.txt` in your `docker` folder (or configure a different `BLACKLISTED_PACKAGES_FILE`) and list any ROS package name to blacklist. 312 | 313 | ### Extra System Dependencies (*apt*) 314 | 315 | If your ROS-based repository requires system dependencies that cannot be installed by specifying their [rosdep](https://docs.ros.org/en/independent/api/rosdep/html/) keys in a `package.xml`, you can use a special `additional-debs.txt` file. 316 | 317 | Create a file `additional-debs.txt` in your `docker` folder (or configure a different `ADDITIONAL_DEBS_FILE`) and list any other dependencies that need to be installed via *apt*. 318 | 319 | ### Extra System Dependencies (*pip*) 320 | 321 | If your ROS-based repository requires Python dependencies that cannot be installed by specifying their [rosdep](https://docs.ros.org/en/independent/api/rosdep/html/) keys in a `package.xml`, you can use a special `additional-pip-requirements.txt` file. 322 | 323 | Create a file `additional-pip-requirements.txt` in your `docker` folder (or configure a different `ADDITIONAL_PIP_FILE`) and list any other Python dependencies that need to be installed via *pip*. 324 | 325 | ### Custom Installation Scripts 326 | 327 | If your ROS-based repository requires to execute any other installation or pre-/post-installation steps, you can specify multiple custom scripts that are executed during the image building process. See the [configuration variable documentation](#configuration-variables) for the following variables. 328 | - `BEFORE_DEPENDENCY_IDENTIFICATION_SCRIPT` 329 | - `BEFORE_DEPENDENCY_INSTALLATION_SCRIPT` 330 | - `AFTER_DEPENDENCY_INSTALLATION_SCRIPT` 331 | 332 | Create those scripts in your `docker` folder (or configure different filepaths via the specified environment variables). 333 | 334 | ### Extra Image Files 335 | 336 | If you need to have additional files present in the deployment image, you can use a special `additional-files` folder. The folder contents will be copied into the container before the custom installation script `custom.sh` is executed. 337 | 338 | Create a folder `additional-files` in your `docker` folder (or configure a different `ADDITIONAL_FILES_DIR`) and place any files or directories in it. The contents will be copied to `/docker-ros/additional-files` in the image. 339 | 340 | 341 | ## Additional Information 342 | 343 | ### User Setup 344 | 345 | Containers of the provided images start with `root` user by default. If the two environment variables `DOCKER_UID` and `DOCKER_GID` are passed, a new user with the corresponding UID/GID is created on the fly. Most importantly, this features allows to mount and edit files of the host user in the container without having to deal with permission issues. 346 | 347 | ```bash 348 | docker run --rm -it -e DOCKER_UID=$(id -u) -e DOCKER_GID=$(id -g) -e DOCKER_USER=$(id -un) rwthika/ros:latest 349 | ``` 350 | 351 | The password of the custom user is set to its username (`dockeruser:dockeruser` by default). 352 | 353 | ### Slim Deployment Image 354 | 355 | *docker-ros* integrates the [*slim*](https://github.com/slimtoolkit/slim) toolkit for minifying container images. *slim* is enabled by default and will, in addition to the `run` deployment image, produce an additional `:latest-slim`-tagged minified image. Note that *slim* removes every single thing not needed for executing the default launch command. To balance image size and out-of-the-box functionality, the `/opt/ros` and `/docker-ros/ws/install` directories are preserved by default. The slimming process can be controlled via the `SLIM_BUILD_ARGS` configuration variable. 356 | 357 | 358 | ## Configuration Variables 359 | 360 | > [!NOTE] 361 | > *GitHub Action input* | *GitLab CI environment variable* 362 | 363 | - **`additional-debs-file` | `ADDITIONAL_DEBS_FILE`** 364 | Relative filepath to file containing additional apt deb packages to install 365 | *default:* `docker/additional-debs.txt` 366 | - **`additional-files-dir` | `ADDITIONAL_FILES_DIR`** 367 | Relative path to directory containing additional files to copy into image 368 | *default:* `docker/additional-files` 369 | - **`additional-pip-file` | `ADDITIONAL_PIP_FILE`** 370 | Relative filepath to file containing additional pip packages to install 371 | *default:* `docker/additional-pip-requirements.txt` 372 | - **`after-dependency-installation-script` | `AFTER_DEPENDENCY_INSTALLATION_SCRIPT`** 373 | Relative filepath to script containing commands to run after dependency installation 374 | *default:* `docker/custom.sh` 375 | - **`base-image` | `BASE_IMAGE`** 376 | Base image `name:tag` 377 | *required* 378 | - **`before-dependency-identification-script` | `BEFORE_DEPENDENCY_IDENTIFICATION_SCRIPT`** 379 | Relative filepath to script containing commands to run before dependency identification 380 | *default:* `docker/before_dependency_identification.sh` 381 | - **`before-dependency-installation-script` | `BEFORE_DEPENDENCY_INSTALLATION_SCRIPT`** 382 | Relative filepath to script containing commands to run before dependency installation 383 | *default:* `docker/before_dependency_installation.sh` 384 | - **`blacklisted-packages-file` | `BLACKLISTED_PACKAGES_FILE`** 385 | Relative filepath to file containing blacklisted packages 386 | *default:* `docker/blacklisted-packages.txt` 387 | - **`build-context` | `BUILD_CONTEXT`** 388 | Build context of Docker build process 389 | *default:* `${{ github.workspace }}` | `.` 390 | - **`command` | `COMMAND`** 391 | Launch command of run image 392 | *required if `target=run`* 393 | - **`cmake-args` | `CMAKE_ARGS`** 394 | CMake arguments to pass to `colcon build` 395 | *default:* `"-DCMAKE_BUILD_TYPE=Release"` 396 | - **`dev-image-name` | `DEV_IMAGE_NAME`** 397 | Image name of dev image 398 | *default:* `` 399 | - **`dev-image-tag` | `DEV_IMAGE_TAG`** 400 | Image tag of dev image 401 | *default:* `-dev` 402 | - **`disable-ros-installation` | `DISABLE_ROS_INSTALLATION`** 403 | Disable automatic installation of `ros-$ROS_DISTRO-ros-core` package 404 | *e.g., if ROS is already installed in `base-image` and package is not available for the OS* 405 | *default:* `false` 406 | - **`-` | `DOCKER_ROS_GIT_REF`** 407 | Git ref of *docker-ros* to run in CI 408 | *default:* `main` 409 | - **`enable-checkout` | `-`** 410 | Enable [*checkout*](https://github.com/actions/checkout) action to (re-)download your repository prior to running the pipeline 411 | *default:* `true` 412 | - **`enable-checkout-submodules` | `-`** 413 | Enable submodules for the [*checkout*](https://github.com/actions/checkout) action (`false`|`true`|`recursive`) 414 | *default:* `recursive` 415 | - **`enable-checkout-lfs` | `-`** 416 | Enable [*Git LFS*](https://git-lfs.com/) support for the [*checkout*](https://github.com/actions/checkout) action 417 | *default:* `true` 418 | - **`enable-industrial-ci` | `ENABLE_INDUSTRIAL_CI`** 419 | Enable [*industrial_ci*](https://github.com/ros-industrial/industrial_ci) 420 | *default:* `false` 421 | - **`enable-push-as-latest` | `ENABLE_PUSH_AS_LATEST`** 422 | Push images with tag `latest`/`latest-dev` in addition to the configured image names 423 | *default:* `false` 424 | - **`enable-singlearch-push` | `ENABLE_SINGLEARCH_PUSH`** 425 | Enable push of single arch images with `-amd64`/`-arm64` postfix 426 | *default:* `false` 427 | - **`enable-recursive-additional-debs` | `ENABLE_RECURSIVE_ADDITIONAL_DEBS`** 428 | Enable recursive discovery of files named `additional-debs-file` 429 | *default:* `false` 430 | - **`enable-recursive-additional-pip` | `ENABLE_RECURSIVE_ADDITIONAL_PIP`** 431 | Enable recursive discovery of files named `additional-pip-file` 432 | *default:* `false` 433 | - **`enable-recursive-after-dependency-installation-script` | `ENABLE_RECURSIVE_AFTER_DEPENDENCY_INSTALLATION_SCRIPT`** 434 | Enable recursive discovery of files named `after-dependency-installation-script` 435 | *default:* `false` 436 | - **`enable-recursive-before-dependency-installation-script` | `ENABLE_RECURSIVE_BEFORE_DEPENDENCY_INSTALLATION_SCRIPT`** 437 | Enable recursive discovery of files named `before-dependency-installation-script` 438 | *default:* `false` 439 | - **`enable-recursive-blacklisted-packages` | `ENABLE_RECURSIVE_BLACKLISTED_PACKAGES`** 440 | Enable recursive discovery of files named `blacklisted-packages-file` 441 | *default:* `false` 442 | - **`enable-recursive-vcs-import` | `ENABLE_RECURSIVE_VCS_IMPORT`** 443 | Enable recursive discovery of files named `*.repos` 444 | *default:* `true` 445 | - **`enable-slim` | `ENABLE_SLIM`** 446 | Enable an extra slimmed run image via [slim](https://github.com/slimtoolkit/slim) (only if `run` stage is targeted) 447 | *default:* `true` 448 | - **`git-https-password` | `GIT_HTTPS_PASSWORD`** 449 | Password for cloning private Git repositories via HTTPS 450 | *default:* `${{ github.token }}` | `$CI_JOB_TOKEN` 451 | - **`git-https-server` | `GIT_HTTPS_SERVER`** 452 | Server URL (without protocol) for cloning private Git repositories via HTTPS 453 | *default:* `github.com` | `$CI_SERVER_HOST:$CI_SERVER_PORT` 454 | - **`git-https-user` | `GIT_HTTPS_USER`** 455 | Username for cloning private Git repositories via HTTPS 456 | *default:* `${{ github.actor }}` | `gitlab-ci-token` 457 | - **`git-ssh-known-host-keys` | `GIT_SSH_KNOWN_HOST_KEYS`** 458 | Known SSH host keys for cloning private Git repositories via SSH (may be obtained using `ssh-keyscan`) 459 | - **`git-ssh-private-key` | `GIT_SSH_PRIVATE_KEY`** 460 | SSH private key for cloning private Git repositories via SSH 461 | - **`image-name` | `IMAGE_NAME`** 462 | Image name of run image 463 | *default:* `ghcr.io/${{ github.repository }}` | `$CI_REGISTRY_IMAGE` 464 | - **`image-tag` | `IMAGE_TAG`** 465 | Image tag of run image 466 | *default:* `latest` 467 | - **`platform` | `PLATFORM`** 468 | Target platform architecture (comma-separated list) 469 | *default:* runner architecture | `amd64` 470 | *supported values:* `amd64`, `arm64` 471 | - **`registry` | `REGISTRY`** 472 | Docker registry to push images to 473 | *default:* `ghcr.io` | `$CI_REGISTRY` 474 | - **`registry-password` | `REGISTRY_PASSWORD`** 475 | Docker registry password 476 | *default:* `${{ github.token }}` | `$CI_REGISTRY_PASSWORD` 477 | - **`registry-user` | `REGISTRY_USER`** 478 | Docker registry username 479 | *default:* `${{ github.actor }}` | `$CI_REGISTRY_USER` 480 | - **`rmw-implementation` | `RMW_IMPLEMENTATION`** 481 | ROS 2 middleware implementation 482 | *default:* `rmw_fastrtps_cpp` 483 | *supported values:* `rmw_zenoh_cpp`, `rmw_fastrtps_cpp`, `rmw_cyclonedds_cpp`, `rmw_gurumdds_cpp`, ... 484 | - **`ros-distro` | `ROS_DISTRO`** 485 | ROS Distro 486 | *required if ROS is not installed in `base-image`* 487 | *supported values:* `rolling`, ..., `noetic`, ... 488 | - **`slim-build-args` | `SLIM_BUILD_ARGS`** 489 | [Arguments to `slim build`](https://github.com/slimtoolkit/slim?tab=readme-ov-file#build-command-options) (except for `--target` and `--tag`) 490 | *default:* `--sensor-ipc-mode proxy --continue-after=10 --show-clogs --http-probe=false --include-path /opt/ros --include-path /docker-ros/ws/install` 491 | - **`slim-image-name` | `SLIM_IMAGE_NAME`** 492 | Image name of slim run image 493 | *default:* `` 494 | - **`slim-image-tag` | `SLIM_IMAGE_TAG`** 495 | Image tag of slim run image 496 | *default:* `-slim` 497 | - **`target` | `TARGET`** 498 | Target stage of Dockerfile (comma-separated list) 499 | *default:* `run` 500 | *supported values:* `dev`, `run` 501 | - **`vcs-import-file` | `VCS_IMPORT_FILE`** 502 | Relative filepath to file containing additional repos to install via vcstools (only relevant if `enable-recursive-vcs-import=false`) 503 | *default:* `.repos` 504 | -------------------------------------------------------------------------------- /action.yml: -------------------------------------------------------------------------------- 1 | name: "docker-ros" 2 | description: "docker-ros automatically builds development and deployment Docker images for your ROS-based repositories." 3 | branding: 4 | color: blue 5 | icon: package 6 | 7 | inputs: 8 | additional-debs-file: 9 | description: "Relative filepath to file containing additional apt deb packages to install" 10 | default: docker/additional-debs.txt 11 | additional-files-dir: 12 | description: "Relative path to directory containing additional files to copy into image" 13 | default: docker/additional-files 14 | additional-pip-file: 15 | description: "Relative filepath to file containing additional pip packages to install" 16 | default: docker/additional-pip-requirements.txt 17 | after-dependency-installation-script: 18 | description: "Relative filepath to script containing commands to run after dependency installation" 19 | default: docker/custom.sh 20 | base-image: 21 | description: "Base image name:tag" 22 | required: true 23 | before-dependency-identification-script: 24 | description: "Relative filepath to script containing commands to run before dependency identification" 25 | default: docker/before_dependency_identification.sh 26 | before-dependency-installation-script: 27 | description: "Relative filepath to script containing commands to run before dependency installation" 28 | default: docker/before_dependency_installation.sh 29 | blacklisted-packages-file: 30 | description: "Relative filepath to file containing blacklisted packages to remove from workspace" 31 | default: docker/blacklisted-packages.txt 32 | build-context: 33 | description: "Build context of Docker build process" 34 | default: ${{ github.workspace }} 35 | command: 36 | description: "Launch command of run image (required if target=run)" 37 | cmake-args: 38 | description: "CMake arguments to pass to `colcon build`" 39 | default: "-DCMAKE_BUILD_TYPE=Release" 40 | dev-image-name: 41 | description: "Image name of dev image" 42 | default: ghcr.io/${{ github.repository }} 43 | dev-image-tag: 44 | description: "Image tag of dev image" 45 | disable-ros-installation: 46 | description: "Disable automatic installation of `ros-$ROS_DISTRO-ros-core` package, e.g., if ROS is already installed in `base-image` and package is not available for the OS" 47 | default: false 48 | enable-checkout: 49 | description: "Enable checkout action to (re-)download your repository prior to running the pipeline" 50 | default: true 51 | enable-checkout-lfs: 52 | description: "Enable Git LFS support for the checkout action" 53 | default: true 54 | enable-checkout-submodules: 55 | description: "Enable submodules for the checkout action (false|true|recursive)" 56 | default: recursive 57 | enable-industrial-ci: 58 | description: "Enable industrial_ci" 59 | default: false 60 | enable-push-as-latest: 61 | description: "Push images with tag `latest`/`latest-dev` in addition to the configured image names" 62 | default: false 63 | enable-recursive-additional-debs: 64 | description: "Enable recursive discovery of files named `additional-debs-file`" 65 | default: false 66 | enable-recursive-additional-pip: 67 | description: "Enable recursive discovery of files named `additional-pip-file`" 68 | default: false 69 | enable-recursive-after-dependency-installation-script: 70 | description: "Enable recursive discovery of files named `after-dependency-installation-script`" 71 | default: false 72 | enable-recursive-before-dependency-installation-script: 73 | description: "Enable recursive discovery of files named `before-dependency-installation-script`" 74 | default: false 75 | enable-recursive-blacklisted-packages: 76 | description: "Enable recursive discovery of files named `blacklisted-packages-file`" 77 | default: false 78 | enable-singlearch-push: 79 | description: "Enable push of single arch images with [-amd64|-arm64] postfix" 80 | default: false 81 | enable-slim: 82 | description: "Enable an extra slimmed run image via slim (only if run stage is targeted)" 83 | default: true 84 | git-https-password: 85 | description: "Password for cloning private Git repositories via HTTPS" 86 | default: ${{ github.token }} 87 | git-https-server: 88 | description: "Server URL (without protocol) for cloning private Git repositories via HTTPS" 89 | default: "github.com" 90 | git-https-user: 91 | description: "Username for cloning private Git repositories via HTTPS" 92 | default: ${{ github.actor }} 93 | git-ssh-known-host-keys: 94 | description: "Known SSH host keys for cloning private Git repositories via SSH (may be obtained using `ssh-keyscan`)" 95 | git-ssh-private-key: 96 | description: "SSH private key for cloning private Git repositories via SSH" 97 | image-name: 98 | description: "Image name of run image" 99 | default: ghcr.io/${{ github.repository }} 100 | image-tag: 101 | description: "Image tag of run image" 102 | default: latest 103 | platform: 104 | description: "Target platform architecture (comma-separated list) [amd64|arm64|...]" 105 | registry: 106 | description: "Docker registry to push images to" 107 | default: ghcr.io 108 | registry-password: 109 | description: "Docker registry password" 110 | default: ${{ github.token }} 111 | registry-user: 112 | description: "Docker registry username" 113 | default: ${{ github.actor }} 114 | ros-distro: 115 | description: "ROS Distro (required if ROS is not installed in `base-image`)" 116 | rmw-implementation: 117 | description: "RMW implementation to use (only for ROS 2)" 118 | default: rmw_fastrtps_cpp 119 | slim-build-args: 120 | description: "Arguments to `slim build` (except for `--target` and `--tag`)" 121 | default: '--sensor-ipc-mode proxy --continue-after=10 --show-clogs --http-probe=false --include-path /opt/ros --include-path /docker-ros/ws/install' 122 | slim-image-name: 123 | description: "Image name of slim run image" 124 | default: ghcr.io/${{ github.repository }} 125 | slim-image-tag: 126 | description: "Image tag of slim run image" 127 | target: 128 | description: "Target stage of Dockerfile (comma-separated list) [dev|run]" 129 | default: run 130 | 131 | 132 | runs: 133 | using: "composite" 134 | steps: 135 | 136 | - name: Checkout repository 137 | uses: actions/checkout@v3 138 | if: ${{ inputs.enable-checkout == 'true' }} 139 | with: 140 | submodules: ${{ inputs.enable-checkout-submodules }} 141 | lfs: ${{ inputs.enable-checkout-lfs }} 142 | 143 | - name: Set up docker-ros 144 | shell: bash 145 | working-directory: ${{ inputs.build-context }} 146 | run: | 147 | if ! [[ -d "docker/docker-ros" ]]; then 148 | mkdir -p docker 149 | cp -r ${GITHUB_ACTION_PATH} docker/docker-ros 150 | fi 151 | 152 | - name: Prepare setup of QEMU 153 | id: prepare-setup-of-qemu 154 | shell: bash 155 | run: echo "RUNNER_ARCH=$(dpkg --print-architecture)" >> $GITHUB_OUTPUT 156 | 157 | - name: Set up QEMU 158 | if: ${{ steps.prepare-setup-of-qemu.outputs.RUNNER_ARCH != inputs.platform }} 159 | uses: docker/setup-qemu-action@v2 160 | 161 | - name: Login to Docker registry 162 | uses: docker/login-action@v3 163 | with: 164 | registry: ${{ inputs.registry }} 165 | username: ${{ inputs.registry-user }} 166 | password: ${{ inputs.registry-password }} 167 | 168 | - name: Set up Docker buildx 169 | uses: docker/setup-buildx-action@v3 170 | 171 | - name: Enforce lower-case image name 172 | id: image-name 173 | uses: ASzc/change-string-case-action@v6 174 | with: 175 | string: ${{ inputs.image-name }} 176 | 177 | - name: Enforce lower-case dev image name 178 | id: dev-image-name 179 | uses: ASzc/change-string-case-action@v6 180 | with: 181 | string: ${{ inputs.dev-image-name }} 182 | 183 | - name: Enforce lower-case slim image name 184 | id: slim-image-name 185 | uses: ASzc/change-string-case-action@v6 186 | with: 187 | string: ${{ inputs.slim-image-name }} 188 | 189 | - name: Build images 190 | id: build-images 191 | shell: bash 192 | working-directory: ${{ inputs.build-context }} 193 | run: docker/docker-ros/scripts/ci.sh 194 | env: 195 | ADDITIONAL_DEBS_FILE: ${{ inputs.additional-debs-file }} 196 | ADDITIONAL_FILES_DIR: ${{ inputs.additional-files-dir }} 197 | ADDITIONAL_PIP_FILE: ${{ inputs.additional-pip-file }} 198 | AFTER_DEPENDENCY_INSTALLATION_SCRIPT: ${{ inputs.after-dependency-installation-script }} 199 | BASE_IMAGE: ${{ inputs.base-image }} 200 | BEFORE_DEPENDENCY_IDENTIFICATION_SCRIPT: ${{ inputs.before-dependency-identification-script }} 201 | BEFORE_DEPENDENCY_INSTALLATION_SCRIPT: ${{ inputs.before-dependency-installation-script }} 202 | BLACKLISTED_PACKAGES_FILE: ${{ inputs.blacklisted-packages-file }} 203 | COMMAND: ${{ inputs.command }} 204 | CMAKE_ARGS: ${{ inputs.cmake-args }} 205 | DEV_IMAGE_NAME: ${{ steps.dev-image-name.outputs.lowercase }} 206 | DEV_IMAGE_TAG: ${{ inputs.dev-image-tag }} 207 | DISABLE_ROS_INSTALLATION: ${{ inputs.disable-ros-installation }} 208 | ENABLE_RECURSIVE_ADDITIONAL_DEBS: ${{ inputs.enable-recursive-additional-debs }} 209 | ENABLE_RECURSIVE_ADDITIONAL_PIP: ${{ inputs.enable-recursive-additional-pip }} 210 | ENABLE_RECURSIVE_AFTER_DEPENDENCY_INSTALLATION_SCRIPT: ${{ inputs.enable-recursive-after-dependency-installation-script }} 211 | ENABLE_RECURSIVE_BEFORE_DEPENDENCY_INSTALLATION_SCRIPT: ${{ inputs.enable-recursive-before-dependency-installation-script }} 212 | ENABLE_RECURSIVE_BLACKLISTED_PACKAGES: ${{ inputs.enable-recursive-blacklisted-packages }} 213 | ENABLE_RECURSIVE_VCS_IMPORT: ${{ inputs.enable-recursive-vcs-import }} 214 | ENABLE_SLIM: ${{ inputs.enable-slim }} 215 | GIT_HTTPS_PASSWORD: ${{ inputs.git-https-password }} 216 | GIT_HTTPS_SERVER: ${{ inputs.git-https-server }} 217 | GIT_HTTPS_USER: ${{ inputs.git-https-user }} 218 | GIT_SSH_KNOWN_HOST_KEYS: ${{ inputs.git-ssh-known-host-keys }} 219 | GIT_SSH_PRIVATE_KEY: ${{ inputs.git-ssh-private-key }} 220 | IMAGE_NAME: ${{ steps.image-name.outputs.lowercase }} 221 | IMAGE_TAG: ${{ inputs.image-tag }} 222 | PLATFORM: ${{ inputs.platform }} 223 | RMW_IMPLEMENTATION: ${{ inputs.rmw-implementation }} 224 | ROS_DISTRO: ${{ inputs.ros-distro }} 225 | SLIM_BUILD_ARGS: ${{ inputs.slim-build-args }} 226 | SLIM_IMAGE_NAME: ${{ steps.slim-image-name.outputs.lowercase }} 227 | SLIM_IMAGE_TAG: ${{ inputs.slim-image-tag }} 228 | TARGET: ${{ inputs.target }} 229 | VCS_IMPORT_FILE: ${{ inputs.vcs-import-file }} 230 | 231 | - name: Set up industrial_ci 232 | if: ${{ inputs.enable-industrial-ci == 'true' }} 233 | shell: bash 234 | run: test -f ${{ inputs.build-context }}/.repos || echo "repositories:" > ${{ inputs.build-context }}/.repos 235 | 236 | - name: Run industrial_ci 237 | if: ${{ inputs.enable-industrial-ci == 'true' }} 238 | uses: ros-industrial/industrial_ci@master 239 | env: 240 | ADDITIONAL_DEBS: git 241 | AFTER_INIT_EMBED: '[[ -n ${{ inputs.git-https-server }} ]] && git config --global url.https://${{ inputs.git-https-user }}:${{ inputs.git-https-password }}@${{ inputs.git-https-server }}.insteadOf https://${{ inputs.git-https-server }}' 242 | DOCKER_IMAGE: ${{ steps.build-images.outputs.INDUSTRIAL_CI_IMAGE }} 243 | DOCKER_PULL: false 244 | DOCKER_RUN_OPTS: -u root:root 245 | SSH_PRIVATE_KEY: ${{ inputs.git-ssh-private-key }} 246 | SSH_SERVER_HOSTKEYS: ${{ inputs.git-ssh-known-host-keys }} 247 | TARGET_WORKSPACE: ${{ inputs.build-context }} 248 | UPSTREAM_WORKSPACE: ${{ inputs.build-context }}/.repos 249 | 250 | - name: Slugify ref name 251 | id: slugify-ref-name 252 | uses: gacts/github-slug@v1 253 | with: 254 | to-slug: ${{ github.ref_name }} 255 | 256 | - name: Push images 257 | shell: bash 258 | working-directory: ${{ inputs.build-context }} 259 | run: docker/docker-ros/scripts/ci.sh 260 | env: 261 | ADDITIONAL_DEBS_FILE: ${{ inputs.additional-debs-file }} 262 | ADDITIONAL_FILES_DIR: ${{ inputs.additional-files-dir }} 263 | ADDITIONAL_PIP_FILE: ${{ inputs.additional-pip-file }} 264 | AFTER_DEPENDENCY_INSTALLATION_SCRIPT: ${{ inputs.after-dependency-installation-script }} 265 | BASE_IMAGE: ${{ inputs.base-image }} 266 | BEFORE_DEPENDENCY_IDENTIFICATION_SCRIPT: ${{ inputs.before-dependency-identification-script }} 267 | BEFORE_DEPENDENCY_INSTALLATION_SCRIPT: ${{ inputs.before-dependency-installation-script }} 268 | BLACKLISTED_PACKAGES_FILE: ${{ inputs.blacklisted-packages-file }} 269 | COMMAND: ${{ inputs.command }} 270 | CMAKE_ARGS: ${{ inputs.cmake-args }} 271 | DEV_IMAGE_NAME: ${{ steps.dev-image-name.outputs.lowercase }} 272 | DEV_IMAGE_TAG: ${{ inputs.dev-image-tag }} 273 | DISABLE_ROS_INSTALLATION: ${{ inputs.disable-ros-installation }} 274 | ENABLE_RECURSIVE_ADDITIONAL_DEBS: ${{ inputs.enable-recursive-additional-debs }} 275 | ENABLE_RECURSIVE_ADDITIONAL_PIP: ${{ inputs.enable-recursive-additional-pip }} 276 | ENABLE_RECURSIVE_AFTER_DEPENDENCY_INSTALLATION_SCRIPT: ${{ inputs.enable-recursive-after-dependency-installation-script }} 277 | ENABLE_RECURSIVE_BEFORE_DEPENDENCY_INSTALLATION_SCRIPT: ${{ inputs.enable-recursive-before-dependency-installation-script }} 278 | ENABLE_RECURSIVE_BLACKLISTED_PACKAGES: ${{ inputs.enable-recursive-blacklisted-packages }} 279 | ENABLE_RECURSIVE_VCS_IMPORT: ${{ inputs.enable-recursive-vcs-import }} 280 | ENABLE_SINGLEARCH_PUSH: ${{ inputs.enable-singlearch-push }} 281 | ENABLE_SLIM: ${{ inputs.enable-slim }} 282 | GIT_HTTPS_PASSWORD: ${{ inputs.git-https-password }} 283 | GIT_HTTPS_SERVER: ${{ inputs.git-https-server }} 284 | GIT_HTTPS_USER: ${{ inputs.git-https-user }} 285 | GIT_SSH_KNOWN_HOST_KEYS: ${{ inputs.git-ssh-known-host-keys }} 286 | GIT_SSH_PRIVATE_KEY: ${{ inputs.git-ssh-private-key }} 287 | IMAGE_NAME: ${{ steps.image-name.outputs.lowercase }} 288 | IMAGE_TAG: ${{ inputs.image-tag }} 289 | PLATFORM: ${{ inputs.platform }} 290 | RMW_IMPLEMENTATION: ${{ inputs.rmw-implementation }} 291 | ROS_DISTRO: ${{ inputs.ros-distro }} 292 | SLIM_BUILD_ARGS: ${{ inputs.slim-build-args }} 293 | SLIM_IMAGE_NAME: ${{ steps.slim-image-name.outputs.lowercase }} 294 | SLIM_IMAGE_TAG: ${{ inputs.slim-image-tag }} 295 | TARGET: ${{ inputs.target }} 296 | VCS_IMPORT_FILE: ${{ inputs.vcs-import-file }} 297 | _ENABLE_IMAGE_PUSH: true 298 | _IMAGE_POSTFIX: ${{ github.ref != format('refs/heads/{0}', github.event.repository.default_branch) && format('_{0}_ci', steps.slugify-ref-name.outputs.slug) || '' }} 299 | 300 | - name: Push images (as latest) 301 | if: ${{ inputs.enable-push-as-latest == 'true' }} 302 | shell: bash 303 | working-directory: ${{ inputs.build-context }} 304 | run: docker/docker-ros/scripts/ci.sh 305 | env: 306 | ADDITIONAL_DEBS_FILE: ${{ inputs.additional-debs-file }} 307 | ADDITIONAL_FILES_DIR: ${{ inputs.additional-files-dir }} 308 | ADDITIONAL_PIP_FILE: ${{ inputs.additional-pip-file }} 309 | AFTER_DEPENDENCY_INSTALLATION_SCRIPT: ${{ inputs.after-dependency-installation-script }} 310 | BASE_IMAGE: ${{ inputs.base-image }} 311 | BEFORE_DEPENDENCY_IDENTIFICATION_SCRIPT: ${{ inputs.before-dependency-identification-script }} 312 | BEFORE_DEPENDENCY_INSTALLATION_SCRIPT: ${{ inputs.before-dependency-installation-script }} 313 | BLACKLISTED_PACKAGES_FILE: ${{ inputs.blacklisted-packages-file }} 314 | COMMAND: ${{ inputs.command }} 315 | CMAKE_ARGS: ${{ inputs.cmake-args }} 316 | DEV_IMAGE_NAME: ${{ steps.dev-image-name.outputs.lowercase }} 317 | DEV_IMAGE_TAG: latest-dev 318 | DISABLE_ROS_INSTALLATION: ${{ inputs.disable-ros-installation }} 319 | ENABLE_RECURSIVE_ADDITIONAL_DEBS: ${{ inputs.enable-recursive-additional-debs }} 320 | ENABLE_RECURSIVE_ADDITIONAL_PIP: ${{ inputs.enable-recursive-additional-pip }} 321 | ENABLE_RECURSIVE_AFTER_DEPENDENCY_INSTALLATION_SCRIPT: ${{ inputs.enable-recursive-after-dependency-installation-script }} 322 | ENABLE_RECURSIVE_BEFORE_DEPENDENCY_INSTALLATION_SCRIPT: ${{ inputs.enable-recursive-before-dependency-installation-script }} 323 | ENABLE_RECURSIVE_BLACKLISTED_PACKAGES: ${{ inputs.enable-recursive-blacklisted-packages }} 324 | ENABLE_RECURSIVE_VCS_IMPORT: ${{ inputs.enable-recursive-vcs-import }} 325 | ENABLE_SINGLEARCH_PUSH: ${{ inputs.enable-singlearch-push }} 326 | ENABLE_SLIM: ${{ inputs.enable-slim }} 327 | GIT_HTTPS_PASSWORD: ${{ inputs.git-https-password }} 328 | GIT_HTTPS_SERVER: ${{ inputs.git-https-server }} 329 | GIT_HTTPS_USER: ${{ inputs.git-https-user }} 330 | GIT_SSH_KNOWN_HOST_KEYS: ${{ inputs.git-ssh-known-host-keys }} 331 | GIT_SSH_PRIVATE_KEY: ${{ inputs.git-ssh-private-key }} 332 | IMAGE_NAME: ${{ steps.image-name.outputs.lowercase }} 333 | IMAGE_TAG: latest 334 | PLATFORM: ${{ inputs.platform }} 335 | RMW_IMPLEMENTATION: ${{ inputs.rmw-implementation }} 336 | ROS_DISTRO: ${{ inputs.ros-distro }} 337 | SLIM_BUILD_ARGS: ${{ inputs.slim-build-args }} 338 | SLIM_IMAGE_NAME: ${{ steps.slim-image-name.outputs.lowercase }} 339 | SLIM_IMAGE_TAG: latest-slim 340 | TARGET: ${{ inputs.target }} 341 | VCS_IMPORT_FILE: ${{ inputs.vcs-import-file }} 342 | _ENABLE_IMAGE_PUSH: true 343 | _IMAGE_POSTFIX: ${{ github.ref != format('refs/heads/{0}', github.event.repository.default_branch) && format('_{0}_ci', steps.slugify-ref-name.outputs.slug) || '' }} 344 | -------------------------------------------------------------------------------- /assets/banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ika-rwth-aachen/docker-ros/dd00b8cf2c001afa1d2ba87cc5250dd577733080/assets/banner.png -------------------------------------------------------------------------------- /assets/logo-midjourney.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ika-rwth-aachen/docker-ros/dd00b8cf2c001afa1d2ba87cc5250dd577733080/assets/logo-midjourney.png -------------------------------------------------------------------------------- /assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ika-rwth-aachen/docker-ros/dd00b8cf2c001afa1d2ba87cc5250dd577733080/assets/logo.png -------------------------------------------------------------------------------- /docker/Dockerfile: -------------------------------------------------------------------------------- 1 | ARG BASE_IMAGE 2 | 3 | ############ dependencies ###################################################### 4 | FROM ${BASE_IMAGE} AS dependencies 5 | ARG TARGETARCH 6 | ENV TARGETARCH=${TARGETARCH} 7 | 8 | USER root 9 | SHELL ["/bin/bash", "-c"] 10 | ARG DEBIAN_FRONTEND=noninteractive 11 | 12 | # create workspace folder structure 13 | ENV WORKSPACE=/docker-ros/ws 14 | WORKDIR $WORKSPACE 15 | RUN mkdir -p src/target src/upstream src/downstream 16 | 17 | # setup keys and sources.list for ROS packages 18 | ARG ROS_DISTRO 19 | ENV ROS_DISTRO=${ROS_DISTRO} 20 | RUN test -n "$ROS_DISTRO" || (echo "missing build-arg: ROS_DISTRO" && false) 21 | RUN apt-get update && \ 22 | apt-get install -y curl gnupg && \ 23 | if ! [[ -n $(find /etc/apt/sources.list.d/ -name "*ros2*") ]]; then \ 24 | ROS_APT_SOURCE_VERSION=$(curl -s https://api.github.com/repos/ros-infrastructure/ros-apt-source/releases/latest | grep -F "tag_name" | awk -F\" '{print $4}') && \ 25 | curl -L -o /tmp/ros2-apt-source.deb "https://github.com/ros-infrastructure/ros-apt-source/releases/download/${ROS_APT_SOURCE_VERSION}/ros2-apt-source_${ROS_APT_SOURCE_VERSION}.$(. /etc/os-release && echo $UBUNTU_CODENAME)_all.deb" && \ 26 | apt-get install -y /tmp/ros2-apt-source.deb ; \ 27 | fi && \ 28 | rm -rf /var/lib/apt/lists/* 29 | 30 | # install ROS bootstrapping tools 31 | ARG DISABLE_ROS_INSTALLATION="false" 32 | RUN apt-get update && \ 33 | apt-get install -y \ 34 | git \ 35 | python3-rosdep \ 36 | python3-vcstool && \ 37 | if [[ "$DISABLE_ROS_INSTALLATION" != "true" ]]; then \ 38 | apt-get install -y ros-${ROS_DISTRO}-ros-core; \ 39 | fi && \ 40 | rm -rf /var/lib/apt/lists/* 41 | 42 | # copy contents of repository 43 | COPY . src/target 44 | 45 | # run custom script before dependency identification 46 | ARG BEFORE_DEPENDENCY_IDENTIFICATION_SCRIPT="docker/before_dependency_identification.sh" 47 | RUN if [[ -f src/target/${BEFORE_DEPENDENCY_IDENTIFICATION_SCRIPT} ]]; then \ 48 | chmod +x src/target/${BEFORE_DEPENDENCY_IDENTIFICATION_SCRIPT} && \ 49 | src/target/${BEFORE_DEPENDENCY_IDENTIFICATION_SCRIPT} ; \ 50 | fi 51 | 52 | # clone .repos upstream dependencies 53 | ARG GIT_HTTPS_SERVER= 54 | ARG GIT_HTTPS_USER= 55 | ARG GIT_HTTPS_PASSWORD= 56 | RUN if [[ -n ${GIT_HTTPS_SERVER} ]]; then \ 57 | git config --global url.https://${GIT_HTTPS_USER}:${GIT_HTTPS_PASSWORD}@${GIT_HTTPS_SERVER}.insteadOf https://${GIT_HTTPS_SERVER} ; \ 58 | fi 59 | ARG GIT_SSH_PRIVATE_KEY= 60 | ARG GIT_SSH_KNOWN_HOST_KEYS= 61 | RUN if [[ -n ${GIT_SSH_PRIVATE_KEY} ]]; then \ 62 | echo -e ${GIT_SSH_PRIVATE_KEY} > /tmp/ssh_id && \ 63 | chmod 400 /tmp/ssh_id && \ 64 | git config --global core.sshCommand "ssh -i /.ssh_key" && \ 65 | mkdir -p ~/.ssh && \ 66 | touch ~/.ssh/known_hosts && \ 67 | echo -e ${GIT_SSH_KNOWN_HOST_KEYS} >> ~/.ssh/known_hosts ; \ 68 | fi 69 | COPY docker/docker-ros/docker/recursive_vcs_import.py /usr/local/bin 70 | RUN apt-get update && \ 71 | apt-get install -y python-is-python3 && \ 72 | rm -rf /var/lib/apt/lists/* 73 | 74 | ARG VCS_IMPORT_FILE=".repos" 75 | ARG ENABLE_RECURSIVE_VCS_IMPORT="true" 76 | RUN if [[ $ENABLE_RECURSIVE_VCS_IMPORT == 'true' ]]; then \ 77 | /usr/local/bin/recursive_vcs_import.py src src/upstream ; \ 78 | elif [[ -f src/target/${VCS_IMPORT_FILE} ]]; then \ 79 | vcs import --recursive src/upstream < src/target/${VCS_IMPORT_FILE} ; \ 80 | fi 81 | 82 | # remove blacklisted packages from workspace 83 | ARG BLACKLISTED_PACKAGES_FILE="docker/blacklisted-packages.txt" 84 | ARG ENABLE_RECURSIVE_BLACKLISTED_PACKAGES="false" 85 | RUN echo "colcon list -p --base-paths src/ --packages-select \\" >> $WORKSPACE/.remove-packages.sh && \ 86 | if [[ $ENABLE_RECURSIVE_BLACKLISTED_PACKAGES == 'true' ]]; then \ 87 | find . -type f -name $(basename ${BLACKLISTED_PACKAGES_FILE}) -exec sed '$a\' {} \; | awk '{print " " $0 " \\"}' >> $WORKSPACE/.remove-packages.sh ; \ 88 | elif [[ -f src/target/${BLACKLISTED_PACKAGES_FILE} ]]; then \ 89 | cat src/target/${BLACKLISTED_PACKAGES_FILE} | awk '{ gsub(/#.*/, ""); gsub(/^[ \t]+|[ \t]+$/, ""); if (NF) print " " $0 " \\" }' >> $WORKSPACE/.remove-packages.sh ; \ 90 | fi && \ 91 | echo ";" >> $WORKSPACE/.remove-packages.sh && \ 92 | chmod +x $WORKSPACE/.remove-packages.sh && \ 93 | $WORKSPACE/.remove-packages.sh 2> /dev/null | xargs rm -rf 94 | 95 | # create install script to run in dependencies-install stage 96 | RUN echo "set -e" > $WORKSPACE/.install-dependencies.sh && \ 97 | chmod +x $WORKSPACE/.install-dependencies.sh 98 | 99 | # add custom script to run before dependency installation to install script 100 | ARG BEFORE_DEPENDENCY_INSTALLATION_SCRIPT="docker/before_dependency_installation.sh" 101 | ARG ENABLE_RECURSIVE_BEFORE_DEPENDENCY_INSTALLATION_SCRIPT="false" 102 | RUN if [[ $ENABLE_RECURSIVE_BEFORE_DEPENDENCY_INSTALLATION_SCRIPT == 'true' ]]; then \ 103 | find . -type f -name $(basename ${BEFORE_DEPENDENCY_INSTALLATION_SCRIPT}) -exec sed '$a\' {} >> $WORKSPACE/.install-dependencies.sh \; ; \ 104 | elif [[ -f src/target/${BEFORE_DEPENDENCY_INSTALLATION_SCRIPT} ]]; then \ 105 | cat src/target/${BEFORE_DEPENDENCY_INSTALLATION_SCRIPT} >> $WORKSPACE/.install-dependencies.sh && \ 106 | echo "" >> $WORKSPACE/.install-dependencies.sh ; \ 107 | fi 108 | 109 | # add list of rosdep dependencies to install script 110 | RUN source /opt/ros/$ROS_DISTRO/setup.bash && \ 111 | apt-get update && \ 112 | (rosdep init || true) && \ 113 | rosdep update --rosdistro ${ROS_DISTRO} && \ 114 | export OS="ubuntu:$(lsb_release -c | awk '{print $2}')" && \ 115 | if [[ "$ROS_DISTRO" = "rolling" && "$OS" = "ubuntu:focal" ]]; then export OS="ubuntu:jammy"; fi && \ 116 | set -o pipefail && \ 117 | PIP_BREAK_SYSTEM_PACKAGES=1 ROS_PACKAGE_PATH=$(pwd):$ROS_PACKAGE_PATH rosdep install --os $OS -y --simulate --from-paths src --ignore-src \ 118 | | sed -E "s/'apt-get install -y ([^']+)' \(alternative 1\/1\)/apt-get install -y \1/" \ 119 | | tee -a $WORKSPACE/.install-dependencies.sh && \ 120 | rm -rf /var/lib/apt/lists/* 121 | 122 | # add additionally specified apt dependencies to install script 123 | ARG ADDITIONAL_DEBS_FILE="docker/additional-debs.txt" 124 | ARG ENABLE_RECURSIVE_ADDITIONAL_DEBS="false" 125 | RUN echo "apt-get install -y \\" >> $WORKSPACE/.install-dependencies.sh && \ 126 | set -o pipefail && \ 127 | if [[ $ENABLE_RECURSIVE_ADDITIONAL_DEBS == 'true' ]]; then \ 128 | find . -type f -name $(basename ${ADDITIONAL_DEBS_FILE}) -exec sed '$a\' {} \; | awk '{print " " $0 " \\"}' >> $WORKSPACE/.install-dependencies.sh ; \ 129 | elif [[ -f src/target/${ADDITIONAL_DEBS_FILE} ]]; then \ 130 | cat src/target/${ADDITIONAL_DEBS_FILE} | awk '{ gsub(/#.*/, ""); gsub(/^[ \t]+|[ \t]+$/, ""); if (NF) print " " $0 " \\" }' >> $WORKSPACE/.install-dependencies.sh ; \ 131 | fi && \ 132 | echo ";" >> $WORKSPACE/.install-dependencies.sh 133 | 134 | # add additionally specified pip dependencies to install script 135 | ARG ADDITIONAL_PIP_FILE="docker/additional-pip-requirements.txt" 136 | ARG ENABLE_RECURSIVE_ADDITIONAL_PIP="false" 137 | RUN echo "pip install pip \\" >> $WORKSPACE/.install-dependencies.sh && \ 138 | set -o pipefail && \ 139 | if [[ $ENABLE_RECURSIVE_ADDITIONAL_PIP == 'true' ]]; then \ 140 | find . -type f -name $(basename ${ADDITIONAL_PIP_FILE}) -exec sed '$a\' {} \; | awk '{print " " $0 " \\"}' >> $WORKSPACE/.install-dependencies.sh ; \ 141 | elif [[ -f src/target/${ADDITIONAL_PIP_FILE} ]]; then \ 142 | cat src/target/${ADDITIONAL_PIP_FILE} | awk '{ gsub(/#.*/, ""); gsub(/^[ \t]+|[ \t]+$/, ""); if (NF) print " " $0 " \\" }' >> $WORKSPACE/.install-dependencies.sh ; \ 143 | fi && \ 144 | echo ";" >> $WORKSPACE/.install-dependencies.sh 145 | 146 | # add custom script to run after dependency installation to install script 147 | ARG AFTER_DEPENDENCY_INSTALLATION_SCRIPT="docker/custom.sh" 148 | ARG ENABLE_RECURSIVE_AFTER_DEPENDENCY_INSTALLATION_SCRIPT="false" 149 | RUN if [[ $ENABLE_RECURSIVE_AFTER_DEPENDENCY_INSTALLATION_SCRIPT == 'true' ]]; then \ 150 | find . -type f -name $(basename ${AFTER_DEPENDENCY_INSTALLATION_SCRIPT}) -exec sed '$a\' {} >> $WORKSPACE/.install-dependencies.sh \; ; \ 151 | elif [[ -f src/target/${AFTER_DEPENDENCY_INSTALLATION_SCRIPT} ]]; then \ 152 | cat src/target/${AFTER_DEPENDENCY_INSTALLATION_SCRIPT} >> $WORKSPACE/.install-dependencies.sh ; \ 153 | fi 154 | 155 | # log install script 156 | RUN cat $WORKSPACE/.install-dependencies.sh 157 | 158 | # remove additional-files folder from copied repository content to avoid redundancies 159 | ARG ADDITIONAL_FILES_DIR="docker/additional-files" 160 | RUN rm -rf src/target/${ADDITIONAL_FILES_DIR} 161 | 162 | ############ dependencies-install ############################################## 163 | FROM ${BASE_IMAGE} AS dependencies-install 164 | ARG TARGETARCH 165 | ARG GIT_HTTPS_URL 166 | ARG GIT_HTTPS_USER 167 | ARG GIT_HTTPS_PASSWORD 168 | ENV TARGETARCH=${TARGETARCH} 169 | ENV DOCKER_ROS=1 170 | 171 | USER root 172 | SHELL ["/bin/bash", "-c"] 173 | ARG DEBIAN_FRONTEND=noninteractive 174 | 175 | # user setup 176 | ENV DOCKER_USER=dockeruser 177 | ENV DOCKER_UID= 178 | ENV DOCKER_GID= 179 | 180 | # ROS setup 181 | ENV RCUTILS_COLORIZED_OUTPUT=1 182 | ENV WORKSPACE=/docker-ros/ws 183 | ENV COLCON_HOME=$WORKSPACE/.colcon 184 | WORKDIR $WORKSPACE 185 | 186 | # setup keys and sources.list for ROS packages 187 | ARG ROS_DISTRO 188 | ENV ROS_DISTRO=${ROS_DISTRO} 189 | RUN test -n "$ROS_DISTRO" || (echo "missing build-arg: ROS_DISTRO" && false) 190 | RUN apt-get update && \ 191 | apt-get install -y curl gnupg && \ 192 | if ! [[ -n $(find /etc/apt/sources.list.d/ -name "*ros2*") ]]; then \ 193 | ROS_APT_SOURCE_VERSION=$(curl -s https://api.github.com/repos/ros-infrastructure/ros-apt-source/releases/latest | grep -F "tag_name" | awk -F\" '{print $4}') && \ 194 | curl -L -o /tmp/ros2-apt-source.deb "https://github.com/ros-infrastructure/ros-apt-source/releases/download/${ROS_APT_SOURCE_VERSION}/ros2-apt-source_${ROS_APT_SOURCE_VERSION}.$(. /etc/os-release && echo $UBUNTU_CODENAME)_all.deb" && \ 195 | apt-get install -y /tmp/ros2-apt-source.deb ; \ 196 | fi && \ 197 | rm -rf /var/lib/apt/lists/* 198 | 199 | # copy contents of files-folder into image 200 | ARG ADDITIONAL_FILES_DIR="docker/additional-files" 201 | ADD ${ADDITIONAL_FILES_DIR}* /docker-ros/additional-files/ 202 | 203 | # install essential build tools and dependencies for install script 204 | ARG DISABLE_ROS_INSTALLATION="false" 205 | RUN apt-get update && \ 206 | apt-get install -y \ 207 | build-essential \ 208 | gosu \ 209 | python-is-python3 \ 210 | python3-pip && \ 211 | if [[ "$DISABLE_ROS_INSTALLATION" != "true" ]]; then \ 212 | apt-get install -y ros-${ROS_DISTRO}-ros-core; \ 213 | fi && \ 214 | rm -rf /var/lib/apt/lists/* 215 | 216 | # configure pip 217 | RUN python -m pip config --global set global.break-system-packages true 218 | 219 | # copy install script from dependencies stage 220 | COPY --from=dependencies $WORKSPACE/.install-dependencies.sh $WORKSPACE/.install-dependencies.sh 221 | 222 | # install dependencies 223 | RUN apt-get update && \ 224 | $WORKSPACE/.install-dependencies.sh && \ 225 | rm -rf /var/lib/apt/lists/* 226 | 227 | # install ROS CLI tools 228 | RUN source /opt/ros/$ROS_DISTRO/setup.bash && \ 229 | apt-get update && \ 230 | apt-get install -y python3-colcon-common-extensions && \ 231 | rm -rf /var/lib/apt/lists/* 232 | 233 | # source ROS 234 | RUN echo "source /opt/ros/$ROS_DISTRO/setup.bash" >> ~/.bashrc 235 | 236 | # install desired ROS 2 middleware 237 | ARG RMW_IMPLEMENTATION 238 | ENV RMW_IMPLEMENTATION=${RMW_IMPLEMENTATION:-rmw_fastrtps_cpp} 239 | RUN source /opt/ros/$ROS_DISTRO/setup.bash && \ 240 | if [[ "$DISABLE_ROS_INSTALLATION" != "true" ]]; then \ 241 | apt-get update && \ 242 | RMW_PACKAGE=ros-$ROS_DISTRO-$(echo $RMW_IMPLEMENTATION | tr '_' '-') && \ 243 | apt-get install -y $RMW_PACKAGE && \ 244 | rm -rf /var/lib/apt/lists/* ; \ 245 | fi 246 | 247 | # move existing install space from base image to make room for new one 248 | RUN if [[ -d $WORKSPACE/install ]]; then \ 249 | mkdir -p /opt/ws_base_image && \ 250 | mv $WORKSPACE/install /opt/ws_base_image/ && \ 251 | echo "source /opt/ws_base_image/install/setup.bash" >> ~/.bashrc ; \ 252 | fi 253 | 254 | # set entrypoint 255 | ENV TINI_VERSION=v0.19.0 256 | ADD https://github.com/krallin/tini/releases/download/${TINI_VERSION}/tini-${TARGETARCH} /tini 257 | RUN chmod +x /tini 258 | COPY docker/docker-ros/docker/entrypoint.sh / 259 | ENTRYPOINT ["/tini", "--", "/entrypoint.sh"] 260 | 261 | ############ dev ############################################################### 262 | FROM dependencies-install AS dev 263 | 264 | # copy contents of repository from dependencies stage 265 | COPY --from=dependencies $WORKSPACE/src $WORKSPACE/src 266 | 267 | CMD ["bash"] 268 | 269 | ############ build ############################################################# 270 | FROM dev AS build 271 | 272 | ARG CMAKE_ARGS="-DCMAKE_BUILD_TYPE=Release" 273 | # build ROS workspace 274 | RUN source /opt/ros/${ROS_DISTRO}/setup.bash && \ 275 | [[ -f /opt/ws_base_image/install/setup.bash ]] && source /opt/ws_base_image/install/setup.bash ; \ 276 | colcon build --cmake-args ${CMAKE_ARGS} 277 | 278 | ############ run ############################################################### 279 | FROM dependencies-install AS run 280 | 281 | # remove source code, if still existing from custom.sh modifications 282 | RUN rm -rf $WORKSPACE/src 283 | 284 | # copy ROS install space from build stage 285 | COPY --from=build $WORKSPACE/install install 286 | RUN ldconfig 287 | RUN echo "[[ -f $WORKSPACE/install/setup.bash ]] && source $WORKSPACE/install/setup.bash" >> ~/.bashrc 288 | 289 | # setup command 290 | ARG COMMAND 291 | ENV DEFAULT_CMD=${COMMAND} 292 | CMD bash -c "${DEFAULT_CMD}" 293 | -------------------------------------------------------------------------------- /docker/entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | # source ROS workspace 5 | source /opt/ros/$ROS_DISTRO/setup.bash 6 | [[ -f /opt/ws_base_image/install/setup.bash ]] && source /opt/ws_base_image/install/setup.bash 7 | [[ -f $WORKSPACE/install/setup.bash ]] && source $WORKSPACE/install/setup.bash 8 | 9 | # exec as dockeruser with configured UID/GID 10 | if [[ $DOCKER_UID && $DOCKER_GID ]]; then 11 | if ! getent group $DOCKER_GID > /dev/null 2>&1; then 12 | groupadd -g $DOCKER_GID $DOCKER_USER 13 | else 14 | echo -e "\e[33mWARNING | Cannot create group '$DOCKER_USER' with GID $DOCKER_GID, another group '$(getent group $DOCKER_GID | cut -d: -f1)' with same GID is already existing\e[0m" 15 | fi 16 | if ! getent passwd $DOCKER_UID > /dev/null 2>&1; then 17 | useradd -s /bin/bash \ 18 | -u $DOCKER_UID \ 19 | -g $DOCKER_GID \ 20 | --create-home \ 21 | --home-dir /home/$DOCKER_USER \ 22 | --groups sudo,video \ 23 | --password "$(openssl passwd -1 $DOCKER_USER)" \ 24 | $DOCKER_USER && \ 25 | touch /home/$DOCKER_USER/.sudo_as_admin_successful 26 | cp /root/.bashrc /home/$DOCKER_USER 27 | ln -s $WORKSPACE /home/$DOCKER_USER/ws 28 | chown -h $DOCKER_UID:$DOCKER_GID $WORKSPACE /home/$DOCKER_USER/ws /home/$DOCKER_USER/.sudo_as_admin_successful 29 | if [[ -d $WORKSPACE/src ]]; then 30 | chown -R $DOCKER_UID:$DOCKER_GID $WORKSPACE/src 31 | fi 32 | else 33 | echo -e "\e[33mWARNING | Cannot create user '$DOCKER_USER' with UID $DOCKER_UID, another user '$(getent passwd $DOCKER_UID | cut -d: -f1)' with same UID is already existing\e[0m" 34 | fi 35 | [[ $(pwd) == "$WORKSPACE" ]] && cd /home/$DOCKER_USER/ws 36 | exec gosu $DOCKER_USER "$@" 37 | else 38 | exec "$@" 39 | fi 40 | -------------------------------------------------------------------------------- /docker/recursive_vcs_import.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import pathlib 4 | import subprocess 5 | import sys 6 | from typing import List, Optional 7 | 8 | 9 | def find_dot_repos(search_path: str, clone_path: Optional[str] = None) -> List[pathlib.Path]: 10 | 11 | repos = list(pathlib.Path(search_path).glob("**/*.repos")) 12 | if clone_path is not None: 13 | repos.extend(list(pathlib.Path(clone_path).glob("**/*.repos"))) 14 | return repos 15 | 16 | def is_file_empty(filename): 17 | """Check if file is empty, allowing blank spaces.""" 18 | try: 19 | with open(filename, 'r') as file: 20 | content = file.read().strip() 21 | return len(content) == 0 22 | except Exception as e: 23 | print(e) 24 | # Exception gets raised later 25 | return False 26 | 27 | def main(): 28 | 29 | search_path = sys.argv[1] if len(sys.argv) > 1 else "." 30 | clone_path = sys.argv[2] if len(sys.argv) > 2 else "." 31 | cloned_repos = [] 32 | 33 | while True: 34 | 35 | found_repos = find_dot_repos(search_path, clone_path) 36 | remaining_repos = set(found_repos) - set(cloned_repos) 37 | 38 | if not remaining_repos: 39 | break 40 | 41 | next_repo = list(remaining_repos)[0] 42 | with open(str(next_repo), "r") as f: 43 | proc = subprocess.run(["vcs", "import", clone_path, "--recursive"], stdin=f) 44 | if proc.returncode != 0: 45 | # Ignore empty .repos files 46 | if not is_file_empty(next_repo): 47 | raise RuntimeError("vcs import failed") 48 | else: 49 | print(f"Ignored empty .repos file: {next_repo}") 50 | 51 | cloned_repos.append(next_repo) 52 | 53 | print(" ".join([str(repo) for repo in set(found_repos)])) 54 | 55 | 56 | if __name__ == "__main__": 57 | main() 58 | -------------------------------------------------------------------------------- /scripts/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | ROOT_PATH="$(realpath "$(cd -P "$(dirname "${0}")" && pwd)"/..)" 6 | source "${ROOT_PATH}/scripts/utils.sh" 7 | 8 | 9 | build_image() { 10 | echo "Building stage '${TARGET}' for platform '${PLATFORM}' as '${IMAGE}' ..." 11 | 12 | DOCKER_ARGS=( 13 | --file "$(dirname "$0")/../docker/Dockerfile" 14 | --target "${TARGET}" 15 | --platform "${PLATFORM}" 16 | --tag "${IMAGE}" 17 | ) 18 | 19 | if [[ "${_ENABLE_IMAGE_PUSH}" == "true" ]]; then 20 | DOCKER_ARGS+=( "--push" ) 21 | else 22 | DOCKER_ARGS+=( "--load" ) 23 | fi 24 | 25 | # required build args 26 | DOCKER_ARGS+=( --build-arg "BASE_IMAGE=${BASE_IMAGE}" ) 27 | DOCKER_ARGS+=( --build-arg "COMMAND=${COMMAND}" ) 28 | 29 | # function to add "--build-arg NAME=VALUE" only if VALUE is non-empty 30 | add_arg_if_set() { 31 | local var_name="$1" 32 | local var_value="${!var_name}" 33 | if [[ -n "${var_value}" ]]; then 34 | DOCKER_ARGS+=( "--build-arg" "${var_name}=${var_value}" ) 35 | fi 36 | } 37 | 38 | # optional build args 39 | add_arg_if_set "ADDITIONAL_DEBS_FILE" 40 | add_arg_if_set "ADDITIONAL_FILES_DIR" 41 | add_arg_if_set "ADDITIONAL_PIP_FILE" 42 | add_arg_if_set "AFTER_DEPENDENCY_INSTALLATION_SCRIPT" 43 | add_arg_if_set "BEFORE_DEPENDENCY_IDENTIFICATION_SCRIPT" 44 | add_arg_if_set "BEFORE_DEPENDENCY_INSTALLATION_SCRIPT" 45 | add_arg_if_set "BLACKLISTED_PACKAGES_FILE" 46 | add_arg_if_set "CMAKE_ARGS" 47 | add_arg_if_set "CUSTOM_SCRIPT_FILE" 48 | add_arg_if_set "DISABLE_ROS_INSTALLATION" 49 | add_arg_if_set "ENABLE_RECURSIVE_ADDITIONAL_DEBS" 50 | add_arg_if_set "ENABLE_RECURSIVE_ADDITIONAL_PIP" 51 | add_arg_if_set "ENABLE_RECURSIVE_AFTER_DEPENDENCY_INSTALLATION_SCRIPT" 52 | add_arg_if_set "ENABLE_RECURSIVE_BEFORE_DEPENDENCY_INSTALLATION_SCRIPT" 53 | add_arg_if_set "ENABLE_RECURSIVE_BLACKLISTED_PACKAGES" 54 | add_arg_if_set "ENABLE_RECURSIVE_VCS_IMPORT" 55 | add_arg_if_set "GIT_HTTPS_PASSWORD" 56 | add_arg_if_set "GIT_HTTPS_SERVER" 57 | add_arg_if_set "GIT_HTTPS_USER" 58 | add_arg_if_set "GIT_SSH_KNOWN_HOST_KEYS" 59 | add_arg_if_set "GIT_SSH_PRIVATE_KEY" 60 | add_arg_if_set "RMW_IMPLEMENTATION" 61 | add_arg_if_set "ROS_DISTRO" 62 | add_arg_if_set "VCS_IMPORT_FILE" 63 | 64 | DOCKER_ARGS+=( "." ) 65 | 66 | docker buildx build "${DOCKER_ARGS[@]}" 67 | echo "Successfully built stage '${TARGET}' for platform '${PLATFORM}' as '${IMAGE}'" 68 | } 69 | 70 | 71 | # check if script is executed 72 | if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then 73 | # load variables from .env 74 | [[ -f "$(pwd)/.env" ]] && source "$(pwd)/.env" 75 | # check for required variables and set defaults for optional variables 76 | TARGET="${TARGET:-run}" 77 | PLATFORM="${PLATFORM:-$(dpkg --print-architecture)}" 78 | require_var "BASE_IMAGE" 79 | require_var "IMAGE" 80 | [[ "${TARGET}" == *"run"* ]] && require_var "COMMAND" 81 | _ENABLE_IMAGE_PUSH="${_ENABLE_IMAGE_PUSH:-false}" 82 | build_image 83 | fi 84 | -------------------------------------------------------------------------------- /scripts/ci.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | ROOT_PATH="$(realpath "$(cd -P "$(dirname "${0}")" && pwd)"/..)" 6 | source "${ROOT_PATH}/scripts/build.sh" 7 | source "${ROOT_PATH}/scripts/utils.sh" 8 | 9 | 10 | # check for required variables and set defaults for optional variables 11 | TARGET="${TARGET:-run}" 12 | PLATFORM="${PLATFORM:-$(dpkg --print-architecture)}" 13 | require_var "BASE_IMAGE" 14 | require_var "IMAGE_NAME" 15 | IMAGE_TAG="${IMAGE_TAG:-latest}" 16 | [[ "${TARGET}" == *"run"* ]] && require_var "COMMAND" 17 | DEV_IMAGE_NAME="${DEV_IMAGE_NAME:-${IMAGE_NAME}}" 18 | DEV_IMAGE_TAG="${DEV_IMAGE_TAG:-${IMAGE_TAG}-dev}" 19 | SLIM_IMAGE_NAME="${SLIM_IMAGE_NAME:-${IMAGE_NAME}}" 20 | SLIM_IMAGE_TAG="${SLIM_IMAGE_TAG:-${IMAGE_TAG}-slim}" 21 | 22 | ADDITIONAL_DEBS_FILE="${ADDITIONAL_DEBS_FILE:-}" 23 | ADDITIONAL_FILES_DIR="${ADDITIONAL_FILES_DIR:-}" 24 | ADDITIONAL_PIP_FILE="${ADDITIONAL_PIP_FILE:-}" 25 | AFTER_DEPENDENCY_INSTALLATION_SCRIPT="${AFTER_DEPENDENCY_INSTALLATION_SCRIPT:-}" 26 | BEFORE_DEPENDENCY_IDENTIFICATION_SCRIPT="${BEFORE_DEPENDENCY_IDENTIFICATION_SCRIPT:-}" 27 | BEFORE_DEPENDENCY_INSTALLATION_SCRIPT="${BEFORE_DEPENDENCY_INSTALLATION_SCRIPT:-}" 28 | BLACKLISTED_PACKAGES_FILE="${BLACKLISTED_PACKAGES_FILE:-}" 29 | CMAKE_ARGS="${CMAKE_ARGS:-}" 30 | DEV_IMAGE="${DEV_IMAGE_NAME}:${DEV_IMAGE_TAG}" 31 | DISABLE_ROS_INSTALLATION="${DISABLE_ROS_INSTALLATION:-}" 32 | ENABLE_RECURSIVE_ADDITIONAL_DEBS="${ENABLE_RECURSIVE_ADDITIONAL_DEBS:-}" 33 | ENABLE_RECURSIVE_ADDITIONAL_PIP="${ENABLE_RECURSIVE_ADDITIONAL_PIP:-}" 34 | ENABLE_RECURSIVE_AFTER_DEPENDENCY_INSTALLATION_SCRIPT="${ENABLE_RECURSIVE_AFTER_DEPENDENCY_INSTALLATION_SCRIPT:-}" 35 | ENABLE_RECURSIVE_BEFORE_DEPENDENCY_INSTALLATION_SCRIPT="${ENABLE_RECURSIVE_BEFORE_DEPENDENCY_INSTALLATION_SCRIPT:-}" 36 | ENABLE_RECURSIVE_BLACKLISTED_PACKAGES="${ENABLE_RECURSIVE_BLACKLISTED_PACKAGES:-}" 37 | ENABLE_RECURSIVE_VCS_IMPORT="${ENABLE_RECURSIVE_VCS_IMPORT:-}" 38 | ENABLE_SINGLEARCH_PUSH="${ENABLE_SINGLEARCH_PUSH:-false}" 39 | ENABLE_SLIM="${ENABLE_SLIM:-true}" 40 | GIT_HTTPS_PASSWORD="${GIT_HTTPS_PASSWORD:-}" 41 | GIT_HTTPS_SERVER="${GIT_HTTPS_SERVER:-}" 42 | GIT_HTTPS_USER="${GIT_HTTPS_USER:-}" 43 | GIT_SSH_KNOWN_HOST_KEYS="${GIT_SSH_KNOWN_HOST_KEYS:-}" 44 | GIT_SSH_PRIVATE_KEY="${GIT_SSH_PRIVATE_KEY:-}" 45 | IMAGE="${IMAGE_NAME}:${IMAGE_TAG}" 46 | RMW_IMPLEMENTATION="${RMW_IMPLEMENTATION:-}" 47 | ROS_DISTRO="${ROS_DISTRO:-}" 48 | SLIM_BUILD_ARGS="${SLIM_BUILD_ARGS:-'--sensor-ipc-mode proxy --continue-after=10 --show-clogs --http-probe=false --include-path /opt/ros --include-path /docker-ros/ws/install'}" 49 | SLIM_IMAGE="${SLIM_IMAGE_NAME}:${SLIM_IMAGE_TAG}" 50 | VCS_IMPORT_FILE="${VCS_IMPORT_FILE:-}" 51 | _ENABLE_IMAGE_PUSH="${_ENABLE_IMAGE_PUSH:-false}" 52 | _IMAGE_POSTFIX="${_IMAGE_POSTFIX:-""}" 53 | 54 | # write image name for industrial_ci to output (GitHub-only) 55 | if [[ -n "${GITHUB_ACTIONS}" ]]; then 56 | industrial_ci_image="${IMAGE}" 57 | [[ "${TARGET}" == *"dev"* ]] && industrial_ci_image="${DEV_IMAGE}" 58 | [[ -n "${_IMAGE_POSTFIX}" ]] && industrial_ci_image="${industrial_ci_image}${_IMAGE_POSTFIX}" 59 | if [[ "${PLATFORM}" != *","* ]]; then 60 | industrial_ci_image="${industrial_ci_image}-${PLATFORM}" 61 | else 62 | industrial_ci_image="${industrial_ci_image}-$(dpkg --print-architecture)" 63 | fi 64 | echo "INDUSTRIAL_CI_IMAGE=${industrial_ci_image}" >> "${GITHUB_OUTPUT}" 65 | fi 66 | 67 | # parse (potentially) comma-separated lists to arrays 68 | IFS="," read -ra TARGETS <<< "${TARGET}" 69 | if [[ "${_ENABLE_IMAGE_PUSH}" != "true" || "${ENABLE_SINGLEARCH_PUSH}" == "true" ]]; then 70 | IFS="," read -ra PLATFORMS <<< "${PLATFORM}" 71 | else 72 | PLATFORMS=( "${PLATFORM}" ) 73 | fi 74 | unset TARGET 75 | unset PLATFORM 76 | 77 | # prepare slim 78 | if [[ "${ENABLE_SLIM}" == "true" ]]; then 79 | curl -L -o ds.tar.gz https://github.com/slimtoolkit/slim/releases/download/1.40.11/dist_linux.tar.gz 80 | tar -xvf ds.tar.gz 81 | fi 82 | 83 | # loop over targets and platforms to build images 84 | for PLATFORM in "${PLATFORMS[@]}"; do 85 | for TARGET in "${TARGETS[@]}"; do 86 | open_log_group "Build ${TARGET} image (${PLATFORM})" 87 | image="${IMAGE}" 88 | [[ "${TARGET}" == "dev" ]] && image="${DEV_IMAGE}" 89 | [[ -n "${_IMAGE_POSTFIX}" ]] && image="${image}${_IMAGE_POSTFIX}" 90 | [[ "${_ENABLE_IMAGE_PUSH}" != "true" || "${ENABLE_SINGLEARCH_PUSH}" == "true" ]] && image="${image}-${PLATFORM}" 91 | IMAGE="${image}" build_image 92 | close_log_group 93 | done 94 | 95 | # slim image 96 | if [[ "${ENABLE_SLIM}" == "true" && "${TARGET}" == "run" ]]; then 97 | open_log_group "Slim image (${PLATFORM})" 98 | image="${IMAGE}" 99 | slim_image="${SLIM_IMAGE}" 100 | [[ -n "${_IMAGE_POSTFIX}" ]] && image="${image}${_IMAGE_POSTFIX}" 101 | [[ -n "${_IMAGE_POSTFIX}" ]] && slim_image="${slim_image}${_IMAGE_POSTFIX}" 102 | [[ "${_ENABLE_IMAGE_PUSH}" != "true" || "${ENABLE_SINGLEARCH_PUSH}" == "true" ]] && image="${image}-${PLATFORM}" 103 | [[ "${_ENABLE_IMAGE_PUSH}" != "true" || "${ENABLE_SINGLEARCH_PUSH}" == "true" ]] && slim_image="${slim_image}-${PLATFORM}" 104 | cd dist_linux* 105 | ./slim build --target "${image}" --tag "${slim_image}" ${SLIM_BUILD_ARGS} 106 | if [[ "${_ENABLE_IMAGE_PUSH}" == "true" ]]; then 107 | docker push "${slim_image}" 108 | fi 109 | cd - 110 | close_log_group 111 | fi 112 | done 113 | -------------------------------------------------------------------------------- /scripts/utils.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | require_var() { 4 | if [[ -z "${!1}" ]]; then 5 | echo "Environment variable '${1}' is required" 6 | exit 1 7 | fi 8 | } 9 | 10 | open_log_group() { 11 | if [[ -n "${GITLAB_CI}" ]]; then 12 | echo -e "section_start:`date +%s`:build_section[collapsed=true]\r\e[0K[docker-ros] ${1}" 13 | elif [[ -n "${GITHUB_ACTIONS}" ]]; then 14 | echo "::group::[docker-ros] ${1}" 15 | fi 16 | } 17 | 18 | close_log_group() { 19 | if [[ -n "${GITLAB_CI}" ]]; then 20 | echo -e "section_end:`date +%s`:build_section\r\e[0K" 21 | elif [[ -n "${GITHUB_ACTIONS}" ]]; then 22 | echo "::endgroup::" 23 | fi 24 | } 25 | --------------------------------------------------------------------------------