FROM {{ base_image }} SHELL ["/bin/bash", "-c"] # Shared environment variables ENV POETRY_VIRTUALENVS_PATH=/openhands/poetry \ MAMBA_ROOT_PREFIX=/openhands/micromamba \ LANG=C.UTF-8 \ LC_ALL=C.UTF-8 \ EDITOR=code \ VISUAL=code \ GIT_EDITOR="code --wait" \ OPENVSCODE_SERVER_ROOT=/openhands/.openvscode-server {% macro setup_base_system() %} # Set PATH early to ensure system commands are available ENV PATH="/usr/bin:/bin:/usr/sbin:/sbin:$PATH" # Install base system dependencies {% if (('ubuntu' in base_image) or ('mswebench' in base_image)) %} RUN apt-get update && \ apt-get install -y --no-install-recommends \ wget curl ca-certificates sudo apt-utils git jq tmux build-essential ripgrep ffmpeg \ coreutils util-linux procps findutils grep sed \ libasound2-plugins libatomic1 && \ (apt-get install -y --no-install-recommends libgl1 || apt-get install -y --no-install-recommends libgl1-mesa-glx) && \ # Install Docker dependencies apt-get install -y --no-install-recommends apt-transport-https ca-certificates curl gnupg lsb-release && \ curl -fsSL https://deb.nodesource.com/setup_22.x | bash - && \ TZ=Etc/UTC DEBIAN_FRONTEND=noninteractive \ {%- if ('mswebench' in base_image) -%} apt-get install -y --no-install-recommends nodejs python3 python-is-python3 python3-pip python3-venv {%- else %} apt-get install -y --no-install-recommends nodejs python3.12 python-is-python3 python3-pip python3.12-venv {% endif -%} {% endif %} {% if (('ubuntu' not in base_image) and ('mswebench' not in base_image)) %} RUN apt-get update && \ apt-get install -y --no-install-recommends \ wget curl ca-certificates sudo apt-utils git jq tmux build-essential ripgrep ffmpeg \ coreutils util-linux procps findutils grep sed \ libasound2-plugins libatomic1 && \ (apt-get install -y --no-install-recommends libgl1 || apt-get install -y --no-install-recommends libgl1-mesa-glx) && \ # Install Docker dependencies apt-get install -y --no-install-recommends apt-transport-https ca-certificates curl gnupg lsb-release {% endif %} {% if (('ubuntu' in base_image) or ('mswebench' in base_image)) %} RUN ln -s "$(dirname $(which node))/corepack" /usr/local/bin/corepack && \ npm install -g corepack && corepack enable yarn && \ curl -fsSL --compressed https://install.python-poetry.org | python - {% endif %} # Install uv (required by MCP) RUN curl -LsSf https://astral.sh/uv/install.sh | env UV_INSTALL_DIR="/openhands/bin" sh # Add /openhands/bin to PATH ENV PATH="/openhands/bin:${PATH}" # Remove UID 1000 and GID 1000 users/groups that might conflict with openhands user RUN (if getent passwd 1000 | grep -q pn; then userdel pn; fi) && \ (if getent passwd 1000 | grep -q ubuntu; then userdel ubuntu; fi) && \ (if getent group 1000 | grep -q pn; then groupdel pn; fi) && \ (if getent group 1000 | grep -q ubuntu; then groupdel ubuntu; fi) # Create openhands group and user (with fallback IDs if 1000 is taken) RUN (if getent group 1000 >/dev/null 2>&1; then \ groupadd openhands; \ else \ groupadd -g 1000 openhands; \ fi) && \ (if getent passwd 1000 >/dev/null 2>&1; then \ useradd -g openhands -m -s /bin/bash openhands; \ else \ useradd -u 1000 -g openhands -m -s /bin/bash openhands; \ fi) && \ usermod -aG sudo openhands && \ echo 'openhands ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers && \ # Set empty password for openhands user to allow passwordless su passwd -d openhands && \ # Set empty password for root user as well to ensure su works in both directions passwd -d root && \ # Ensure root can su to openhands without password by configuring PAM sed -i '/pam_rootok.so/d' /etc/pam.d/su && \ sed -i '1i auth sufficient pam_rootok.so' /etc/pam.d/su # Create necessary directories RUN mkdir -p /openhands && \ mkdir -p /openhands/logs && \ mkdir -p /openhands/poetry && \ chown -R openhands:openhands /openhands # ================================================================ # Define Docker installation macro {% macro install_docker() %} # Install Docker following official documentation # https://docs.docker.com/engine/install/ubuntu/ # https://docs.docker.com/engine/install/debian/ RUN \ # Determine OS type and install accordingly if [[ "{{ base_image }}" == *"ubuntu"* || "{{ base_image }}" == *"betty1202"* ]]; then \ # 'betty1202' for sweperf # Handle Ubuntu (following https://docs.docker.com/engine/install/ubuntu/) # Add Docker's official GPG key apt-get update && \ apt-get install -y ca-certificates curl && \ install -m 0755 -d /etc/apt/keyrings && \ curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc && \ chmod a+r /etc/apt/keyrings/docker.asc && \ # Add the repository to Apt sources # For Ubuntu 24.04 (noble), use jammy repository as noble isn't supported yet echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null; \ else \ # Handle Debian (following https://docs.docker.com/engine/install/debian/) # Add Docker's official GPG key apt-get update && \ apt-get install -y ca-certificates curl && \ install -m 0755 -d /etc/apt/keyrings && \ curl -fsSL https://download.docker.com/linux/debian/gpg -o /etc/apt/keyrings/docker.asc && \ chmod a+r /etc/apt/keyrings/docker.asc && \ # Add the repository to Apt sources (default to bookworm for stability) echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/debian bookworm stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null; \ fi && \ # Install Docker Engine, containerd, and Docker Compose apt-get update && \ apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin && \ apt-get clean && \ rm -rf /var/lib/apt/lists/* # Configure Docker daemon with MTU 1450 to prevent packet fragmentation issues RUN mkdir -p /etc/docker && \ echo '{"mtu": 1450}' > /etc/docker/daemon.json {% endmacro %} # Install Docker only if not a swebench or mswebench image {% if not ('swebench' in base_image) and not ('mswebench' in base_image) %} {{ install_docker() }} {% endif %} # ================================================================ {% endmacro %} {% macro setup_vscode_server() %} # Reference: # 1. https://github.com/gitpod-io/openvscode-server # 2. https://github.com/gitpod-io/openvscode-releases # Setup VSCode Server ARG RELEASE_TAG="openvscode-server-v1.98.2" ARG RELEASE_ORG="gitpod-io" # ARG USERNAME=openvscode-server # ARG USER_UID=1000 # ARG USER_GID=1000 RUN if [ -z "${RELEASE_TAG}" ]; then \ echo "The RELEASE_TAG build arg must be set." >&2 && \ exit 1; \ fi && \ arch=$(uname -m) && \ if [ "${arch}" = "x86_64" ]; then \ arch="x64"; \ elif [ "${arch}" = "aarch64" ]; then \ arch="arm64"; \ elif [ "${arch}" = "armv7l" ]; then \ arch="armhf"; \ fi && \ wget https://github.com/${RELEASE_ORG}/openvscode-server/releases/download/${RELEASE_TAG}/${RELEASE_TAG}-linux-${arch}.tar.gz && \ tar -xzf ${RELEASE_TAG}-linux-${arch}.tar.gz && \ if [ -d "${OPENVSCODE_SERVER_ROOT}" ]; then rm -rf "${OPENVSCODE_SERVER_ROOT}"; fi && \ mv ${RELEASE_TAG}-linux-${arch} ${OPENVSCODE_SERVER_ROOT} && \ cp ${OPENVSCODE_SERVER_ROOT}/bin/remote-cli/openvscode-server ${OPENVSCODE_SERVER_ROOT}/bin/remote-cli/code && \ rm -f ${RELEASE_TAG}-linux-${arch}.tar.gz && \ chown -R openhands:openhands ${OPENVSCODE_SERVER_ROOT} {% endmacro %} {% macro install_vscode_extensions() %} # Install our custom extensions as openhands user USER openhands RUN mkdir -p ${OPENVSCODE_SERVER_ROOT}/extensions/openhands-hello-world && \ cp -r /openhands/code/openhands/runtime/utils/vscode-extensions/hello-world/* ${OPENVSCODE_SERVER_ROOT}/extensions/openhands-hello-world/ RUN mkdir -p ${OPENVSCODE_SERVER_ROOT}/extensions/openhands-memory-monitor && \ cp -r /openhands/code/openhands/runtime/utils/vscode-extensions/memory-monitor/* ${OPENVSCODE_SERVER_ROOT}/extensions/openhands-memory-monitor/ # Some extension dirs are removed because they trigger false positives in vulnerability scans. RUN rm -rf ${OPENVSCODE_SERVER_ROOT}/extensions/{handlebars,pug,json,diff,grunt,ini,npm} {% endmacro %} {% macro install_dependencies_root() %} # Install system-level dependencies that require root USER root RUN \ {% if enable_browser %} # Install system dependencies for Playwright (requires root) apt-get update && \ apt-get install -y --no-install-recommends \ libnss3 libnspr4 libatk-bridge2.0-0 libdrm2 libxkbcommon0 libxcomposite1 \ libxdamage1 libxrandr2 libgbm1 libxss1 && \ # Install libasound2 - try new package name first (Ubuntu 24.04+), fallback to old name (apt-get install -y --no-install-recommends libasound2t64 || apt-get install -y --no-install-recommends libasound2) && \ apt-get clean && rm -rf /var/lib/apt/lists/* && \ # Install Playwright browsers in shared location accessible to all users export PLAYWRIGHT_BROWSERS_PATH=/opt/playwright-browsers && \ mkdir -p /opt/playwright-browsers && \ /openhands/micromamba/bin/micromamba run -n openhands poetry run playwright install --with-deps chromium && \ # Set proper permissions for shared access chmod -R 755 /opt/playwright-browsers && \ # Create cache directories and symlinks for both users mkdir -p /home/openhands/.cache && \ mkdir -p /root/.cache && \ ln -sf /opt/playwright-browsers /home/openhands/.cache/ms-playwright && \ ln -sf /opt/playwright-browsers /root/.cache/ms-playwright && \ chown -h openhands:openhands /home/openhands/.cache/ms-playwright && \ # Set environment variable for all users echo 'export PLAYWRIGHT_BROWSERS_PATH=/opt/playwright-browsers' >> /etc/environment && \ {% endif %} # Set environment variables (requires root) /openhands/micromamba/bin/micromamba run -n openhands poetry run python -c "import sys; print('OH_INTERPRETER_PATH=' + sys.executable)" >> /etc/environment && \ # Set permissions for shared read-only access chmod -R 755 /openhands/poetry && \ chmod -R 755 /openhands/micromamba && \ chown -R openhands:openhands /openhands/poetry && \ mkdir -p /openhands/workspace && chmod -R g+rws,o+rw /openhands/workspace && \ chown -R openhands:openhands /openhands/workspace && \ chown -R openhands:openhands /openhands/micromamba && \ # Ensure PATH includes system binaries early in startup echo 'export PATH="/usr/bin:/bin:/usr/sbin:/sbin:$PATH"' >> /etc/environment && \ echo 'export PATH="/usr/bin:/bin:/usr/sbin:/sbin:$PATH"' >> /etc/bash.bashrc && \ # Set up conda environment activation for all users echo 'eval "$(/openhands/micromamba/bin/micromamba shell hook --shell bash)"' >> /etc/bash.bashrc && \ echo 'micromamba activate openhands 2>/dev/null || true' >> /etc/bash.bashrc && \ # Set up environment for root user echo 'export PATH="/usr/bin:/bin:/usr/sbin:/sbin:/openhands/micromamba/bin:$PATH"' >> /root/.bashrc && \ echo 'export PLAYWRIGHT_BROWSERS_PATH=/opt/playwright-browsers' >> /root/.bashrc && \ echo 'eval "$(/openhands/micromamba/bin/micromamba shell hook --shell bash)"' >> /root/.bashrc && \ echo 'micromamba activate openhands 2>/dev/null || true' >> /root/.bashrc && \ # Clean up system packages (requires root) apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* {% endmacro %} {% macro install_dependencies_user() %} # Install user-level dependencies as openhands user WORKDIR /openhands/code USER openhands RUN \ /openhands/micromamba/bin/micromamba config set changeps1 False && \ /openhands/micromamba/bin/micromamba run -n openhands poetry config virtualenvs.path /openhands/poetry && \ /openhands/micromamba/bin/micromamba run -n openhands poetry env use python3.12 && \ # Install project dependencies /openhands/micromamba/bin/micromamba run -n openhands poetry install --only main,runtime --no-interaction --no-root && \ # Clean up user caches /openhands/micromamba/bin/micromamba run -n openhands poetry cache clear --all . -n && \ /openhands/micromamba/bin/micromamba clean --all {% endmacro %} {% if build_from_scratch %} # ================================================================ # START: Build Runtime Image from Scratch # ================================================================ # This is used in cases where the base image is something more generic like nikolaik/python-nodejs # rather than the current OpenHands release {{ setup_base_system() }} # Install micromamba RUN mkdir -p /openhands/micromamba/bin && \ /bin/bash -c "PREFIX_LOCATION=/openhands/micromamba BIN_FOLDER=/openhands/micromamba/bin INIT_YES=no CONDA_FORGE_YES=yes $(curl -L https://micro.mamba.pm/install.sh)" && \ /openhands/micromamba/bin/micromamba config remove channels defaults && \ /openhands/micromamba/bin/micromamba config list && \ chown -R openhands:openhands /openhands/micromamba && \ # Create read-only shared access to micromamba for all users # This allows both root and openhands users to access the same packages # while maintaining security by keeping openhands as the owner chmod -R 755 /openhands/micromamba && \ # Create a separate writable location for root's micromamba cache/config mkdir -p /root/.local/share/micromamba && \ # Set up environment variables for system-wide access echo 'export PATH="/openhands/micromamba/bin:$PATH"' >> /etc/environment # Create the openhands virtual environment and install poetry and python RUN /openhands/micromamba/bin/micromamba create -n openhands -y && \ /openhands/micromamba/bin/micromamba install -n openhands -c conda-forge poetry python=3.12 -y # Create a clean openhands directory including only the pyproject.toml, poetry.lock and openhands/__init__.py RUN \ if [ -d /openhands/code ]; then rm -rf /openhands/code; fi && \ mkdir -p /openhands/code/openhands && \ touch /openhands/code/openhands/__init__.py && \ chown -R openhands:openhands /openhands/code && \ # Set global git configuration to ensure proper author/committer information git config --global user.name "openhands" && \ git config --global user.email "openhands@all-hands.dev" COPY --chown=openhands:openhands ./code/pyproject.toml ./code/poetry.lock /openhands/code/ {{ install_dependencies_user() }} {{ install_dependencies_root() }} # ================================================================ # END: Build Runtime Image from Scratch # ================================================================ {% endif %} # Ensure openhands user/group and base dirs exist even when not building from scratch USER root RUN \ # Ensure group exists (prefer GID 1000 if available) if ! getent group openhands >/dev/null 2>&1; then \ if getent group 1000 >/dev/null 2>&1; then groupadd openhands; else groupadd -g 1000 openhands; fi; \ fi && \ # Ensure user exists (prefer UID 1000 if available) if ! id -u openhands >/dev/null 2>&1; then \ if getent passwd 1000 >/dev/null 2>&1; then useradd -m -s /bin/bash -g openhands openhands; else useradd -u 1000 -g openhands -m -s /bin/bash openhands; fi; \ fi && \ # Ensure home and required directories exist before later steps mkdir -p /home/openhands && \ mkdir -p /openhands && \ mkdir -p $(dirname ${OPENVSCODE_SERVER_ROOT}) && \ # Ensure ownership is correct for all OpenHands paths chown -R openhands:openhands /home/openhands || true && \ chown -R openhands:openhands /openhands || true {{ setup_vscode_server() }} # ================================================================ # Copy Project source files # ================================================================ RUN if [ -d /openhands/code/openhands ]; then rm -rf /openhands/code/openhands; fi COPY --chown=openhands:openhands ./code/pyproject.toml ./code/poetry.lock /openhands/code/ RUN if [ -d /openhands/code/skills ]; then rm -rf /openhands/code/skills; fi COPY --chown=openhands:openhands ./code/skills /openhands/code/skills COPY --chown=openhands:openhands ./code/openhands /openhands/code/openhands RUN chmod a+rwx /openhands/code/openhands/__init__.py && \ chown -R openhands:openhands /openhands/code # ================================================================ # END: Build from versioned image # ================================================================ {% if build_from_versioned %} {{ install_dependencies_user() }} {{ install_dependencies_root() }} {{ install_vscode_extensions() }} {% endif %} # Install extra dependencies if specified (as openhands user) {% if extra_deps %} USER openhands RUN {{ extra_deps }} {% endif %} # Set up environment for openhands user USER root RUN \ # Set up environment for openhands user echo 'export PATH="/usr/bin:/bin:/usr/sbin:/sbin:/openhands/micromamba/bin:$PATH"' >> /home/openhands/.bashrc && \ echo 'export PLAYWRIGHT_BROWSERS_PATH=/opt/playwright-browsers' >> /home/openhands/.bashrc && \ echo 'eval "$(/openhands/micromamba/bin/micromamba shell hook --shell bash)"' >> /home/openhands/.bashrc && \ echo 'micromamba activate openhands 2>/dev/null || true' >> /home/openhands/.bashrc && \ chown openhands:openhands /home/openhands/.bashrc