# ===== MULTI-STAGE DOCKERFILE FOR OPENHANDS WEBAPP ===== # This Dockerfile builds and runs the complete OpenHands application # including both the React frontend and Python FastAPI backend # ===== FRONTEND BUILD STAGE ===== FROM node:22.13.1-bookworm-slim AS frontend-builder WORKDIR /app/frontend # Copy package files first for better layer caching COPY frontend/package.json frontend/package-lock.json ./ # Install frontend dependencies RUN npm ci --only=production # Copy frontend source code COPY frontend/ ./ # Build the React application RUN npm run build # ===== BACKEND BUILD STAGE ===== FROM python:3.12.8-slim AS backend-builder WORKDIR /app # Set Poetry environment variables ENV POETRY_NO_INTERACTION=1 \ POETRY_VIRTUALENVS_IN_PROJECT=1 \ POETRY_VIRTUALENVS_CREATE=1 \ POETRY_CACHE_DIR=/tmp/poetry_cache # Install system dependencies for building Python packages RUN apt-get update -y \ && apt-get install -y --no-install-recommends \ curl \ make \ git \ build-essential \ && rm -rf /var/lib/apt/lists/* # Install Poetry RUN python3 -m pip install poetry==1.8.2 --break-system-packages # Copy Python dependency files first for better layer caching COPY pyproject.toml poetry.lock ./ RUN touch README.md # Install Python dependencies RUN poetry install --no-root --no-dev && rm -rf $POETRY_CACHE_DIR # ===== FINAL RUNTIME STAGE ===== FROM python:3.12.8-slim AS runtime WORKDIR /app # Build arguments ARG OPENHANDS_BUILD_VERSION=dev # Environment variables for OpenHands ENV RUN_AS_OPENHANDS=true \ OPENHANDS_USER_ID=42420 \ SANDBOX_LOCAL_RUNTIME_URL=http://host.docker.internal \ USE_HOST_NETWORK=false \ WORKSPACE_BASE=/opt/workspace_base \ OPENHANDS_BUILD_VERSION=$OPENHANDS_BUILD_VERSION \ SANDBOX_USER_ID=0 \ FILE_STORE=local \ FILE_STORE_PATH=/.openhands-state \ PYTHONPATH='/app' \ VIRTUAL_ENV=/app/.venv \ PATH="/app/.venv/bin:$PATH" # Create necessary directories RUN mkdir -p $FILE_STORE_PATH $WORKSPACE_BASE # Install runtime system dependencies RUN apt-get update -y \ && apt-get install -y --no-install-recommends \ curl \ ssh \ sudo \ git \ && rm -rf /var/lib/apt/lists/* # Configure user management for different UID ranges RUN sed -i 's/^UID_MIN.*/UID_MIN 499/' /etc/login.defs \ && sed -i 's/^UID_MAX.*/UID_MAX 1000000/' /etc/login.defs # Create app group and openhands user RUN groupadd app \ && useradd -l -m -u $OPENHANDS_USER_ID -s /bin/bash openhands \ && usermod -aG app openhands \ && usermod -aG sudo openhands \ && echo '%sudo ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers # Set ownership and permissions RUN chown -R openhands:app /app && chmod -R 770 /app \ && chown -R openhands:app $WORKSPACE_BASE && chmod -R 770 $WORKSPACE_BASE # Switch to openhands user for application setup USER openhands # Copy Python virtual environment from builder stage COPY --chown=openhands:app --chmod=770 --from=backend-builder /app/.venv /app/.venv # Copy Python application code COPY --chown=openhands:app --chmod=770 ./microagents ./microagents COPY --chown=openhands:app --chmod=770 ./openhands ./openhands COPY --chown=openhands:app --chmod=777 ./openhands/runtime/plugins ./openhands/runtime/plugins COPY --chown=openhands:app --chmod=770 ./openhands/agenthub ./openhands/agenthub # Copy configuration and metadata files COPY --chown=openhands:app ./pyproject.toml ./poetry.lock ./README.md ./MANIFEST.in ./LICENSE ./ # Download assets (run as openhands user to set correct ownership) RUN python openhands/core/download.py # Copy built frontend from frontend-builder stage COPY --chown=openhands:app --chmod=770 --from=frontend-builder /app/frontend/build ./frontend/build # Copy entrypoint script COPY --chown=openhands:app --chmod=770 ./containers/app/entrypoint.sh /app/entrypoint.sh # Ensure all files are in the app group RUN find /app \! -group app -exec chgrp app {} + 2>/dev/null || true # Switch back to root for entrypoint execution USER root # Health check to ensure the application is running HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \ CMD curl -f http://localhost:3000/ || exit 1 # Expose the application port EXPOSE 3000 # Set entrypoint and default command ENTRYPOINT ["/app/entrypoint.sh"] CMD ["uvicorn", "openhands.server.listen:app", "--host", "0.0.0.0", "--port", "3000"]