SetUp Docker - dingdongdengdong/astra_ws GitHub Wiki

if you don't know docker, must learn.

How to Run

code detail

.devcontainer

Let's break down Dockerfile, devcontainer.json, and ros_entrypoint.sh.


Dockerfile

The Dockerfile defines the steps to build a Docker image, which serves as the base for your development environment.

Deep Study

FROM ros:humble-ros-base

ARG USERNAME=rosdev
ARG UID=1000
ARG GID=$UID

# Install some dependencies packages
RUN apt update -q \
    && apt upgrade -q -y \
    && apt install -y --no-install-recommends \
    software-properties-common \
    python3-pip \
    python-is-python3 \
    xauth \
    ros-humble-desktop \
    && apt clean \
    && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*

# Create and switch to user
RUN groupadd -g $GID $USERNAME \
    && useradd -lm -u $UID -g $USERNAME -s /bin/bash $USERNAME \
    && echo "$USERNAME ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers
USER $USERNAME

# Create workspace so that user own this directory
RUN mkdir -p /home/$USERNAME/ros2_ws/src
WORKDIR /home/$USERNAME/ros2_ws

# Copy configuration files
RUN echo 'source /opt/ros/'$ROS_DISTRO'/setup.bash' >> /home/$USERNAME/.bashrc \
    && echo 'source /home/'$USERNAME'/ros2_ws/install/setup.bash' >> /home/$USERNAME/.bashrc \
    && echo 'export PATH="$HOME/.local/bin:$PATH"' >> /home/$USERNAME/.bashrc

# RUN echo 'export PYTHONPATH=$PYTHONPATH:/usr/lib/python3/dist-packages:/opt/ros/humble/lib/python3.10/site-packages/' >> /home/$USERNAME/.bashrc

# Setup entrypoint
COPY ./ros_entrypoint.sh /
ENTRYPOINT ["/ros_entrypoint.sh"]
CMD ["bash"]

