Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
39d0b03
Add Dockerfile and GitHub Actions workflow for building AOC Docker im…
marc-hanheide Feb 16, 2026
6400cd7
Add GitHub Actions workflow for building AOC Docker images with multi…
marc-hanheide Feb 16, 2026
b3e9a40
Update Docker build workflow to enable sequential builds and dynamic …
marc-hanheide Feb 16, 2026
7f2808b
Add CUDA Dockerfile and update build workflow for CUDA support
marc-hanheide Feb 16, 2026
6e0c408
Refactor Docker build configuration to use ROS_DISTRO variable for ta…
marc-hanheide Feb 16, 2026
a1fe98d
Add ROS installation and setup commands to CUDA Dockerfile
marc-hanheide Feb 16, 2026
a16011a
Add ROS_DISTRO argument and environment variables to CUDA Dockerfile
marc-hanheide Feb 16, 2026
71f257b
Add ARG declarations for BASE_IMAGE and ROS_DISTRO in Dockerfiles
marc-hanheide Feb 16, 2026
3f967cb
Add CUDA Dockerfile stages for base and desktop images with ROS support
marc-hanheide Feb 16, 2026
14e9331
Comment out cache-from and cache-to lines in Docker build configuration
marc-hanheide Feb 16, 2026
728dad1
Move python3-rosdep installation to the correct stage in Dockerfile
marc-hanheide Feb 16, 2026
3749deb
Add target specification for Docker build stage in workflow
marc-hanheide Feb 16, 2026
0d530ee
Add additional CUDA Dockerfile stages for base and desktop images wit…
marc-hanheide Feb 16, 2026
c55cbf2
Uncomment cache-from and cache-to lines in Docker build configuration
marc-hanheide Feb 16, 2026
ac535ef
Initialize rosdep before updating in Dockerfile
marc-hanheide Feb 16, 2026
1a5d2bf
Add support for ROS Jazzy in Docker build configuration
marc-hanheide Feb 16, 2026
37b628f
Refactor Docker build configuration by removing unused desktop stage …
marc-hanheide Feb 16, 2026
2e8ba15
Add reusable Docker image build workflow and CUDA desktop Dockerfile
marc-hanheide Feb 16, 2026
6a10ecd
Add outputs section for image digest in Docker build workflow
marc-hanheide Feb 16, 2026
8d66679
Reorder Docker build steps to ensure proper setup of buildx before bu…
marc-hanheide Feb 16, 2026
0591fb5
Fix Dockerfile syntax for ros installation
marc-hanheide Feb 16, 2026
66aed27
chore: update from ros_dist to ros_distro added to readme description…
cooperj Feb 16, 2026
14154fc
feat: add non root `ros` user
cooperj Feb 16, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 82 additions & 0 deletions .github/workflows/_build-image.yaml
Original file line number Diff line number Diff line change
@@ -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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] Upstream this is at v6, check that all steps are up-to-date

- 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 }}
78 changes: 78 additions & 0 deletions .github/workflows/docker-build-and-push.yaml
Original file line number Diff line number Diff line change
@@ -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:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should look at how we are naming the components of this, as in it is too easy to confuse desktop and display components.

I don't believe that the display container should be running ROS directly as this makes it, so the containers need to do more than one thing, breaking the principle of containerisation.

The way I propose we do it is like this: #2

I think we should have a simple, slim and efficient way of hydrating a desktop environment to any container.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would propose that we do not use desktop to name in this context. I wonder what the size difference is between running sudo apt install ros-humble-desktop and sudo apt install ros-humble-desktop-full REP2001 then we should potentially name the containers cuda-full OR leave this down to the implementor.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The other option is to use a separate deployment target and ship cuda-full as the default and allow other containers to use less?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should look at how we are naming the components of this, as in it is too easy to confuse desktop and display components.

I don't believe that the display container should be running ROS directly as this makes it, so the containers need to do more than one thing, breaking the principle of containerisation.

The way I propose we do it is like this: #2

I think we should have a simple, slim and efficient way of hydrating a desktop environment to any container.

I'm not opposed. This was just to indicate the general structure of dependency in workflows not to suggest a specific set of containers yet. The only concern I have is that one cannot have multiple inherence in docker easily. Hence a display with ROS should probably be based on the ros_cuda image?

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
9 changes: 8 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -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. |
39 changes: 39 additions & 0 deletions base.dockerfile
Original file line number Diff line number Diff line change
@@ -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/*


62 changes: 62 additions & 0 deletions cuda.dockerfile
Original file line number Diff line number Diff line change
@@ -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

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need to ensure we are shipping VirtualGL if we are wanting to use one desktop container and passing windows through, which I think is the best approach.

It would allow us to take apps such as Gazebo (the client, not server) and keep that container completely separate to the desktop environment, and using X11 forwarding to handle this.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, as said, the current stub is NOT yet doing all that needs to be done. I only created the skeleton of workflows that allows to build dependent images most efficiently and correctly

# 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/*

8 changes: 8 additions & 0 deletions cuda_desktop.dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
ARG BASE_IMAGE

###########################################
FROM ${BASE_IMAGE} AS base

Check warning on line 4 in cuda_desktop.dockerfile

View workflow job for this annotation

GitHub Actions / ros-cuda-desktop-humble / build

Default value for global ARG results in an empty or invalid base image name

InvalidDefaultArgInFrom: Default value for ARG ${BASE_IMAGE} results in empty or invalid base image name More info: https://docs.docker.com/go/dockerfile/rule/invalid-default-arg-in-from/

Check warning on line 4 in cuda_desktop.dockerfile

View workflow job for this annotation

GitHub Actions / ros-cuda-desktop-jazzy / build

Default value for global ARG results in an empty or invalid base image name

InvalidDefaultArgInFrom: Default value for ARG ${BASE_IMAGE} results in empty or invalid base image name More info: https://docs.docker.com/go/dockerfile/rule/invalid-default-arg-in-from/

RUN apt-get update && apt-get install -y --no-install-recommends \
ros-${ROS_DISTRO}-desktop \
&& rm -rf /var/lib/apt/lists/*
Empty file added docker/.dockerignore
Empty file.