Skip to main content
Version: 7

Use MoveIt Pro without the CLI

If the moveit_pro CLI verb is inaccessible for your use case, such as in a CI/CD Pipeline, docker compose can be used to run all the services associated with the MoveIt Pro Runtime, including the UI. You will need your workspace docker-compose.yaml to extend the services found in the docker-compose.yaml installed by MoveIt Pro (default location:/opt/moveit_pro).

Click here to see the contents of docker-compose.yaml
# Launch MoveIt Pro
# Notes on usage:
# Several environment variables can be set on the host to configure the containers.
# The variables are documented in the `environment:` section of the `base` service.
# The recommended method for overriding these variables is by setting them using moveit_pro configure.

# Host sysfs the license fingerprint needs, attached only to the services that resolve it.
# Deliberately NOT on `base`: that is the documented customer extension point, and the shipped
# overlay template extends it with non-privileged services (rest_api, rviz, setup_assistant) for
# which "already privileged, so this grants nothing" would be false.
#
# The fallback tier reads the permanent hardware MAC from /sys/class/net, which is
# network-namespace scoped — a bridge-networked container sees only its own veth. Reading the
# host's copy makes the fingerprint identical in every network mode, so this no longer depends on
# `network_mode: host`. The whole tree is mounted rather than just class/net because those entries
# are symlinks into /sys/devices. Long syntax with an explicit recursive read-only bind so a kernel
# that cannot honor it fails loudly instead of quietly leaving submounts writable.
#
# TPM access needs nothing here: `tss` is listed in bin/docker/utils/user_groups.conf, so
# create_user.sh joins the container user to it and aligns its GID to whatever owns /dev/tpmrm0 on
# this host — the same mechanism already used for the render and video device groups.
x-license-fingerprint-host-access: &license-fingerprint-host-access
environment: &license-fingerprint-environment
MOVEIT_HOST_SYSFS: /host/sys
volumes:
- &license-fingerprint-host-sysfs-volume
type: bind
# Production reads the real host. Release validation overrides this with
# a deterministic synthetic sysfs tree because ephemeral CI runners have
# neither a TPM nor a permanent hardware NIC.
source: ${MOVEIT_HOST_SYSFS_SOURCE:-/sys}
target: /host/sys
read_only: true
bind:
recursive: readonly

