Docker#
This page describes how to write Dockerfiles in Autoware. pre-commit checks each Dockerfile with hadolint. The Dockerfiles of the docker/ directory of the autoware repository follow these rules, and the examples on this page come from them.
References#
hadolint#
pre-commit runs hadolint on all Dockerfiles. hadolint also checks the shell commands of the RUN instructions with ShellCheck. Fix every warning.
If a rule does not fit one instruction, turn it off for that instruction with a comment on the line before it. For example, base.Dockerfile turns off DL3003 (use WORKDIR to change the directory) for a RUN instruction that uses cd:
# hadolint ignore=DL3003
RUN --mount=type=bind,source=ansible,target=/tmp/ansible/ansible \
...
pipx install --include-deps "ansible==10.*" && \
cd /tmp/ansible && \
...
If a rule does not fit the whole repository, add it to the .hadolint.yaml file at the root of the repository, with a comment that gives the reason. For example, the .hadolint.yaml of the autoware repository turns off DL3002 (the last user must not be root), because the intermediate stages of the multi-stage builds need root.
Name the files#
Give each Dockerfile a name of the form <name>.Dockerfile, for example base.Dockerfile and universe-cuda.Dockerfile. Put them in the docker/ directory. If a project builds more than one image, describe the targets in a Docker Bake file such as docker/docker-bake.hcl.
Use multi-stage builds#
Build the code in one stage and copy only the result to the runtime stage. Give each stage a name with AS. For example, universe.Dockerfile builds Autoware in the universe-devel stage and copies /opt/autoware to the universe stage:
FROM ${CORE_IMAGE} AS universe
# ...
COPY --from=universe-devel /opt/autoware /opt/autoware
Set the shell#
Set Bash with pipefail as the shell at the start of each stage, so that a failed command in a pipe fails the build (hadolint rule DL4006):
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
Install packages#
- Install
aptpackages with--no-install-recommends. Use cache mounts for
apt,pip, andccache, so that the next build reuses the downloaded files:RUN --mount=type=cache,id=apt-cache-${ROS_DISTRO},target=/var/cache/apt,sharing=locked \ --mount=type=cache,id=apt-lists-${ROS_DISTRO},target=/var/lib/apt/lists,sharing=locked \ apt-get update && \ apt-get install -y --no-install-recommends \ sudo \ pipx
Install the dependencies of ROS packages with
rosdepfrom theirpackage.xmlfiles. Copy only thepackage.xmlfiles before this step, so that a change to the source code does not runrosdepagain:COPY --parents --chown=${USERNAME}:${USERNAME} src/**/package.xml /tmp/autoware/
- Use the Ansible roles of the
autowarerepository to install other dependencies, so that the Docker images and the source installation install the same dependencies.
Pin remote files#
When you download a file with ADD, give its checksum. Then the build fails if the file changes. For example, base.Dockerfile pins the ROS package schemas:
ADD --chmod=644 --checksum=sha256:585201809cbf8c81ec5a4d7d84d827f4676dd62a4bc0a812da7a485dea2ad43a \
http://download.ros.org/schema/package_format3.xsd /usr/share/xml/ros/
Users and permissions#
The Autoware images create the user aw and run the commands of the build as this user where possible. The entrypoint runs as root. It changes the user and group IDs of aw to the IDs of the host user and applies the network settings for DDS. Then it starts the command as aw with gosu.
Comments#
Write a comment for each step that is not obvious. Say why the step is necessary, not only what it does. For example:
# ament_xmllint downloads the package.xml schema from download.ros.org for every package,
# and its tests time out when that server is slow. The catalog points xmllint to local copies.