Line-by-Line Explanation:

  • FROM ros:humble-ros-base:
    • This is the base image for your Docker container. It specifies that your image will be built on top of the official ROS 2 Humble Hawksbill ros-base image. ros-base includes the core ROS 2 packages and build tools but doesn't include desktop tools or GUI components.
  • ARG USERNAME=rosdev:
    • Declares a build-time argument USERNAME with a default value of rosdev. This allows you to customize the username when building the image without modifying the Dockerfile.
  • ARG UID=1000:
    • Declares a build-time argument UID with a default value of 1000. This is the User ID for the new user. Using 1000 is common as it often corresponds to the first non-root user on Linux systems.
  • ARG GID=$UID:
    • Declares a build-time argument GID (Group ID) and sets its default value to be the same as UID. This creates a group with the same ID as the user's ID.
  • RUN apt update -q \ ...:
    • This command block executes several apt commands in a single layer to minimize image size and improve build performance.
      • apt update -q: Updates the package lists quietly.
      • apt upgrade -q -y: Upgrades all installed packages quietly and without prompting.
      • apt install -y --no-install-recommends ...: Installs essential packages:
        • software-properties-common: Provides add-apt-repository.
        • python3-pip: Python package installer.
        • python-is-python3: Ensures python command points to python3.
        • xauth: Required for X11 forwarding (GUI applications).
        • ros-humble-desktop: Crucially, this installs the full ROS 2 desktop environment including RViz, Gazebo (if available for humble-desktop), and other GUI tools. This is a significant addition compared to the ros-base base image.
      • && apt clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*: Cleans up the APT cache and temporary files to reduce the final image size.
  • RUN groupadd -g $GID $USERNAME \ ...:
    • This block creates a new user and configures sudo access.
      • groupadd -g $GID $USERNAME: Creates a new user group with the specified GID and username.
      • useradd -lm -u $UID -g $USERNAME -s /bin/bash $USERNAME: Creates a new user with the specified UID, GID, home directory (-m), and sets /bin/bash as their default shell.
      • echo "$USERNAME ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers: Adds the new user to the sudoers file, allowing them to run sudo commands without being prompted for a password. This is common in development containers for convenience.
  • USER $USERNAME:
    • Switches the user context from root to the newly created $USERNAME for subsequent commands. This is a security best practice, as running processes as root is generally discouraged.
  • RUN mkdir -p /home/$USERNAME/ros2_ws/src:
    • Creates the ROS 2 workspace directory structure (ros2_ws/src) in the user's home directory. The -p flag ensures that parent directories are created if they don't exist.
  • WORKDIR /home/$USERNAME/ros2_ws:
    • Sets the working directory for any subsequent RUN, CMD, ENTRYPOINT, COPY, or ADD instructions to /home/$USERNAME/ros2_ws. This means when you enter the container, you'll land in this directory.
  • RUN echo 'source /opt/ros/'$ROS_DISTRO'/setup.bash' >> /home/$USERNAME/.bashrc \ ...:
    • Appends commands to the .bashrc file of the rosdev user. This ensures that these commands are executed every time a new bash shell is started by the user.
      • source /opt/ros/$ROS_DISTRO/setup.bash: Sources the main ROS 2 installation setup file. This sets up ROS 2 environment variables like ROS_DISTRO, AMENT_PREFIX_PATH, etc.
      • source /home/$USERNAME/ros2_ws/install/setup.bash: Sources the setup file for your custom ROS 2 workspace. This allows the system to find packages you build within /home/$USERNAME/ros2_ws.
      • export PATH="$HOME/.local/bin:$PATH": Adds the user's local binary directory to the system PATH, useful for executables installed via pip --user or other local installations.
  • # RUN echo 'export PYTHONPATH=...':
    • This line is commented out. If uncommented, it would add specific Python paths to PYTHONPATH. This can be necessary for certain Python packages or ROS 2 environments where Python modules are installed in non-standard locations.
  • COPY ./ros_entrypoint.sh /:
    • Copies the ros_entrypoint.sh script from your local directory into the root directory (/) of the Docker image.
  • ENTRYPOINT ["/ros_entrypoint.sh"]:
    • Sets the default command that will be executed when a container is started from this image. This means ros_entrypoint.sh will always run first.
  • CMD ["bash"]:
    • Provides default arguments to the ENTRYPOINT. In this case, if you run the container without specifying a command (e.g., docker run my_ros_image), bash will be passed as an argument to ros_entrypoint.sh. This effectively means ros_entrypoint.sh bash will be executed, giving you an interactive bash shell after the entrypoint script has set up the environment.

ros_entrypoint.sh

Deep Study

This script is the entry point for your Docker container. It's designed to set up the necessary ROS 2 environment before the main command (usually bash) is executed.

#!/bin/bash
# shellcheck disable=SC1090,SC1091
set -e

# setup ros2 environment
source /opt/ros/"$ROS_DISTRO"/setup.bash --
source ~/ros2_ws/install/setup.bash --
export PATH="$HOME/.local/bin:$PATH"
# export PYTHONPATH=$PYTHONPATH:/usr/lib/python3/dist-packages:/opt/ros/humble/lib/python3.10/site-packages/

exec "$@"