services:
# Sets common properties for other services. Should not be instantiated directly.
base:
# We assume the user has a user workspace and that user workspace contains a Dockerfile with a target layer called "user-overlay"
# As per these docs:
# https://docs.picknik.ai/en/stable/getting_started/configuration_tutorials/docker_configuration/create_overlay_docker_image.html#creating-a-release-overlay-container
# MOVEIT_PRO_BASE_IMAGE: The image and tag to where the user overlay will start FROM
# MOVEIT_DOCKER_TAG: Version to pull from Docker Hub, e.g. "v1.10". Defaults to "main".
# MOVEIT_ROS_DISTRO: ROS2 distribution to pull from Docker Hub, defaults to "jazzy".
# The following args ensure log files aren't owned by root
# MOVEIT_USERNAME: the username on the host
# MOVEIT_USER_UID: the user uid on the host
# MOVEIT_USER_GID: the user gid on the host
image: moveit-pro-base:${MOVEIT_DOCKER_TAG:-main}-${MOVEIT_ROS_DISTRO:-jazzy}-${MOVEIT_HOST_USER_WORKSPACE_NAME:-}${MOVEIT_DEV_SUFFIX:-}
build:
context: ${MOVEIT_HOST_USER_WORKSPACE}
pull: true
args:
USERNAME: ${MOVEIT_USERNAME}
USER_UID: ${MOVEIT_USER_UID}
USER_GID: ${MOVEIT_USER_GID}
MOVEIT_PRO_BASE_IMAGE: picknikciuser/moveit-pro:${MOVEIT_DOCKER_TAG:-main}-${MOVEIT_ROS_DISTRO:-jazzy}${MOVEIT_TARGET:-}
network_mode: host
# Run an init process within the container to allow using default signal handling to cleanly shut down the container.
# See this doc for more info: https://docs.docker.com/compose/compose-file/compose-file-v3/#init
init: true
environment:
# The default for MoveIt Pro will use mock hardware, if available, to simulate the robot.
- MOCK_HARDWARE=${MOCK_HARDWARE:-true}
# TODO: Should we delete or tick/tock ROS_DOMAIN_ID here?
# The ROS domain ID to use in deployment. See https://docs.ros.org/en/jazzy/Concepts/About-Domain-ID.html
- ROS_DOMAIN_ID
# Provides a license key for the MoveIt Pro application to validate in the container.
- MOVEIT_LICENSE_KEY
# Required shared secret for client-facing Runtime endpoints: REST,
# Foxglove, video, terminal, and MCP. Runtime processes fail closed when
# the deployment leaves this blank
# or unset.
- MOVEIT_FRONTEND_KEY=${MOVEIT_FRONTEND_KEY:-}
# MCP Streamable HTTP rejects non-loopback Host and Origin values unless
# the deployment explicitly allowlists them.
- MOVEIT_MCP_ALLOWED_HOSTS
- MOVEIT_MCP_ALLOWED_ORIGINS
# Optional host TLS overrides are mounted at fixed in-container paths
# below. Empty host values keep these environment variables unset.
- MOVEIT_TLS_CERT_FILE=${MOVEIT_TLS_CERT_FILE:+/etc/moveit_pro/tls/cert.pem}
- MOVEIT_TLS_KEY_FILE=${MOVEIT_TLS_KEY_FILE:+/etc/moveit_pro/tls/key.pem}
- MOVEIT_TLS_CERT_DIR=/var/lib/moveit_pro/tls
# Robot configuration package for deployment. Defaults to a mock hardware UR5e.
- MOVEIT_CONFIG_PACKAGE=${MOVEIT_CONFIG_PACKAGE:-picknik_ur_mock_hw_config}
# Image variant is configuration evidence only; the status API combines it
# with direct process reports before presenting capability state.
- MOVEIT_TARGET
# Exact target identity for the AgentInfo/MCP cooperative handshake.
# The CLI exports all four values before launch. For the supported
# direct-Compose development path, runtime.launch.py derives the blank
# workspace value before the Agent and REST server start. Keep the
# fallback workspace path absolute so a host stdio client derives the
# same ID as the contained HTTP server.
- MOVEIT_DEPLOYMENT_ID=${MOVEIT_DEPLOYMENT_ID:-moveit_pro}
- MOVEIT_INSTANCE_ID=${MOVEIT_INSTANCE_ID:-default}
- MOVEIT_ROBOT_ID=${MOVEIT_ROBOT_ID:-${MOVEIT_CONFIG_PACKAGE:-picknik_ur_mock_hw_config}}
- MOVEIT_WORKSPACE_ID=${MOVEIT_WORKSPACE_ID:-}
# Optional opaque launcher generation used only to ensure a lifecycle
# session never adopts a later same-target runtime incarnation.
- MOVEIT_MCP_RUNTIME_GENERATION
- MOVEIT_MCP_RUNTIME_BINDING_TIER=cooperative
# Persistent, owner-protected audit sink for the REST-hosted HTTP MCP
# server. Named instances use distinct files so independent server
# processes never interleave one hash chain.
- MOVEIT_MCP_AUDIT_LOG=/home/${MOVEIT_USERNAME:-moveit-pro-user}/.local/share/moveit_pro/audit/mcp-http-${MOVEIT_INSTANCE_ID:-default}.jsonl
# The directory on the host machine where persistent MoveIt Pro data is stored, e.g. client authentication data.
# Typically, this is "$HOME/.local/share/moveit_pro", which is the default.
- MOVEIT_HOST_DATA_DIR=${MOVEIT_HOST_DATA_DIR:-$HOME/.local/share/moveit_pro}
# The directory inside the container where persistent MoveIt Pro data is mounted.
- MOVEIT_CONTAINER_DATA_DIR=/home/${MOVEIT_USERNAME:-moveit-pro-user}/.local/share/moveit_pro
# The path to the colcon workspace on the host machine containing user-defined packages,
# containing, e.g., configuration packages and custom behaviors
# Defaults to a folder named "moveit_pro_example_ws" in the current directory.
# If this folder is blank, it will be ignored at container startup.
- MOVEIT_HOST_USER_WORKSPACE=${MOVEIT_HOST_USER_WORKSPACE:-${PWD}/moveit_pro_example_ws}
# The location of the user workspace inside the Docker container
- USER_WS=/home/${MOVEIT_USERNAME:-moveit-pro-user}/user_ws/
# Network interface to use in the generated DDS configuration (in the container). Defaults to "lo".
- CYCLONEDDS_NETWORK_INTERFACE=${CYCLONEDDS_NETWORK_INTERFACE:-lo}
# Comma-separated list of the IP addresses of machines that will participate in the ROS network,
# including the host machine's own IP address. For example: CYCLONEDDS_PEER_ADDRESSES=192.168.0.35,192.168.0.37)
# Defaults to "127.0.0.1"
- CYCLONEDDS_PEER_ADDRESSES=${CYCLONEDDS_PEER_ADDRESSES:-127.0.0.1}
# TODO: Should we delete or tick/tock CYCLONEDDS_USE_MULTICAST here?
- CYCLONEDDS_USE_MULTICAST=${CYCLONEDDS_USE_MULTICAST:-false}
# Highest participant index Cyclone probes when auto-selecting one. Raise it on hosts running many
# participants (e.g. external `docker exec` clients alongside the Runtime) so service discovery does not
# silently fail once the indices are exhausted. The 120 default must stay in sync with
# DEFAULT_MAX_AUTO_PARTICIPANT_INDEX in bin/docker/utils/create_cyclonedds_xml.py.
- CYCLONEDDS_MAX_AUTO_PARTICIPANT_INDEX=${CYCLONEDDS_MAX_AUTO_PARTICIPANT_INDEX:-120}
# The ROS middleware to use. Defaults to rmw_cyclonedds_cpp, the CycloneDDS middleware.
# MoveIt Pro supports CycloneDDS and FastRTPS, and will respect your CYCLONEDDS_URI and FASTRTPS_DEFAULT_PROFILES_FILE for custom configurations.
- RMW_IMPLEMENTATION=${RMW_IMPLEMENTATION:-rmw_cyclonedds_cpp}
- CYCLONEDDS_URI=${CYCLONEDDS_URI:-}
- MOVEIT_CYCLONEDDS_URI_CONTAINER_VALUE=${MOVEIT_CYCLONEDDS_URI_CONTAINER_VALUE:-}
# If using FastRTPS, setting this variable to 1 allows modifying Quality-of-Service (QoS) settings from a specified XML file.
- RMW_FASTRTPS_USE_QOS_FROM_XML
# Renderer used for Gazebo (can be ogre or ogre2)
- GAZEBO_RENDERER=${GAZEBO_RENDERER:-ogre}
# Allow the user to run graphical programs from within the container. You probably don't want to change these!
- DISPLAY
- QT_X11_NO_MITSHM=1
# User settings
- USER_UID=${MOVEIT_USER_UID:-1000}
- USERNAME=${MOVEIT_USERNAME:-moveit-pro-user}
# If USE_HOST_DDS=true, MoveIt Pro will replicate the host DDS configuration files.
- USE_HOST_DDS=${USE_HOST_DDS:-false}
# enable bash prompt colorization
- TERM=xterm-256color
# Short, uniform rcutils console format and forced color output. Production
# source of truth — the release image's `FROM scratch ... COPY --from=release-base / /`
# drops the ENV set in `ubuntu-common`. The release stages re-assert them
# too; these lines are the runtime override that wins over any image default.
# See docs_nonpublic/design/Backend-Console-Log-Format.md.
- "RCUTILS_CONSOLE_OUTPUT_FORMAT=[{severity}] [{date_time_with_ms}] {message}"
- RCUTILS_COLORIZED_OUTPUT=${RCUTILS_COLORIZED_OUTPUT:-1}
# Pass through the user's language and locale.
- SET_LANG=${LANG}
- SET_LC_ALL=${LC_ALL:-}
# Optional: API key for Gemini Behaviors and explicitly enabled MCP inspection.
# Leave unset on the host if Gemini integration is not used.
- GOOGLE_GEMINI_API_KEY
- MOVEIT_MCP_GEMINI_ENABLED
- MOVEIT_MCP_GEMINI_MODEL
# Optional: point at a Quest teleop APK you staged yourself, and/or move
# where a fetched one is cached. MoveIt Pro does not ship that APK (see
# src/teleop/quest_oculus/oculus_reader/APK/APK_PROVENANCE.md); it is
# fetched and digest-verified on first headset connect. Set
# QUEST_OCULUS_APK_PATH on an air-gapped machine so nothing is fetched.
# Both paths are read inside the container, so point them at something
# mounted there. The default cache lives under the bind-mounted data dir
# and so survives a recreate; an override elsewhere in the container does
# not, which quietly turns "one download per machine" into one per
# container lifetime.
- QUEST_OCULUS_APK_PATH=${QUEST_OCULUS_APK_PATH:-}
- QUEST_OCULUS_APK_CACHE=${QUEST_OCULUS_APK_CACHE:-}
# Optional: when MOVEIT_FRONTEND_SETTINGS_HOST_FILE is set on the host
# (typically via `moveit_pro run --frontend-settings <path>`), the file is
# bind-mounted into the container at the path below and surfaced to the
# REST API as MOVEIT_FRONTEND_SETTINGS_FILE. The `:+` substitution makes
# the in-container env var unset when no host file was supplied so the
# config-package fallback can take over.
- MOVEIT_FRONTEND_SETTINGS_FILE=${MOVEIT_FRONTEND_SETTINGS_HOST_FILE:+/etc/moveit_pro/frontend_settings.yaml}
# Terminal pane host-shell opt-in + bind host. Production Runtime uses
# authenticated TLS on all host interfaces so a remote Desktop App can
# reach the advertised endpoint. Operators can override this for an SSH
# tunnel or another explicitly managed trusted-ingress topology.
- MOVEIT_ALLOW_HOST_SHELL=${MOVEIT_ALLOW_HOST_SHELL:-}
- MOVEIT_TERMINAL_BIND_HOST=${MOVEIT_TERMINAL_BIND_HOST:-0.0.0.0}
# Cameras already encoded upstream (e.g. an RTSP camera), relayed to the
# Desktop App instead of decoded, republished and re-encoded. Comma-separated
# `<ros topic>=<source url>` pairs; see the Robot Hardware and Cameras
# troubleshooting guide. Empty means every topic uses the ROS encode path.
- MOVEIT_WEBRTC_PASSTHROUGH=${MOVEIT_WEBRTC_PASSTHROUGH:-}
# Extra ICE candidate hosts advertised on top of the addresses MediaMTX
# discovers from the container's own interfaces. A deployment the Desktop App
# reaches by hostname, or through a NAT, needs its external address here
# or the offered candidates are all unreachable. Comma-separated.
- MOVEIT_WEBRTC_ADDITIONAL_HOSTS=${MOVEIT_WEBRTC_ADDITIONAL_HOSTS:-}
# Camera stream tuning for a bandwidth-constrained link: the VP8 target
# bitrate per open camera pane (e.g. 500k) and a `<width>x<height>` box
# larger frames are scaled down into, aspect ratio kept (e.g. 640x480).
# Empty keeps 2M at the camera's native resolution. See the Robot
# Hardware and Cameras troubleshooting guide.
- MOVEIT_WEBRTC_BITRATE=${MOVEIT_WEBRTC_BITRATE:-}
- MOVEIT_WEBRTC_MAX_RESOLUTION=${MOVEIT_WEBRTC_MAX_RESOLUTION:-}
# MediaMTX must bind the same port that the cloud media NLB forwards.
# Unlike ordinary published ports, ICE candidates cannot be remapped.
- MOVEIT_WEBRTC_MEDIA_PORT=${MOVEIT_WEBRTC_MEDIA_PORT:-3203}
# Allow the user to use the host's network video4linux and usb_device devices.
# "c 81:* rmw": This rule specifies that the container has read, write, and mknod access (rmw) to all character devices (c) with a major number of 81 (video4linux).
# "c 189:* rmw": This rule gives the container read, write, and mknod access (rmw) to all character devices (c) with a major number of 189 (usb_device).
device_cgroup_rules:
- "c 81:* rmw"
- "c 189:* rmw"
volumes:
# MOVEIT_HOST_LOG_DIR: host directory for the ROS logs. Defaults to the
# shared log directory; `moveit_pro run --instance` points it at a
# per-instance subdirectory so concurrent deployments keep separate logs.
- ${MOVEIT_HOST_LOG_DIR:-$HOME/.ros/log_moveit_pro}:/home/${MOVEIT_USERNAME:-moveit-pro-user}/.ros/log
- type: tmpfs
target: /home/${MOVEIT_USERNAME:-moveit-pro-user}/.config/moveit_pro/
- ${MOVEIT_HOST_DATA_DIR:-$HOME/.local/share/moveit_pro}:/home/${MOVEIT_USERNAME:-moveit-pro-user}/.local/share/moveit_pro/:rw
# Persist the root adb server's identity key so a USB Meta Quest headset's
# "Always allow from this computer" authorization survives container
# recreation (see bin/docker/utils/start_adb_server.sh). Root-owned, isolated
# in its own adb/ subdir of the durable data dir.
- ${MOVEIT_HOST_DATA_DIR:-$HOME/.local/share/moveit_pro}/adb:/root/.android:rw
- ${MOVEIT_HOST_USER_WORKSPACE:-./moveit_pro_example_ws}:/home/${MOVEIT_USERNAME:-moveit-pro-user}/user_ws/:rw
# Mount the host's DDS config(s) so we can talk to user's ROS tooling
# The CLI stages local path and file:// CYCLONEDDS_URI values in this
# bind-mounted directory. Inline XML and remote http(s) URIs do not need
# a host file mount and therefore use /dev/null. The CYCLONEDDS_URI
# fallback preserves direct Docker Compose use for a single local path.
- ${MOVEIT_CYCLONEDDS_URI_MOUNT_SOURCE:-${CYCLONEDDS_URI:-/dev/null}}:/home/${MOVEIT_USERNAME:-moveit-pro-user}/.ros/cyclonedds_host_configs:ro
# TODO: This should only happen if USE_HOST_DDS=true, RMW_IMPLEMENTATION=rmw_fastrtps_cpp, and FASTRTPS_DEFAULT_PROFILES_FILE points to a file.
- ${FASTRTPS_DEFAULT_PROFILES_FILE:-/dev/null}:/home/${MOVEIT_USERNAME:-moveit-pro-user}/.ros/fastdds_host.xml
# Allow the user to run graphical programs from within the docker container.
- /tmp/.X11-unix:/tmp/.X11-unix:ro
# Optional bind-mount for user-supplied frontend settings overrides.
# When MOVEIT_FRONTEND_SETTINGS_HOST_FILE is unset, /dev/null is mounted
# and the REST API ignores it (its env var is also empty in that case).
- ${MOVEIT_FRONTEND_SETTINGS_HOST_FILE:-/dev/null}:/etc/moveit_pro/frontend_settings.yaml:ro
# Optional host TLS pair and writable fallback-generation directory.
# The generated-pair default lives inside the persistent data directory;
# never mount /dev/null where a writable directory is required.
- ${MOVEIT_TLS_CERT_FILE:-/dev/null}:/etc/moveit_pro/tls/cert.pem:ro
- ${MOVEIT_TLS_KEY_FILE:-/dev/null}:/etc/moveit_pro/tls/key.pem:ro
- ${MOVEIT_TLS_CERT_DIR:-${MOVEIT_HOST_DATA_DIR:-$HOME/.local/share/moveit_pro}/tls}:/var/lib/moveit_pro/tls:rw

