diff --git a/.github/workflows/_build-image.yaml b/.github/workflows/_build-image.yaml new file mode 100644 index 0000000..23832aa --- /dev/null +++ b/.github/workflows/_build-image.yaml @@ -0,0 +1,82 @@ +name: Build a single Docker image (reusable) + +on: + workflow_call: + inputs: + base_image: + description: The FROM base image + required: true + type: string + push_image: + description: Image name on the LCAS registry + required: true + type: string + ros_distro: + required: true + type: string + dockerfile: + required: true + type: string + architectures: + required: true + type: string + outputs: + digest: + description: Image digest from the build + value: ${{ jobs.build.outputs.digest }} + secrets: + LCAS_REGISTRY_PUSHER: + required: true + LCAS_REGISTRY_TOKEN: + required: true + +jobs: + build: + runs-on: [lcas, qemu] + outputs: + digest: ${{ steps.build.outputs.digest }} + steps: + - uses: actions/setup-node@v4 + - uses: actions/checkout@v3 + - run: echo "BRANCH=${GITHUB_REF##*/}" >> $GITHUB_ENV + + - uses: docker/login-action@v3 + with: + registry: lcas.lincoln.ac.uk + username: ${{ secrets.LCAS_REGISTRY_PUSHER }} + password: ${{ secrets.LCAS_REGISTRY_TOKEN }} + + - name: Docker meta + id: meta + uses: docker/metadata-action@v5 + with: + flavor: latest=false + labels: | + org.opencontainers.image.description=AOC Docker Image (${{ inputs.push_image }}, ${{ inputs.ros_distro }}) + org.opencontainers.image.authors=L-CAS Team + images: lcas.lincoln.ac.uk/${{ inputs.push_image }} + tags: | + type=raw,value=${{ inputs.ros_distro }}-staging + type=raw,enable=${{ github.event_name != 'pull_request' }},value=${{ inputs.ros_distro }}-latest + type=ref,enable=${{ github.event_name != 'pull_request' }},event=branch,prefix=${{ inputs.ros_distro }}- + type=semver,pattern={{version}},prefix=${{ inputs.ros_distro }}- + type=semver,pattern={{major}}.{{minor}},prefix=${{ inputs.ros_distro }}- + type=semver,pattern={{major}},prefix=${{ inputs.ros_distro }}- + + - uses: docker/setup-buildx-action@v3 + + - name: Build and push + id: build + uses: docker/build-push-action@v6 + with: + context: ./docker + file: ./${{ inputs.dockerfile }} + platforms: ${{ inputs.architectures }} + push: true + cache-from: type=registry,ref=lcas.lincoln.ac.uk/cache/${{ inputs.push_image }}:${{ inputs.ros_distro }} + cache-to: type=registry,ref=lcas.lincoln.ac.uk/cache/${{ inputs.push_image }}:${{ inputs.ros_distro }},mode=max + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + build-args: | + BASE_IMAGE=${{ inputs.base_image }} + ROS_DISTRO=${{ inputs.ros_distro }} \ No newline at end of file diff --git a/.github/workflows/docker-build-and-push.yaml b/.github/workflows/docker-build-and-push.yaml new file mode 100644 index 0000000..f2c6942 --- /dev/null +++ b/.github/workflows/docker-build-and-push.yaml @@ -0,0 +1,78 @@ +name: Build AOC Docker Images + +on: + push: + branches: [ main ] + tags: [ '*' ] + pull_request: + branches: [ main ] + workflow_dispatch: + +jobs: + + # ── Level 0: no dependencies ────────────────────────────────────────────── + + ros-humble: + uses: ./.github/workflows/_build-image.yaml + with: + base_image: ros:humble + push_image: ros + ros_distro: humble + dockerfile: base.dockerfile + architectures: linux/amd64,linux/arm64 + secrets: inherit + + ros-jazzy: + uses: ./.github/workflows/_build-image.yaml + with: + base_image: ros:jazzy + push_image: ros + ros_distro: jazzy + dockerfile: base.dockerfile + architectures: linux/amd64,linux/arm64 + secrets: inherit + + ros-cuda-humble: + uses: ./.github/workflows/_build-image.yaml + with: + base_image: nvidia/cuda:11.8.0-runtime-ubuntu22.04 + push_image: ros_cuda + ros_distro: humble + dockerfile: cuda.dockerfile + architectures: linux/amd64 + secrets: inherit + + ros-cuda-jazzy: + uses: ./.github/workflows/_build-image.yaml + with: + base_image: nvidia/cuda:12.6.3-cudnn-devel-ubuntu24.04 + push_image: ros_cuda + ros_distro: jazzy + dockerfile: cuda.dockerfile + architectures: linux/amd64 + secrets: inherit + + # ── Level 1: base_image is the exact digest from level 0 ───────────────── + + ros-cuda-desktop-humble: + needs: ros-cuda-humble + uses: ./.github/workflows/_build-image.yaml + with: + # Digest is immutable — no staging tag, no race condition, no cleanup + base_image: lcas.lincoln.ac.uk/ros_cuda@${{ needs.ros-cuda-humble.outputs.digest }} + push_image: ros_cuda_desktop + ros_distro: humble + dockerfile: cuda_desktop.dockerfile + architectures: linux/amd64 + secrets: inherit + + ros-cuda-desktop-jazzy: + needs: ros-cuda-jazzy + uses: ./.github/workflows/_build-image.yaml + with: + base_image: lcas.lincoln.ac.uk/ros_cuda@${{ needs.ros-cuda-jazzy.outputs.digest }} + push_image: ros_cuda_desktop + ros_distro: jazzy + dockerfile: cuda_desktop.dockerfile + architectures: linux/amd64 + secrets: inherit \ No newline at end of file diff --git a/README.md b/README.md index e78a415..1e97aee 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,9 @@ # aoc_container_base -A repository of verstile ROS-enabled Docker containers + +A repository of verstile ROS-enabled Docker containers, orginally developed as apart of the [Agri-OpenCore (AOC) project](https://agri-opencore.org). + +| Container Name | Tags | Purpose | +| ------------------------------------- | --------------------- | --------------------------------------------------------------------------------------------------------------------------------- | +| `lcas.lincoln.ac.uk/ros` | { `humble`, `jazzy` } | Base ROS Container, the minimal environment you need for ROS | +| `lcas.lincoln.ac.uk/ros_cuda` | { `humble`, `jazzy` } | ROS + Nvidia. When you need to use a GPU in your ROS environment for either better quality simulation or AI workloads. | +| `lcas.lincoln.ac.uk/ros_cuda_desktop` | { `humble`, `jazzy` } | ROS + Nvidia + Packages. Installs the `ros-{distro}-desktop` varient so there is the full ROS stack available. | \ No newline at end of file diff --git a/base.dockerfile b/base.dockerfile new file mode 100644 index 0000000..ba655d9 --- /dev/null +++ b/base.dockerfile @@ -0,0 +1,39 @@ +ARG BASE_IMAGE=ros:humble +ARG ROS_DISTRO=humble + +FROM ${BASE_IMAGE} AS base +ARG BASE_IMAGE +ARG ROS_DISTRO + +ENV BASE_IMAGE=${BASE_IMAGE} +ENV ROS_DISTRO=${ROS_DISTRO} + +ENV DEBIAN_FRONTEND=noninteractive + +RUN apt-get update && apt-get upgrade -y && apt-get install -y \ + build-essential \ + cmake \ + git \ + curl \ + wget \ + unzip \ + ros-${ROS_DISTRO}-ros-base \ + && rm -rf /var/lib/apt/lists/* + +RUN . /opt/ros/${ROS_DISTRO}/setup.sh && rosdep update + +ARG USERNAME=ros +ARG USER_UID=1001 +ARG USER_GID=$USER_UID + +# Create a non-root user +RUN groupadd --gid $USER_GID $USERNAME \ + && useradd -s /bin/bash --uid $USER_UID --gid $USER_GID -m $USERNAME \ + # Add sudo support for the non-root user + && apt-get update \ + && apt-get install -y --no-install-recommends sudo \ + && echo $USERNAME ALL=\(root\) NOPASSWD:ALL > /etc/sudoers.d/$USERNAME\ + && chmod 0440 /etc/sudoers.d/$USERNAME \ + && rm -rf /var/lib/apt/lists/* + + diff --git a/cuda.dockerfile b/cuda.dockerfile new file mode 100644 index 0000000..a25af0d --- /dev/null +++ b/cuda.dockerfile @@ -0,0 +1,62 @@ +ARG BASE_IMAGE=nvidia/cuda:11.8.0-runtime-ubuntu22.04 +ARG ROS_DISTRO=humble + + +########################################### +FROM ${BASE_IMAGE} AS base +ARG BASE_IMAGE +ARG ROS_DISTRO + +ENV BASE_IMAGE=${BASE_IMAGE} +ENV ROS_DISTRO=${ROS_DISTRO} + +ENV DEBIAN_FRONTEND=noninteractive + +# Install language +RUN apt-get update ; \ + apt-get upgrade -y && \ + apt-get install -y --no-install-recommends \ + locales \ + curl \ + gnupg2 \ + lsb-release \ + git \ + nano \ + python3-setuptools \ + software-properties-common \ + wget \ + tzdata \ + && locale-gen en_US.UTF-8 \ + && update-locale LC_ALL=en_US.UTF-8 LANG=en_US.UTF-8 \ + && rm -rf /var/lib/apt/lists/* +ENV LANG=en_US.UTF-8 + +RUN curl -sSL https://raw.githubusercontent.com/ros/rosdistro/master/ros.key -o /usr/share/keyrings/ros-archive-keyring.gpg + +# Prepare ROS2 +RUN add-apt-repository universe \ + && curl -sSL https://raw.githubusercontent.com/ros/rosdistro/master/ros.key -o /usr/share/keyrings/ros-archive-keyring.gpg \ + && echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/ros-archive-keyring.gpg] http://packages.ros.org/ros2/ubuntu $(. /etc/os-release && echo $UBUNTU_CODENAME) main" | tee /etc/apt/sources.list.d/ros2.list > /dev/null + + +RUN apt-get update && apt-get install -y --no-install-recommends \ + ros-${ROS_DISTRO}-ros-base \ + python3-rosdep \ + && rm -rf /var/lib/apt/lists/* + +RUN . /opt/ros/${ROS_DISTRO}/setup.sh && rosdep init && rosdep update + +ARG USERNAME=ros +ARG USER_UID=1001 +ARG USER_GID=$USER_UID + +# Create a non-root user +RUN groupadd --gid $USER_GID $USERNAME \ + && useradd -s /bin/bash --uid $USER_UID --gid $USER_GID -m $USERNAME \ + # Add sudo support for the non-root user + && apt-get update \ + && apt-get install -y --no-install-recommends sudo \ + && echo $USERNAME ALL=\(root\) NOPASSWD:ALL > /etc/sudoers.d/$USERNAME\ + && chmod 0440 /etc/sudoers.d/$USERNAME \ + && rm -rf /var/lib/apt/lists/* + diff --git a/cuda_desktop.dockerfile b/cuda_desktop.dockerfile new file mode 100644 index 0000000..9e36983 --- /dev/null +++ b/cuda_desktop.dockerfile @@ -0,0 +1,8 @@ +ARG BASE_IMAGE + +########################################### +FROM ${BASE_IMAGE} AS base + +RUN apt-get update && apt-get install -y --no-install-recommends \ + ros-${ROS_DISTRO}-desktop \ + && rm -rf /var/lib/apt/lists/* diff --git a/docker/.dockerignore b/docker/.dockerignore new file mode 100644 index 0000000..e69de29