Line-by-Line Explanation:

  • #!/bin/bash:
    • Shebang line, specifying that the script should be executed with bash.
  • # shellcheck disable=SC1090,SC1091:
    • These are comments for shellcheck, a static analysis tool for shell scripts. They disable specific warnings related to source commands, which shellcheck can sometimes flag for dynamic paths.
  • set -e:
    • This is an important shell option. It means that the script will exit immediately if any command fails (returns a non-zero exit status). This helps in debugging, as you'll know if an environment setup step failed.
  • # setup ros2 environment:
    • A comment indicating the purpose of the following lines.
  • source /opt/ros/"$ROS_DISTRO"/setup.bash --:
    • Sources the main ROS 2 installation setup file. The ROS_DISTRO variable is set by the ros-humble-ros-base image. The -- at the end is often used to ensure source doesn't interpret subsequent arguments as options. This command sets up all the core ROS 2 environment variables.
  • source ~/ros2_ws/install/setup.bash --:
    • Sources the setup file for your custom ROS 2 workspace. This is crucial for your system to find and use any ROS 2 packages you develop and build within your ros2_ws. It overlays your workspace on top of the base ROS 2 installation.
  • export PATH="$HOME/.local/bin:$PATH":
    • Adds the ~/.local/bin directory to the beginning of the PATH environment variable. This ensures that any executables installed in this user-specific directory (e.g., via pip install --user) are found first when you run commands.
  • # export PYTHONPATH=...:
    • This line is commented out. Similar to the Dockerfile, if uncommented, it would extend the PYTHONPATH to include specific Python directories. This is useful if you encounter Python module import issues with ROS 2 or other Python libraries installed in non-standard locations.
  • exec "$@":
    • This is the final and crucial command.
      • "$@" expands to all the arguments passed to the ros_entrypoint.sh script. As per the Dockerfile's CMD ["bash"], this will typically be bash.
      • exec replaces the current shell process with the command specified by "$@". This means that after ros_entrypoint.sh has finished setting up the environment, it doesn't just run bash as a child process; it becomes the bash process. This is good practice for Docker entry points as it avoids an extra shell process and correctly passes signals (like Ctrl+C) to the main application.

devcontainer.json

Deep Study

This file is a configuration file used by the VS Code Dev Containers extension. It tells VS Code how to set up and interact with your development environment inside a Docker container.

{
    "name": "ROS 2 Workspace humble-ros-base", // Thx https://github.com/BrunoB81HK/ros2-workspace-devcontainer-template/tree/main/src/ros2-workspace
    "remoteUser": "rosdev",
    "build": {
        "dockerfile": "Dockerfile",
        "args": {
            "USERNAME": "rosdev",
            "GID": "1000",
            "UID": "1000"
        }
    },
    "customizations": {
        "vscode": {
            "extensions": [
                "ms-python.python",
                "charliermarsh.ruff",
                "ms-vscode.cpptools-extension-pack",
                "ms-iot.vscode-ros",
                "smilerobotics.urdf",
                "redhat.vscode-xml",
                "tamasfe.even-better-toml",
                "timonwong.shellcheck",
                "yzhang.markdown-all-in-one"
            ],
            "settings": {
                "files.associations": {
                    "*.rviz": "yaml",
                    "*.srdf": "xml",
                    "*.urdf": "xml",
                    "*.xacro": "xml"
                }
            }
        }
    },
    "workspaceFolder": "/home/rosdev/ros2_ws",
    "workspaceMount": "source=${localWorkspaceFolder},target=/home/rosdev/ros2_ws,type=bind",
    "mounts": [],
    "runArgs": [
        "--net=host",
        "-v", "/dev:/dev",
        "--privileged",
        "-v=/tmp/.X11-unix:/tmp/.X11-unix:rw", "--env=DISPLAY",
        "--gpus=all", "-e", "NVIDIA_DRIVER_CAPABILITIES=all"
    ],
    "features": {
        // "ghcr.io/rocker-org/devcontainer-features/miniforge:1": {}
    }
}