# Starts the MoveIt Pro Runtime Agent and client transport.
runtime:
<<: *license-fingerprint-host-access
image: moveit-pro-runtime:${MOVEIT_DOCKER_TAG:-main}-${MOVEIT_ROS_DISTRO:-jazzy}-${MOVEIT_HOST_USER_WORKSPACE_NAME:-}${MOVEIT_DEV_SUFFIX:-}
extends: base
privileged: true
# A service-level `environment` or `volumes` key replaces the one the merge
# key above supplies, so both blocks restate the fingerprint entries.
environment:
<<: *license-fingerprint-environment
MOVEIT_INFERENCE_KEY: ${MOVEIT_INFERENCE_KEY:-}
# Product-owned secret seam between Trainer and the inference sidecar.
# The Runtime writes one Hugging Face token; the sidecar receives the
# same named volume read-only and never sees other Trainer credentials.
MOVEIT_PRO_SERVING_CREDENTIALS_ROOT: /home/${MOVEIT_USERNAME:-moveit-pro-user}/.local/share/moveit_pro/trainer/serving_credentials
volumes:
- *license-fingerprint-host-sysfs-volume
- moveit_pro_inference_credentials:/home/${MOVEIT_USERNAME:-moveit-pro-user}/.local/share/moveit_pro/trainer/serving_credentials:rw
# Empty by default (container keeps its own PID namespace). `moveit_pro run
# --allow-host-shell` sets MOVEIT_TERMINAL_PID_MODE=host so the Terminal
# pane's Host target can nsenter into the host — only when opted in.
pid: "${MOVEIT_TERMINAL_PID_MODE:-}"
command: ros2 launch moveit_studio_agent runtime.launch.py
# Start only after the drivers container is healthy. Subset launches skip
# this with `--no-deps`: `moveit_pro run --no-drivers` does it automatically,
# and direct `docker compose up <subset>` users add `--no-deps` by hand to
# start a service without pulling in its dependency.
# No restart policy by design: a failed container exits non-zero and stays
# exited (fail fast) — recovery is owned by the deployment's supervisor,
# which can watch the health status below to decide when to restart.
depends_on:
drivers:
condition: service_healthy
# Healthy once the agent is actually serving: /health returns 200 only when
# the Objective Server heartbeat is fresh (see health_api in src/rest_api).
# An `unhealthy` status never auto-restarts anything — it is a monitoring
# signal for `depends_on` gates and external supervisors.
healthcheck:
test: ["CMD-SHELL", "curl -kfsS https://127.0.0.1:3200/health >/dev/null"]
interval: 5s
timeout: 5s
retries: 24
start_period: 30s

