diff --git a/.github/ISSUE_TEMPLATE/request-variant.yaml b/.github/ISSUE_TEMPLATE/request-variant.yaml new file mode 100644 index 0000000..e062a04 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/request-variant.yaml @@ -0,0 +1,75 @@ +name: "Request New Image Variant" +description: "Request a new variant based on the images scaffolded by this repository (e.g. ROS / CUDA variants)." +title: "[Variant]: " +labels: + - "enhancement" + - "feature request" +body: + - type: textarea + id: why + attributes: + label: "Explain Your Problem" + description: "What problem does this solve? Why can't you use an existing image that is produced from this repository?" + validations: + required: true + + - type: input + id: ros_version + attributes: + label: "ROS 2 Distro" + placeholder: "humble" + + - type: dropdown + id: rep2001 + attributes: + label: "Is the ROS 2 version a LTS variant?" + description: "See [REP-2001](https://www.ros.org/reps/rep-2001.html)" + options: + - "Yes" + - "No" + + - type: input + id: ubuntu_version + attributes: + label: "Ubuntu version" + placeholder: "22.04" + + - type: checkboxes + id: ubuntu_lts + attributes: + label: "Is Ubuntu LTS?" + options: + - label: "Yes (LTS)" + + - type: textarea + id: non_lts_justification + attributes: + label: "If not a Ubuntu LTS, please justify why you need this version." + + - type: checkboxes + id: cuda_needed + attributes: + label: "Is NVIDIA CUDA needed?" + description: "CUDA will make the images much larger and should only be used for 'AI' workloads that will take advantage of CUDA" + options: + - label: "Yes, I need to use CUDA in this container" + + - type: input + id: cuda_version + attributes: + label: "If yes, what CUDA version do you need?" + description: "If you are using your own machine, run `nvidia-smi` and see what version you are using or state latest." + placeholder: "13.2.0" + + - type: input + id: cuda_base_tag + attributes: + label: "What is your proposed NVIDIA base image to use? (optional)" + description: "From [NVIDIA supported tags list](https://gitlab.com/nvidia/container-images/cuda/-/blob/master/doc/supported-tags.md)." + placeholder: "13.2.0-cudnn-runtime-ubuntu22.04" + + - type: textarea + id: use_case + attributes: + label: "What is your use case?" + description: "Provide us with information and links of how you expect to be using this." diff --git a/.github/workflows/_build-ros-image.yaml b/.github/workflows/_build-ros-image.yaml new file mode 100644 index 0000000..99421c0 --- /dev/null +++ b/.github/workflows/_build-ros-image.yaml @@ -0,0 +1,133 @@ +name: Build a single ROS 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 + tag_stem: + description: "Base image tag stem (example: humble-11.8)" + required: true + type: string + image_context: + description: "Tag context suffix (main, release tag, or staging)" + required: true + type: string + ros_distro: + description: ROS distro to build for (for tags/build args) + required: true + type: string + dockerfile: + required: true + type: string + architectures: + required: true + type: string + build_desktop: + description: Build a desktop variant after the primary image? + required: false + default: false + type: boolean + desktop_push_image: + description: Desktop image name on the LCAS registry, i.e. ros_cuda_desktop + required: false + default: "" + 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/checkout@v6 + + - uses: docker/login-action@v4 + 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@v6 + 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.tag_stem }}-${{ inputs.image_context }} + type=semver,pattern={{version}},prefix=${{ inputs.tag_stem }}- + type=semver,pattern={{major}}.{{minor}},prefix=${{ inputs.tag_stem }}- + type=semver,pattern={{major}},prefix=${{ inputs.tag_stem }}- + + - uses: docker/setup-buildx-action@v4 + + - name: Build and push + id: build + uses: docker/build-push-action@v7 + 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.tag_stem }} + cache-to: type=registry,ref=lcas.lincoln.ac.uk/cache/${{ inputs.push_image }}:${{ inputs.tag_stem }},mode=max + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + build-args: | + BASE_IMAGE=${{ inputs.base_image }} + ROS_DISTRO=${{ inputs.ros_distro }} + + ##### + + - name: Docker meta desktop + if: ${{ inputs.build_desktop && inputs.desktop_push_image != '' }} + id: meta_desktop + uses: docker/metadata-action@v6 + with: + flavor: latest=false + labels: | + org.opencontainers.image.description=AOC Docker Image (${{ inputs.desktop_push_image }}, ${{ inputs.ros_distro }} desktop) + org.opencontainers.image.authors=L-CAS Team + images: lcas.lincoln.ac.uk/${{ inputs.desktop_push_image }} + tags: | + type=raw,value=${{ inputs.tag_stem }}-${{ inputs.image_context }} + type=semver,pattern={{version}},prefix=${{ inputs.tag_stem }}- + type=semver,pattern={{major}}.{{minor}},prefix=${{ inputs.tag_stem }}- + type=semver,pattern={{major}},prefix=${{ inputs.tag_stem }}- + + - name: Build and push desktop + if: ${{ inputs.build_desktop && inputs.desktop_push_image != '' }} + id: build_desktop + uses: docker/build-push-action@v7 + with: + context: ./docker + file: ./desktop.dockerfile + platforms: ${{ inputs.architectures }} + push: true + cache-from: type=registry,ref=lcas.lincoln.ac.uk/cache/${{ inputs.desktop_push_image }}:${{ inputs.tag_stem }} + cache-to: type=registry,ref=lcas.lincoln.ac.uk/cache/${{ inputs.desktop_push_image }}:${{ inputs.tag_stem }},mode=max + tags: ${{ steps.meta_desktop.outputs.tags }} + labels: ${{ steps.meta_desktop.outputs.labels }} + build-args: | + BASE_IMAGE=lcas.lincoln.ac.uk/${{ inputs.push_image }}@${{ steps.build.outputs.digest }} + ROS_DISTRO=${{ inputs.ros_distro }} + diff --git a/.github/workflows/_build-image.yaml b/.github/workflows/_build-standalone-image.yaml similarity index 61% rename from .github/workflows/_build-image.yaml rename to .github/workflows/_build-standalone-image.yaml index 80c2ebd..b70cce3 100644 --- a/.github/workflows/_build-image.yaml +++ b/.github/workflows/_build-standalone-image.yaml @@ -1,4 +1,4 @@ -name: Build a single Docker image (reusable) +name: Build a single standalone Docker image (reusable) on: workflow_call: @@ -11,9 +11,6 @@ on: description: Image name on the LCAS registry required: true type: string - ros_distro: - required: true - type: string dockerfile: required: true type: string @@ -36,11 +33,9 @@ jobs: outputs: digest: ${{ steps.build.outputs.digest }} steps: - - uses: actions/setup-node@v4 - - uses: actions/checkout@v3 - - run: echo "BRANCH=${GITHUB_REF##*/}" >> $GITHUB_ENV + - uses: actions/checkout@v6 - - uses: docker/login-action@v3 + - uses: docker/login-action@v4 with: registry: lcas.lincoln.ac.uk username: ${{ secrets.LCAS_REGISTRY_PUSHER }} @@ -51,35 +46,34 @@ jobs: - name: Docker meta id: meta - uses: docker/metadata-action@v5 + uses: docker/metadata-action@v6 with: flavor: latest=false labels: | - org.opencontainers.image.description=AOC Docker Image (${{ inputs.push_image }}, ${{ inputs.ros_distro }}) + org.opencontainers.image.description=AOC Docker Image (${{ inputs.push_image }}) 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 }}- + type=raw,enable=${{ github.ref_name == 'main' }},value=main + type=raw,enable=${{ github.ref_name == 'main' }},value=latest + type=raw,enable=${{ github.ref_name != 'main' }},value=staging + type=semver,pattern={{version}} + type=semver,pattern={{major}}.{{minor}} + type=semver,pattern={{major}} - - uses: docker/setup-buildx-action@v3 + - uses: docker/setup-buildx-action@v4 - name: Build and push id: build - uses: docker/build-push-action@v6 + uses: docker/build-push-action@v7 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 + cache-from: type=registry,ref=lcas.lincoln.ac.uk/cache/${{ inputs.push_image }}:latest + cache-to: type=registry,ref=lcas.lincoln.ac.uk/cache/${{ inputs.push_image }}:latest,mode=max tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} build-args: | BASE_IMAGE=${{ inputs.base_image }} - ROS_DISTRO=${{ inputs.ros_distro }} diff --git a/.github/workflows/build-docker-images.yaml b/.github/workflows/build-docker-images.yaml new file mode 100644 index 0000000..f6d4125 --- /dev/null +++ b/.github/workflows/build-docker-images.yaml @@ -0,0 +1,114 @@ +name: Build AOC Container Images + +on: + push: + branches: [ main ] + tags: [ '*' ] + pull_request: + branches: [ main ] + workflow_dispatch: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + ################################################################################################# + + prepare: + runs-on: [lcas, qemu] + outputs: + image_context: ${{ steps.vars.outputs.image_context }} + steps: + # Work out the context of this build request, store as an output! + - id: vars + run: | + if [ "${GITHUB_EVENT_NAME}" = "pull_request" ]; then + IMAGE_CONTEXT="staging" + elif [ "${GITHUB_REF_TYPE}" = "tag" ]; then + IMAGE_CONTEXT="${GITHUB_REF_NAME}" + elif [ "${GITHUB_REF_NAME}" = "main" ]; then + IMAGE_CONTEXT="main" + else + IMAGE_CONTEXT="staging" + fi + + echo "image_context=${IMAGE_CONTEXT}" >> "$GITHUB_OUTPUT" + + ################################################################################################# + build_standalone: + name: Build ${{ matrix.push_image }} + needs: prepare + strategy: + matrix: + include: + # VNC standalone image + - base_image: "debian:trixie-slim" + push_image: "vnc" + dockerfile: "vnc.dockerfile" + architectures: "linux/amd64,linux/arm64" + + uses: ./.github/workflows/_build-standalone-image.yaml + with: + base_image: ${{ matrix.base_image }} + push_image: ${{ matrix.push_image }} + dockerfile: ${{ matrix.dockerfile }} + architectures: ${{ matrix.architectures }} + secrets: inherit + + + build_ros: + name: Build ${{ matrix.push_image }} ${{ matrix.tag_stem }} + needs: prepare + strategy: + max-parallel: 8 + matrix: + include: + # ROS Humble + - base_image: "ros:humble" + push_image: "ros" + ros_distro: "humble" + tag_stem: "humble" + dockerfile: "base.dockerfile" + architectures: "linux/amd64,linux/arm64" + + # ROS Jazzy + - base_image: "ros:jazzy" + push_image: "ros" + ros_distro: "jazzy" + tag_stem: "jazzy" + dockerfile: "base.dockerfile" + architectures: "linux/amd64,linux/arm64" + + # ROS CUDA Humble + - base_image: "nvidia/cuda:11.8.0-runtime-ubuntu22.04" + push_image: "ros_cuda" + ros_distro: "humble" + tag_stem: "humble-11.8" + dockerfile: "cuda.dockerfile" + architectures: "linux/amd64" + build_desktop: true + desktop_push_image: "ros_cuda_desktop" + + # ROS CUDA Jazzy + - base_image: "nvidia/cuda:12.9.1-runtime-ubuntu24.04" + push_image: "ros_cuda" + ros_distro: "jazzy" + tag_stem: "jazzy-12.9" + dockerfile: "cuda.dockerfile" + architectures: "linux/amd64" + build_desktop: true + desktop_push_image: "ros_cuda_desktop" + + uses: ./.github/workflows/_build-ros-image.yaml + with: + base_image: ${{ matrix.base_image }} + push_image: ${{ matrix.push_image }} + tag_stem: ${{ matrix.tag_stem }} + image_context: ${{ needs.prepare.outputs.image_context }} + ros_distro: ${{ matrix.ros_distro }} + dockerfile: ${{ matrix.dockerfile }} + architectures: ${{ matrix.architectures }} + build_desktop: ${{ matrix.build_desktop || false }} + desktop_push_image: ${{ matrix.desktop_push_image }} + secrets: inherit diff --git a/.github/workflows/docker-build-and-push.yaml b/.github/workflows/docker-build-and-push.yaml deleted file mode 100644 index 407675f..0000000 --- a/.github/workflows/docker-build-and-push.yaml +++ /dev/null @@ -1,78 +0,0 @@ -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.9.1-runtime-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 1e97aee..4c9481a 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,78 @@ -# aoc_container_base +# AOC Container Base -A repository of verstile ROS-enabled Docker containers, orginally developed as apart of the [Agri-OpenCore (AOC) project](https://agri-opencore.org). +A repository of versatile containers, developed as part of the [Agri-OpenCore (AOC) project](https://agri-opencore.org). Designed for the execution of simple and reliable containerised robotics solutions. -| 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 +This repository manages the following containers: + +| Image | Tags | Includes | Choose This If... | Dockerfile | +| --- | --- | --- | --- | --- | +| `lcas.lincoln.ac.uk/ros` | `humble`, `jazzy` | Minimal ROS runtime and tooling, plus VirtualGL | You need ROS with graphical workflows (including VirtualGL-based rendering) but do not need NVIDIA CUDA | [base.dockerfile](base.dockerfile) | +| `lcas.lincoln.ac.uk/ros_cuda` | `humble-11.8`, `jazzy-12.9` | ROS plus NVIDIA CUDA support | You need CUDA-enabled GPU acceleration for simulation or AI workloads | [cuda.dockerfile](cuda.dockerfile) | +| `lcas.lincoln.ac.uk/ros_cuda_desktop` | `humble-11.8`, `jazzy-12.9` | ROS + CUDA + `ros-{distro}-desktop` package set | You need CUDA and the full ROS desktop stack | [desktop.dockerfile](desktop.dockerfile) | +| `lcas.lincoln.ac.uk/vnc` | `latest` | Browser-accessible VNC/X11 display endpoint | You need a shared web-based display target for GUI applications from other containers | [vnc.dockerfile](vnc.dockerfile) | + +These containers are built from three standard container images, `ros`, `nvidia/cuda` and `debian`. Each container is either built from one of these pre-existing images or one derived from it in this pattern. + +```mermaid +%%{init: {"theme":"neutral"}}%% +flowchart TB + library_ros["library/ros"] --> lcas_ros["lcas/ros"] + nvidia_cuda["nvidia/cuda"] --> lcas_ros_cuda["lcas/ros_cuda"] + lcas_ros_cuda --> lcas_ros_cuda_desktop["lcas/ros_cuda_desktop"] + debian["debian"] --> vnc["vnc"] + + %% External base images (dashed) + style library_ros stroke-width:1px,stroke-dasharray: 3 3 + style nvidia_cuda stroke-width:1px,stroke-dasharray: 3 3 + style debian stroke-width:1px,stroke-dasharray: 3 3 +``` + +## How can I use this? + +Choose your image using this quick guide: + +- No CUDA, but graphical workflows: use `lcas.lincoln.ac.uk/ros` (as this includes VirtualGL). +- Need CUDA: use `lcas.lincoln.ac.uk/ros_cuda` or `lcas.lincoln.ac.uk/ros_cuda_desktop`. +- Minimal ROS only: if you do not need VirtualGL or the shared VNC environment, use [`ros` on DockerHub](https://hub.docker.com/_/ros). + +### ROS + +This works best if you follow the [`ros2_pkg_template`](https://github.com/lcas/ros2_pkg_template). Use it as a template to build your own repositories containing the packages you want to ship. + +You can work either inside the devcontainer or by running the container yourself. When you are ready, enable the deployment workflows and add automated testing. Once you are happy, move this onto a real robot platform and keep iterating until it works. + +```mermaid +%%{init: {"theme":"neutral","flowchart":{"curve":"linear"}}}%% +flowchart LR + Dev["Develop in the Devcontainer"] + Workflows["Activate Workflows to Build and Push Images"] + AutoTest["Add Automated Testing Steps Where Possible"] + Deploy["Add/Update Your Container onto the Platform Deployment Repository"] + RealTest["Test the Packages on a Real Robot"] + Outcome{"Did it work well?"} + Fix(("Make changes that will fix the issues")) + Complete(["✅ Complete"]) + + %% main flow + Dev --> Workflows --> AutoTest --> Deploy --> RealTest --> Outcome + Outcome -- "Yes" --> Complete + Outcome -- "No" --> Fix --> AutoTest + + %% iterative dev loop (renders as a real loop in GitHub) + Dev -. "Iterate" .-> Iter(("🔄")) + Iter -.-> Dev + + %% keep it subtle (GitHub-friendly) + classDef loop fill:transparent,stroke:#999,stroke-width:1px; + class Iter loop; +``` + +### VNC + +One of the components, `vnc`, is an X11 destination that allows graphical applications to be displayed in a web browser, either on the robot or remotely. + +`vnc` is not ROS-specific; it can display applications from any container that outputs to X11. It also works with VirtualGL-based applications running in ROS containers, where VirtualGL forwards 3D rendering from those application containers to the host GPU, and the rendered frames are then shown through the VNC session. + +The general concept is to deploy the `vnc` container image only once[^vnc-plural], which removes the need to include display tooling in every application container. + +[^vnc-plural]: You may want to deploy this multiple times, i.e. to support multiple displays for monitoring, but this is atypical. Either way, we deploy as few displays as possible so we have lower resource requirements. diff --git a/base.dockerfile b/base.dockerfile index 1e9ead5..974acb7 100644 --- a/base.dockerfile +++ b/base.dockerfile @@ -11,53 +11,48 @@ ENV ROS_DISTRO=${ROS_DISTRO} ENV DEBIAN_FRONTEND=noninteractive RUN apt-get update \ - && apt-get upgrade -y && \ - apt-get install -y --no-install-recommends \ - build-essential \ - ca-certificates \ - gnupg \ - cmake \ - git \ - curl \ - wget \ - unzip \ - ros-${ROS_DISTRO}-ros-base \ - ros-${ROS_DISTRO}-rmw-cyclonedds-cpp \ - python3-colcon-common-extensions && \ - rm -rf /var/lib/apt/lists/* + && apt-get upgrade -y \ + && apt-get install -y --no-install-recommends \ + build-essential \ + ca-certificates \ + gnupg \ + cmake \ + git \ + curl \ + wget \ + unzip \ + nano \ + ros-${ROS_DISTRO}-ros-base \ + ros-${ROS_DISTRO}-rmw-cyclonedds-cpp \ + python3-colcon-common-extensions \ + && rm -rf /var/lib/apt/lists/* ENV LANG=en_US.UTF-8 -RUN . /opt/ros/${ROS_DISTRO}/setup.sh && rosdep update - -ARG USERNAME=ros -ARG USER_UID=1001 -ARG USER_GID=$USER_UID +RUN . /opt/ros/${ROS_DISTRO}/setup.sh && rosdep update --rosdistro ${ROS_DISTRO} -# Create a non-root user and add sudo support for the non-root user -RUN groupadd --gid $USER_GID $USERNAME \ - && useradd -s /bin/bash --uid $USER_UID --gid $USER_GID -m $USERNAME \ - && 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/* +# Setup VirtualGL +RUN wget -q -O- https://packagecloud.io/dcommander/virtualgl/gpgkey | gpg --dearmor >/etc/apt/trusted.gpg.d/VirtualGL.gpg \ + && echo "deb [signed-by=/etc/apt/trusted.gpg.d/VirtualGL.gpg] https://packagecloud.io/dcommander/virtualgl/any/ any main" >>/etc/apt/sources.list.d/virtualgl.list \ + && apt-get update && apt-get install -y virtualgl libgl1 && rm -rf /var/lib/apt/lists/* -# Cyclone DDS Config -COPY cyclonedds.xml /etc/cyclonedds.xml +# Fix GLVND NVIDIA EGL registration +COPY nvidia-egl-vendor.json /usr/share/glvnd/egl_vendor.d/10_nvidia.json # Configure bash profile RUN echo "if [ -f /etc/bash.bashrc ]; then source /etc/bash.bashrc; fi" >> /root/.bashrc && \ - echo "if [ -f /etc/bash.bashrc ]; then source /etc/bash.bashrc; fi" >> /home/${USERNAME}/.bashrc && \ echo 'PS1="${debian_chroot:+($debian_chroot)}\[\033[01;32m\]\u@\h\[\033[00m\]:\[\033[01;34m\]\w\[\033[00m\]\$ "' >> /etc/bash.bashrc && \ - echo "source /opt/ros/${ROS_DISTRO}/setup.bash" >> /etc/bash.bashrc && \ - echo "alias t='tmux'" >> /etc/bash.bashrc && \ - echo "alias cls='clear'" >> /etc/bash.bashrc && \ - chown ${USERNAME}:${USER_GID} /home/${USERNAME}/.bashrc + echo "source /opt/ros/${ROS_DISTRO}/setup.bash" >> /etc/bash.bashrc ENV RMW_IMPLEMENTATION=rmw_cyclonedds_cpp -ENV CYCLONEDDS_URI=file:///etc/cyclonedds.xml +ENV TVNC_VGL=1 +ENV VGL_ISACTIVE=1 +ENV VGL_FPS=25 +ENV VGL_COMPRESS=0 +ENV VGL_DISPLAY=egl +ENV VGL_WM=1 +ENV VGL_PROBEGLX=0 +ENV LD_PRELOAD=libdlfaker.so:libvglfaker.so +ENV SHELL=/bin/bash -USER ${USERNAME} CMD ["bash", "-l"] - diff --git a/compose.cuda.yml b/compose.cuda.yml new file mode 100644 index 0000000..6942181 --- /dev/null +++ b/compose.cuda.yml @@ -0,0 +1,56 @@ +# Docker Compose illustrating a VNC desktop container alongside a ROS container. +# The ROS container renders GUI applications (e.g. RViz) through the VNC desktop +# by sharing the X11 socket. Both services are on the same bridge network so +# CycloneDDS peer discovery works out of the box. +# +# Usage: +# No GPU: docker compose up +# With GPU: docker compose -f compose.cuda.yml -f compose.gpu.yml up +# +# Access the desktop at: http://localhost:5801/vnc.html?autoconnect=true + +services: + + # ── VNC Desktop ────────────────────────────────────────────────────────────── + vnc: + build: + context: ./docker # contains vnc-entrypoint.sh + dockerfile: ../vnc.dockerfile + target: xfce + ports: + - "5801" # noVNC web interface + volumes: + - x11:/tmp/.X11-unix # share X server socket with other containers + networks: + - ros_net + ipc: shareable # Allow other containers to access the IPC of the VNC container to allow data to go into it's X11 + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:5801/vnc.html"] + interval: 10s + timeout: 5s + retries: 5 + + # ── ROS Container ──────────────────────────────────────────────────────────── + ros: + build: + context: ./docker # contains cyclonedds.xml + dockerfile: ../cuda.dockerfile + depends_on: + vnc: + condition: service_healthy # wait for VNC desktop to be ready before starting ROS + environment: + DISPLAY: ":1" # connect to the VNC X server + volumes: + - x11:/tmp/.X11-unix # X11 socket shared from VNC container + ipc: "service:vnc" # Use the IPC of the VNC container to allow access to X11 socket reliably + networks: + - ros_net + command: ["sleep", "infinity"] + +# ── Shared resources ─────────────────────────────────────────────────────────── +volumes: + x11: # ephemeral volume for the X11 unix socket + +networks: + ros_net: + driver: bridge # CycloneDDS multicast works within a bridge network diff --git a/compose.gpu.yml b/compose.gpu.yml new file mode 100644 index 0000000..fd45a58 --- /dev/null +++ b/compose.gpu.yml @@ -0,0 +1,28 @@ +# GPU override — merge on top of compose.yml when an NVIDIA GPU is available. +# Requires the NVIDIA Container Toolkit to be installed and configured. +# +# Usage: docker compose -f compose.yml -f compose.gpu.yml up + +services: + + vnc: + devices: + - /dev/dri:/dev/dri # passes all card* and renderD* nodes + deploy: + resources: + reservations: + devices: + - driver: nvidia + count: all + capabilities: [gpu, utility, video, graphics] + + ros: + devices: + - /dev/dri:/dev/dri # passes all card* and renderD* nodes + deploy: + resources: + reservations: + devices: + - driver: nvidia + count: all + capabilities: [gpu, compute, utility, video, graphics] diff --git a/compose.yml b/compose.yml new file mode 100644 index 0000000..95acdf0 --- /dev/null +++ b/compose.yml @@ -0,0 +1,56 @@ +# Docker Compose illustrating a VNC desktop container alongside a ROS container. +# The ROS container renders GUI applications (e.g. RViz) through the VNC desktop +# by sharing the X11 socket. Both services are on the same bridge network so +# CycloneDDS peer discovery works out of the box. +# +# Usage: +# No GPU: docker compose up +# With GPU: docker compose -f compose.yml -f compose.gpu.yml up +# +# Access the desktop at: http://localhost:5801/vnc.html?autoconnect=true + +services: + + # ── VNC Desktop ────────────────────────────────────────────────────────────── + vnc: + build: + context: ./docker # contains vnc-entrypoint.sh + dockerfile: ../vnc.dockerfile + target: xfce + ports: + - "5801" # noVNC web interface + volumes: + - x11:/tmp/.X11-unix # share X server socket with other containers + networks: + - ros_net + ipc: shareable # Allow other containers to access the IPC of the VNC container to allow data to go into it's X11 + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:5801/vnc.html"] + interval: 10s + timeout: 5s + retries: 5 + + # ── ROS Container ──────────────────────────────────────────────────────────── + ros: + build: + context: ./docker # contains cyclonedds.xml + dockerfile: ../base.dockerfile + depends_on: + vnc: + condition: service_healthy # wait for VNC desktop to be ready before starting ROS + environment: + DISPLAY: ":1" # connect to the VNC X server + volumes: + - x11:/tmp/.X11-unix # X11 socket shared from VNC container + ipc: "service:vnc" # Use the IPC of the VNC container to allow access to X11 socket reliably + networks: + - ros_net + command: ["sleep", "infinity"] + +# ── Shared resources ─────────────────────────────────────────────────────────── +volumes: + x11: # ephemeral volume for the X11 unix socket + +networks: + ros_net: + driver: bridge # CycloneDDS multicast works within a bridge network diff --git a/cuda.dockerfile b/cuda.dockerfile index 0a4e2ec..6a37e79 100644 --- a/cuda.dockerfile +++ b/cuda.dockerfile @@ -12,71 +12,52 @@ ENV ROS_DISTRO=${ROS_DISTRO} ENV DEBIAN_FRONTEND=noninteractive RUN apt-get update \ - && apt-get upgrade -y \ - && apt-get install -y --no-install-recommends \ - locales \ - curl \ - wget \ - ca-certificates \ - gnupg2 \ - lsb-release \ - git \ - nano \ - python3-setuptools \ - software-properties-common \ - 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/* + && apt-get upgrade -y \ + && apt-get install -y --no-install-recommends \ + locales \ + curl \ + wget \ + ca-certificates \ + gnupg2 \ + lsb-release \ + git \ + nano \ + python3-setuptools \ + software-properties-common \ + 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 # 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 + && 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 \ - ros-${ROS_DISTRO}-rmw-cyclonedds-cpp \ - && rm -rf /var/lib/apt/lists/* - -# Cyclone DDS Config -COPY cyclonedds.xml /etc/cyclonedds.xml + ros-${ROS_DISTRO}-ros-base \ + python3-rosdep \ + ros-${ROS_DISTRO}-rmw-cyclonedds-cpp \ + && rm -rf /var/lib/apt/lists/* RUN . /opt/ros/${ROS_DISTRO}/setup.sh && rosdep init && rosdep update # Setup VirtualGL -RUN wget -q -O- https://packagecloud.io/dcommander/virtualgl/gpgkey | gpg --dearmor >/etc/apt/trusted.gpg.d/VirtualGL.gpg && \ - echo "deb [signed-by=/etc/apt/trusted.gpg.d/VirtualGL.gpg] https://packagecloud.io/dcommander/virtualgl/any/ any main" >> /etc/apt/sources.list.d/virtualgl.list && \ - apt update && apt install -y virtualgl libgl1 && rm -rf /var/lib/apt/lists/* - -# Create a non-root user -ARG USERNAME=ros -ARG USER_UID=1001 -ARG USER_GID=$USER_UID +RUN wget -q -O- https://packagecloud.io/dcommander/virtualgl/gpgkey | gpg --dearmor >/etc/apt/trusted.gpg.d/VirtualGL.gpg \ + && echo "deb [signed-by=/etc/apt/trusted.gpg.d/VirtualGL.gpg] https://packagecloud.io/dcommander/virtualgl/any/ any main" >>/etc/apt/sources.list.d/virtualgl.list \ + && apt-get update && apt-get install -y virtualgl libgl1 && rm -rf /var/lib/apt/lists/* -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/* +# Fix GLVND NVIDIA EGL registration +COPY nvidia-egl-vendor.json /usr/share/glvnd/egl_vendor.d/10_nvidia.json # Configure bash profile RUN echo "if [ -f /etc/bash.bashrc ]; then source /etc/bash.bashrc; fi" >> /root/.bashrc && \ - echo "if [ -f /etc/bash.bashrc ]; then source /etc/bash.bashrc; fi" >> /home/${USERNAME}/.bashrc && \ - chown ${USERNAME}:${USERNAME} /home/${USERNAME}/.bashrc && \ - echo 'PS1="${debian_chroot:+($debian_chroot)}\[\033[01;32m\]\u@\h\[\033[00m\]:\[\033[01;34m\]\w\[\033[00m\]\$ "' >> /etc/bash.bashrc && \ - echo "source /opt/ros/${ROS_DISTRO}/setup.bash" >> /etc/bash.bashrc && \ - echo "alias t='tmux'" >> /etc/bash.bashrc && \ - echo "alias cls='clear'" >> /etc/bash.bashrc - + echo 'PS1="${debian_chroot:+($debian_chroot)}\[\033[01;32m\]\u@\h\[\033[00m\]:\[\033[01;34m\]\w\[\033[00m\]\$ "' >> /etc/bash.bashrc && \ + echo "source /opt/ros/${ROS_DISTRO}/setup.bash" >> /etc/bash.bashrc + ENV RMW_IMPLEMENTATION=rmw_cyclonedds_cpp -ENV CYCLONEDDS_URI=file:///etc/cyclonedds.xml ENV TVNC_VGL=1 ENV VGL_ISACTIVE=1 ENV VGL_FPS=25 @@ -84,9 +65,7 @@ ENV VGL_COMPRESS=0 ENV VGL_DISPLAY=egl ENV VGL_WM=1 ENV VGL_PROBEGLX=0 -ENV LD_PRELOAD=/usr/lib/libdlfaker.so:/usr/lib/libvglfaker.so +ENV LD_PRELOAD=libdlfaker.so:libvglfaker.so ENV SHELL=/bin/bash -USER ${USERNAME} - CMD ["bash", "-l"] diff --git a/cuda_desktop.dockerfile b/cuda_desktop.dockerfile deleted file mode 100644 index 30e7907..0000000 --- a/cuda_desktop.dockerfile +++ /dev/null @@ -1,8 +0,0 @@ -ARG BASE_IMAGE=lcas.lincoln.ac.uk/ros_cuda:humble-main - -########################################### -FROM ${BASE_IMAGE} AS base - -RUN sudo apt-get update && sudo apt-get install -y --no-install-recommends \ - ros-${ROS_DISTRO}-desktop \ - && sudo rm -rf /var/lib/apt/lists/* diff --git a/desktop.dockerfile b/desktop.dockerfile new file mode 100644 index 0000000..bf8cc57 --- /dev/null +++ b/desktop.dockerfile @@ -0,0 +1,9 @@ +ARG BASE_IMAGE=lcas.lincoln.ac.uk/ros_cuda:humble-main +ARG ROS_DISTRO=humble + +FROM ${BASE_IMAGE} AS base +ARG ROS_DISTRO + +RUN apt-get update && apt-get install -y --no-install-recommends \ + ros-${ROS_DISTRO}-desktop \ + && rm -rf /var/lib/apt/lists/* \ No newline at end of file diff --git a/docker/cyclonedds.xml b/docker/cyclonedds.xml deleted file mode 100644 index 82132ff..0000000 --- a/docker/cyclonedds.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - true - - - auto - - - \ No newline at end of file diff --git a/docker/nvidia-egl-vendor.json b/docker/nvidia-egl-vendor.json new file mode 100644 index 0000000..2eac23b --- /dev/null +++ b/docker/nvidia-egl-vendor.json @@ -0,0 +1,6 @@ +{ + "file_format_version" : "1.0.0", + "ICD" : { + "library_path" : "libEGL_nvidia.so.0" + } +} \ No newline at end of file diff --git a/docker/vnc-entrypoint.sh b/docker/vnc-entrypoint.sh new file mode 100644 index 0000000..4c6cebb --- /dev/null +++ b/docker/vnc-entrypoint.sh @@ -0,0 +1,66 @@ +#!/bin/bash + +echo +echo "****************************************************************************************************************************************" +echo "AOC VNC container starting..." +echo "****************************************************************************************************************************************" + +sudo rm -rf /tmp/.X1-lock /tmp/.X11-unix/X1 > /dev/null 2>&1 +screen -dmS turbovnc bash -c '/opt/TurboVNC/bin/vncserver :1 -depth 24 -noxstartup -securitytypes TLSNone,X509None,None 2>&1 | tee /tmp/vnc.log; read -p "Press any key to continue..."' + +echo "waiting for display to be up" +while ! xdpyinfo -display :1 2> /dev/null > /dev/null; do + sleep .1 +done +echo "display is up" + +echo "starting xfce4" +screen -dmS xfce4 bash -c 'DISPLAY=:1 /usr/bin/xfce4-session 2>&1 | tee /tmp/xfce4.log' +while [ -z "$DBUS_SESSION_BUS_ADDRESS" ]; do + if pgrep xfce4-session > /dev/null; then + XFCE_PID=$(pgrep xfce4-session) + export DBUS_SESSION_BUS_ADDRESS=$(grep -z DBUS_SESSION_BUS_ADDRESS /proc/$XFCE_PID/environ|cut -d= -f2-|tr -d '\0') + fi + sleep .1 +done +echo "xfce4 up" + +echo "starting novnc ${NOVNC_VERSION}" +screen -dmS novnc bash -c '/usr/local/novnc/noVNC-${NOVNC_VERSION}/utils/novnc_proxy --vnc localhost:5901 --listen 5801 2>&1 | tee /tmp/novnc.log' + +DISPLAY=:1 xhost +local: 2>/dev/null +echo "xhost +local: applied to :1" + +# set the wallpaper +( + export DISPLAY=:1 + CURRENT_USER="$(id -un)" + VNC_WALLPAPER=${VNC_WALLPAPER:-lcas} + WALLPAPER="/usr/share/backgrounds/xfce/${VNC_WALLPAPER}.jpg" + base="/backdrop/screen0/monitorVNC-0/workspace0" + + xfconf-query -c xfce4-desktop -p "${base}/image-show" -n -t bool -s true + xfconf-query -c xfce4-desktop -p "${base}/image-style" -n -t int -s 5 + xfconf-query -c xfce4-desktop -p "${base}/image-path" -n -t string -s "$WALLPAPER" + xfconf-query -c xfce4-desktop -p "${base}/last-image" -n -t string -s "$WALLPAPER" + xfconf-query -c xfce4-desktop -p "${base}/last-single-image" -n -t string -s "$WALLPAPER" + + # xfdesktop --reload +) & + +echo +echo "****************************************************************************************************************************************" +echo "VNC Desktop ready. Open your browser at http://localhost:5801/vnc.html?autoconnect=true or another hostname and port you may have forwarded." +echo "****************************************************************************************************************************************" +echo + +echo >&2 + +# This script can either be a wrapper around arbitrary command lines, +# or it will simply exec bash if no arguments were given +if [[ $# -eq 0 ]]; then + exec "/bin/bash" +else + exec "$@" +fi + diff --git a/docker/wallpapers/aoc.jpg b/docker/wallpapers/aoc.jpg new file mode 100644 index 0000000..49abcc1 Binary files /dev/null and b/docker/wallpapers/aoc.jpg differ diff --git a/docker/wallpapers/gir.jpg b/docker/wallpapers/gir.jpg new file mode 100644 index 0000000..1481b25 Binary files /dev/null and b/docker/wallpapers/gir.jpg differ diff --git a/docker/wallpapers/lcas.jpg b/docker/wallpapers/lcas.jpg new file mode 100644 index 0000000..5d4022d Binary files /dev/null and b/docker/wallpapers/lcas.jpg differ diff --git a/docker/wallpapers/uol.jpg b/docker/wallpapers/uol.jpg new file mode 100644 index 0000000..e3fb402 Binary files /dev/null and b/docker/wallpapers/uol.jpg differ diff --git a/vnc.dockerfile b/vnc.dockerfile new file mode 100644 index 0000000..7f840b2 --- /dev/null +++ b/vnc.dockerfile @@ -0,0 +1,105 @@ +ARG BASE_IMAGE=debian:trixie-slim +FROM ${BASE_IMAGE} AS base + +ARG BASE_IMAGE +ARG username=lcas +ENV BASE_IMAGE=${BASE_IMAGE} + +ENV DEBIAN_FRONTEND=noninteractive \ + DISPLAY=:1 + +# Install timezone +RUN ln -fs /usr/share/zoneinfo/UTC /etc/localtime \ + && export DEBIAN_FRONTEND=noninteractive \ + && apt-get update \ + && apt-get install -y --no-install-recommends tzdata \ + && dpkg-reconfigure --frontend noninteractive tzdata \ + && rm -rf /var/lib/apt/lists/* + +# Install packages +RUN apt-get update && apt-get install -y --no-install-recommends \ + ca-certificates \ + curl \ + gnupg2 \ + lsb-release \ + sudo \ + wget \ + libglvnd0 \ + libgl1 \ + libglx0 \ + libegl1 \ + libxext6 \ + libx11-6 \ + x11-utils \ + less \ + screen \ + unzip \ + x11-apps \ + && rm -rf /var/lib/apt/lists/* + +# Create non-root user +RUN useradd -m -s /bin/bash -G video,sudo ${username} \ + && echo "${username} ALL=(ALL) NOPASSWD:ALL" >/etc/sudoers.d/${username} \ + && chmod 0440 /etc/sudoers.d/${username} + +# Fix /tmp/.X11-unix permissions +RUN mkdir -p /tmp/.X11-unix \ + && chmod 1777 /tmp/.X11-unix + +# Install Python 3 for noVNC/websockify +RUN apt-get update && apt-get install -y python3 && rm -rf /var/lib/apt/lists/* + +# Install TurboVNC +RUN wget -q -O- https://packagecloud.io/dcommander/turbovnc/gpgkey | gpg --dearmor >/etc/apt/trusted.gpg.d/TurboVNC.gpg \ + && echo "deb [signed-by=/etc/apt/trusted.gpg.d/TurboVNC.gpg] https://packagecloud.io/dcommander/turbovnc/any/ any main" >>/etc/apt/sources.list.d/TurboVNC.list \ + && apt-get update && apt-get install -y turbovnc && rm -rf /var/lib/apt/lists/* + +# Install noVNC +ENV NOVNC_VERSION=1.4.0 +ENV WEBSOCKETIFY_VERSION=0.10.0 +RUN mkdir -p /usr/local/novnc \ + && curl -sSL https://github.com/novnc/noVNC/archive/v${NOVNC_VERSION}.zip -o /tmp/novnc-install.zip \ + && unzip /tmp/novnc-install.zip -d /usr/local/novnc \ + && cp /usr/local/novnc/noVNC-${NOVNC_VERSION}/vnc.html /usr/local/novnc/noVNC-${NOVNC_VERSION}/index.html \ + && curl -sSL https://github.com/novnc/websockify/archive/v${WEBSOCKETIFY_VERSION}.zip -o /tmp/websockify-install.zip \ + && unzip /tmp/websockify-install.zip -d /usr/local/novnc \ + && ln -s /usr/local/novnc/websockify-${WEBSOCKETIFY_VERSION} /usr/local/novnc/noVNC-${NOVNC_VERSION}/utils/websockify \ + && rm -f /tmp/websockify-install.zip /tmp/novnc-install.zip \ + && sed -i -E 's/^python /python3 /' /usr/local/novnc/websockify-${WEBSOCKETIFY_VERSION}/run + +FROM base AS xfce + +# Install XFCE4 +RUN apt-get update \ + && apt-get -y install \ + xfce4-session \ + xfce4-panel \ + xfdesktop4 \ + && rm -rf /var/lib/apt/lists/* +RUN apt-get purge -y xfce4-screensaver + +COPY vnc-entrypoint.sh /entrypoint.sh +RUN chmod +x /entrypoint.sh +ENTRYPOINT ["/entrypoint.sh"] + +# Copy in wallpaper +COPY ./wallpapers/*.jpg /usr/share/backgrounds/xfce/ + +# Allow other containers to share windows into this display +RUN echo 'xhost +local: 2>/dev/null' >> /etc/bash.bashrc && \ + echo "if [ -f /etc/bash.bashrc ]; then source /etc/bash.bashrc; fi" >> /root/.bashrc && \ + echo "alias cls='clear'" >> /etc/bash.bashrc && \ + echo '[[ $- == *i* ]] && [[ $$ -ne 1 ]] && echo -e "$(printf "%80s" | tr " " "=") \nYou are inside the VNC container,\n - You do not have access to ROS in this terminal\n - You may docker exec into other containers if that is configured.\n$(printf "%80s" | tr " " "=")\n"' >> /etc/bash.bashrc + +EXPOSE 5801 + +USER ${username} +ENV HOME=/home/${username} +WORKDIR ${HOME} +RUN mkdir -p ${HOME}/.local/bin + +ENV DISPLAY=:1 +ENV TVNC_VGL=1 +ENV SHELL=/bin/bash + +CMD ["sleep", "infinity"]