Line-by-Line Explanation:

  • "name": "ROS 2 Workspace humble-ros-base":
    • This is a human-readable name for your development container that will appear in VS Code.
  • "remoteUser": "rosdev":
    • Specifies the default user that VS Code will connect as inside the container. This should match the USERNAME you created in your Dockerfile.
  • "build":
    • This section configures how the Docker image for the container is built.
      • "dockerfile": "Dockerfile": Points to your Dockerfile in the same directory, telling VS Code to build the image from it.
      • "args": Passes build arguments to your Dockerfile. These values will override any default ARG values in the Dockerfile.
        • "USERNAME": "rosdev", "GID": "1000", "UID": "1000": Ensures the user rosdev with specified IDs is created during the image build process.
  • "customizations":
    • This section allows you to customize VS Code's behavior specifically for this dev container.
      • "vscode": Contains VS Code-specific customizations.
        • "extensions": An array of VS Code extension IDs. These extensions will be automatically installed and enabled within the dev container environment.
          • "ms-python.python": Python extension for linting, debugging, etc.
          • "charliermarsh.ruff": Ruff linter for Python.
          • "ms-vscode.cpptools-extension-pack": C/C++ extension pack for IntelliSense, debugging, etc.
          • "ms-iot.vscode-ros": Official Microsoft ROS extension, providing ROS-specific features.
          • "smilerobotics.urdf": URDF (Unified Robot Description Format) file support.
          • "redhat.vscode-xml": XML language support.
          • "tamasfe.even-better-toml": TOML (Tom's Obvious, Minimal Language) file support.
          • "timonwong.shellcheck": Shell script linting.
          • "yzhang.markdown-all-in-one": Comprehensive Markdown support.
        • "settings": VS Code workspace settings that will be applied when you open the project in the container.
          • "files.associations": Maps file extensions to specific language modes, improving syntax highlighting and IntelliSense.
            • "*.rviz": "yaml": Treats .rviz files as YAML.
            • "*.srdf": "xml": Treats .srdf (Semantic Robot Description Format) files as XML.
            • "*.urdf": "xml": Treats .urdf files as XML.
            • "*.xacro": "xml": Treats .xacro (XML macro) files as XML.
  • "workspaceFolder": "/home/rosdev/ros2_ws":
    • Sets the default working directory for the VS Code workspace inside the container. When you open a terminal in VS Code, it will typically start in this directory.
  • "workspaceMount": "source=${localWorkspaceFolder},target=/home/rosdev/ros2_ws,type=bind":
    • This crucial setting mounts your local project folder into the container.
      • source=${localWorkspaceFolder}: Refers to the path of your local folder opened in VS Code.
      • target=/home/rosdev/ros2_ws: Specifies the destination path inside the container where your local folder will be mounted.
      • type=bind: Uses a bind mount, meaning changes on your local machine are immediately reflected in the container and vice-versa.
  • "mounts": []:
    • An empty array, but this is where you would define additional mounts (e.g., named volumes, other bind mounts) if needed.
  • "runArgs":
    • An array of additional arguments that are passed directly to the docker run command when the container is created. These are critical for specific container functionalities.
      • "--net=host": Configures the container to use the host's network namespace. This allows the container to directly access network services on the host and vice-versa, which can simplify ROS 2 communication, especially if you have other ROS nodes running outside the container.
      • "-v", "/dev:/dev": Mounts the host's /dev directory into the container. This is essential for the container to access hardware devices (e.g., USB ports, serial devices) connected to the host machine. The comment // See: https://micro.ros.org/docs/tutorials/core/first_application_linux/ suggests its relevance for micro-ROS development.
      • "--privileged": Grants extended privileges to the container. This can be necessary for certain operations that require direct hardware access or low-level system calls (e.g., running ffmpeg as mentioned in the comment). Use with caution as it reduces isolation.
      • "-v=/tmp/.X11-unix:/tmp/.X11-unix:rw", "--env=DISPLAY": These arguments enable X11 forwarding, allowing GUI applications (like RViz or Gazebo) running inside the Docker container to display their windows on your host machine's desktop.
        • -v=/tmp/.X11-unix:/tmp/.X11-unix:rw: Mounts the X11 socket, which is how X clients communicate with the X server.
        • --env=DISPLAY: Passes the DISPLAY environment variable from the host to the container, telling GUI applications where to connect to the X server.
      • "--gpus=all", "-e", "NVIDIA_DRIVER_CAPABILITIES=all": These arguments enable GPU access within the container, which is vital for applications requiring hardware acceleration (e.g., simulation, machine learning inference). This requires NVIDIA drivers and Docker's NVIDIA Container Toolkit to be installed on your host system.
        • --gpus=all: Exposes all available GPUs to the container.
        • -e NVIDIA_DRIVER_CAPABILITIES=all: Specifies that the container should have access to all NVIDIA driver capabilities.
  • "features": {}:
    • This section allows you to add pre-built Dev Container Features to your environment. Features are self-contained bundles of installation scripts and configurations. The commented-out line "ghcr.io/rocker-org/devcontainer-features/miniforge:1": {} shows an example of how to add a feature (in this case, Miniforge for Conda environments).

diagram