# Client-owned local MCP bootstrap. This profile-gated definition is never
# started by `compose up`; `moveit_pro mcp serve` creates exactly one
# ephemeral `docker compose run --rm --no-deps -T` container from it. It must
# use the vendor release image directly: every other backend image is built
# from the mounted user workspace and therefore cannot be a bootstrap trust
# anchor.
mcp:
profiles: ["mcp-bootstrap"]
image: picknikciuser/moveit-pro:${MOVEIT_DOCKER_TAG:-main}-${MOVEIT_ROS_DISTRO:-jazzy}${MOVEIT_TARGET:-}
init: true
network_mode: host
stdin_open: true
environment:
- USER_UID=${MOVEIT_USER_UID:-1000}
# Preserve an omitted GID until the entrypoint records whether the caller
# supplied it, then normalize it to USER_UID before creating the user.
- USER_GID=${MOVEIT_USER_GID:-}
- USERNAME=${MOVEIT_USERNAME:-moveit-pro-user}
- USER_WS=/home/${MOVEIT_USERNAME:-moveit-pro-user}/user_ws/
- HOME=/home/${MOVEIT_USERNAME:-moveit-pro-user}
- MOVEIT_CONFIG_PACKAGE=${MOVEIT_CONFIG_PACKAGE:-picknik_ur_mock_hw_config}
- MOVEIT_DEPLOYMENT_ID
- MOVEIT_INSTANCE_ID
- MOVEIT_ROBOT_ID
- MOVEIT_WORKSPACE_ID
- MOVEIT_MCP_RUNTIME_BINDING_TIER=cooperative
- GOOGLE_GEMINI_API_KEY
- MOVEIT_MCP_GEMINI_ENABLED
- MOVEIT_MCP_GEMINI_MODEL
- MOVEIT_MCP_SESSION_CONTEXT
- MOVEIT_MCP_SESSION_CREDENTIAL
- MOVEIT_MCP_SESSION_EXPIRES_AT
- MOVEIT_MCP_LIFECYCLE_SOCKET=/run/moveit-pro-mcp/lifecycle.sock
- MOVEIT_MCP_AUDIT_LOG=/run/moveit-pro-mcp/audit.jsonl
- MOVEIT_MCP_LOG_DIR=/home/${MOVEIT_USERNAME:-moveit-pro-user}/.ros/log
- ROS_DOMAIN_ID
- RMW_IMPLEMENTATION=${RMW_IMPLEMENTATION:-rmw_cyclonedds_cpp}
- USE_HOST_DDS=${USE_HOST_DDS:-false}
- CYCLONEDDS_NETWORK_INTERFACE=${CYCLONEDDS_NETWORK_INTERFACE:-lo}
- CYCLONEDDS_PEER_ADDRESSES=${CYCLONEDDS_PEER_ADDRESSES:-127.0.0.1}
- CYCLONEDDS_USE_MULTICAST=${CYCLONEDDS_USE_MULTICAST:-false}
- CYCLONEDDS_MAX_AUTO_PARTICIPANT_INDEX=${CYCLONEDDS_MAX_AUTO_PARTICIPANT_INDEX:-120}
- CYCLONEDDS_URI=${CYCLONEDDS_URI:-}
- MOVEIT_CYCLONEDDS_URI_CONTAINER_VALUE=${MOVEIT_CYCLONEDDS_URI_CONTAINER_VALUE:-}
- RMW_FASTRTPS_USE_QOS_FROM_XML
- SET_LANG=${LANG}
- SET_LC_ALL=${LC_ALL:-}
volumes:
- ${MOVEIT_HOST_USER_WORKSPACE:-./moveit_pro_example_ws}:/home/${MOVEIT_USERNAME:-moveit-pro-user}/user_ws/:${MOVEIT_MCP_WORKSPACE_ACCESS:-ro}
- ${MOVEIT_MCP_CONFIG_DIR:-/dev/null}:/home/${MOVEIT_USERNAME:-moveit-pro-user}/.config/moveit_pro/:rw
- ${MOVEIT_HOST_LOG_DIR:-$HOME/.ros/log_moveit_pro}:/home/${MOVEIT_USERNAME:-moveit-pro-user}/.ros/log:ro
- ${MOVEIT_MCP_LIFECYCLE_SOCKET:-/dev/null}:/run/moveit-pro-mcp/lifecycle.sock:rw
- ${MOVEIT_MCP_AUDIT_LOG:-/dev/null}:/run/moveit-pro-mcp/audit.jsonl:rw
- ${MOVEIT_CYCLONEDDS_URI_MOUNT_SOURCE:-${CYCLONEDDS_URI:-/dev/null}}:/home/${MOVEIT_USERNAME:-moveit-pro-user}/.ros/cyclonedds_host_configs:ro
- ${FASTRTPS_DEFAULT_PROFILES_FILE:-/dev/null}:/home/${MOVEIT_USERNAME:-moveit-pro-user}/.ros/fastdds_host.xml:ro
tmpfs:
- /tmp
cap_drop:
- ALL
cap_add:
- CHOWN
- DAC_OVERRIDE
- FOWNER
- SETGID
- SETUID
security_opt:
- no-new-privileges:true

