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:
MOVEIT_HOST_SYSFS: /host/sys
volumes:
- type: bind
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}
# 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 Behaviors that call Google Gemini (e.g. GetPoints2DFromGeminiQuery).
# Leave unset on the host if Gemini integration is not used.
- GOOGLE_GEMINI_API_KEY
# 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:-}
# 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
# 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
- 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/: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
# Ensures the drivers container has RT priority
ulimits:
rtprio: 99
# 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.
# Mid-run crash recovery only. The launcher owns starting and stopping
# this service, either with the stack or on its own
# (--only-inference-server).
restart: on-failure:3
# Deliberately no mem_limit / pids_limit: model serving needs large,
# hardware-dependent memory, and a guessed cap would OOM-kill legitimate
# loads. The bounded restart count above is what contains a crash-looping
# (e.g. OOM-killed) server instead.
# 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:
# Shared secret required on /infer requests, the same key the web
# backend endpoints use. /health stays token-free for health probes.
- MOVEIT_FRONTEND_KEY=${MOVEIT_FRONTEND_KEY:-}

# Developer specific configuration
dev:
extends: base
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
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"]

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.