# Starts the robot drivers
drivers:
<<: *license-fingerprint-host-access
image: moveit-pro-drivers:${MOVEIT_DOCKER_TAG:-main}-${MOVEIT_ROS_DISTRO:-jazzy}-${MOVEIT_HOST_USER_WORKSPACE_NAME:-}${MOVEIT_DEV_SUFFIX:-}
extends: base
privileged: true
# Lets the non-root container user run ros2_control with SCHED_FIFO and lock memory.
ulimits:
rtprio: 99
memlock: -1
# The container lives exactly as long as the driver launch, then stays
# exited — fail fast, no restart policy; the deployment's supervisor owns
# recovery. Paired with fail_launch_on_process_exit (in
# ros2_control_config.launch.py), which makes ros2 launch exit non-zero on
# critical node death. (Previously `robot.app && exec sleep infinity`: since
# ros2 launch exits 0 even when a driver node dies, the `&&` ran
# sleep infinity and left a dead driver stack in a container looking "up".)
command: robot.app
# Liveness of the driver launch process. Deliberately not a DDS-based check
# (e.g. `ros2 service type /controller_manager/...`): healthcheck processes
# don't run through the entrypoint, so they lack the generated CycloneDDS
# config and discovery from them is unreliable. Robot-specific readiness
# checks belong in the user overlay — override this healthcheck there.
healthcheck:
test: ["CMD-SHELL", "pgrep -f robot_drivers.launch >/dev/null"]
interval: 10s
timeout: 5s
retries: 3
start_period: 10s


# Model inference server skeleton (`moveit_pro run --with-inference-server` /
# `--only-inference-server`). This defines only the product-owned half:
# lifecycle, GPU runtime, security, and the published port. The user
# workspace docker-compose.yaml completes the service under this same name
# with the actual `build:` or `image:` plus its model and config mounts —
# Compose merges same-named services across files. Profile-gated so a bare
# `docker compose up` never starts it; the launcher activates the profile
# when an inference flag is passed.
inference_server:
profiles: ["inference"]
# Run an init process so SIGTERM reaches the server (a bare PID 1 ignores
# unhandled signals) and Ctrl-C stops it promptly instead of hitting
# docker's 10s kill timeout.
init: true
# GPU access comes from nvidia-compose.yaml, applied automatically on CUDA
# targets like it is for the other services. A ROCm host gets
# amd-compose.yaml for those services but not for this one, so an AMD GPU
# serves on CPU here until that overlay carries a fragment too.
# No restart policy: restart a failed server with `moveit_pro run --only-inference-server`.
# Deliberately no mem_limit / pids_limit: model serving needs large, hardware-dependent memory, and a guessed cap would OOM-kill legitimate loads.
# Defense in depth for a network-listening container: no privilege
# escalation, no Linux capabilities.
security_opt:
- no-new-privileges:true
cap_drop:
- ALL
# Run as the host user so model downloads land in workspace-mounted caches
# with the right ownership, not as root-owned files.
user: "${MOVEIT_USER_UID:-1000}:${MOVEIT_USER_GID:-1000}"
# The 127.0.0.1 publish prefix confines the server's /infer and /health
# endpoints to this host. The in-container port is fixed at 8973;
# MOVEIT_INFERENCE_PORT moves only the host side. Never switch this
# service to network_mode: host.
ports:
- "127.0.0.1:${MOVEIT_INFERENCE_PORT:-8973}:8973"
environment:
# The inference-only credential the launcher derives from the frontend
# key. The server requires it as a bearer token on /infer and /status.
# /health stays token-free for health probes.
- MOVEIT_INFERENCE_KEY=${MOVEIT_INFERENCE_KEY:-}
# Deprecated: kept so a server written against 10.0 or 10.1, which reads
# the frontend key, keeps working. Removed in a future release; require
# MOVEIT_INFERENCE_KEY instead.
- MOVEIT_FRONTEND_KEY=${MOVEIT_FRONTEND_KEY:-}
# Trainer-provisioned access stays in the named volume rather than
# Compose's interpolated environment or `docker inspect` output.
- HF_TOKEN_FILE=/run/secrets/moveit_pro/hugging_face
# Explicit fallback for checkpoints configured outside Trainer. Trainer's
# Configure action uses the private file above.
- HF_TOKEN=${HF_TOKEN:-}
volumes:
- moveit_pro_inference_credentials:/run/secrets/moveit_pro:ro

# Developer specific configuration
dev:
extends: base
environment:
MOVEIT_INFERENCE_KEY: ${MOVEIT_INFERENCE_KEY:-}
build:
target: user-overlay-dev
image: moveit-pro-overlay-dev:${MOVEIT_DOCKER_TAG:-main}-${MOVEIT_ROS_DISTRO:-jazzy}-${MOVEIT_HOST_USER_WORKSPACE_NAME:-}${MOVEIT_DEV_SUFFIX:-}
pull_policy: build
stdin_open: true
tty: true
privileged: true
# Same realtime limits as drivers, for running ros2_control from this container.
ulimits:
rtprio: 99
memlock: -1
command: sleep infinity
# Making a separate profile prevents this service from being built when using `docker compose build`
# and launched when using `docker compose up`.
profiles: ["dev"]

volumes:
moveit_pro_inference_credentials:

The docker-compose.yaml​

We recommend following the below template when extending docker-compose.yaml.

# Docker Compose file that will be merged with /opt/moveit_pro/docker-compose.yaml.
# For more details on how merging works, see https://docs.docker.com/compose/how-tos/multiple-compose-files/merge/#merging-rules.

# Services that are listed under `/opt/moveit_pro/docker-compose.yaml` are mirrored here for merging.
# Feel free to remove services here that are unmodified.
services:
# The base image that all MoveIt Pro services extend off of. Builds the user workspace.
base:
build:
# List any arguments for building the user workspace here.
args:

# Starts the MoveIt Pro Agent and the Bridge between the Agent and the Web UI.
agent_bridge:

# Starts the robot drivers.
drivers:

# Starts the web UI frontend.
web_ui:

# Developer specific configuration when running `moveit_pro dev`.
dev:

With the correct environment, docker compose build will build packages in your workspace that launch MoveIt Pro.

Creating an envfile​

Run moveit_pro configure and moveit_pro envfile to generate a .env file that has the same required parameters for a build environment that Docker will use.

You can alternatively write all the necessary environment variables yourself into the .env file for more advanced users.

Running with Docker commands​

Upon crafting your build arguments and specifications in your docker-compose.yaml, you can use the following to build and run the Docker services:

note

Relative paths are used for your docker-compose.yaml in the following commands, so they must be executed from the same location.

docker compose -f /opt/moveit_pro/docker-compose.yaml -f ./docker-compose.yaml build
docker compose -f /opt/moveit_pro/docker-compose.yaml -f ./docker-compose.yaml run --rm base

Inside the container (~/user_ws), make sure to build your workspace:

colcon build

From the host, launch the agent_bridge and drivers services to start the MoveIt Pro runtime:

docker compose -f /opt/moveit_pro/docker-compose.yaml -f ./docker-compose.yaml up --build agent_bridge drivers

You can alternatively launch the dev service if you want to run the container without launching the MoveIt Pro runtime:

docker compose -f /opt/moveit_pro/docker-compose.yaml -f ./docker-compose.yaml up --build dev

Launching the Web UI​

You can launch the web_ui in a separate container after following the above instructions by calling:

docker compose -f /opt/moveit_pro/docker-compose.yaml -f ./docker-compose.yaml up --build web_ui

From here, go to localhost in your browser of choice, and you can access the MoveIt Pro UI.

To simply these commands, we recommend the following alias:

alias moveit-pro-compose="docker compose -f /opt/moveit_pro/docker-compose.yaml -f ./docker-compose.yaml"

Alternative Setup​

One other option is to copy the system compose configuration found at /opt/moveit_pro/docker-compose.yaml into your workspace and work with a single file. Creating an envfile is still necessary with this setup.

This will simplify the docker commands above as you will no longer need to specify -f /opt/moveit_pro/docker-compose.yaml -f ./docker-compose.yaml with each command, but you must manually replicate any changes to the system docker-compose.yaml in future releases.

Please contact support@picknik.ai if you need assistance with creating and running this docker-compose.yaml.