diff --git a/Development.md b/Development.md index 2b79f29570a0..7dd4fca3bb91 100644 --- a/Development.md +++ b/Development.md @@ -97,3 +97,28 @@ Please refer to [this README](./tests/integration/README.md) for details. ### 9. Add or update dependency 1. Add your dependency in `pyproject.toml` or use `poetry add xxx` 2. Update the poetry.lock file via `poetry lock --no-update` + +## Develop inside Docker container + +TL;DR + +```bash +make docker-dev +``` + +See more details [here](./containers/dev/README.md) + +If you are just interested in running `OpenHands` without installing all the required tools on your host. + +```bash +make docker-run +``` + +If you do not have `make` on your host, run: + +```bash +cd ./containers/dev +./dev.sh +``` + +You do need [Docker](https://docs.docker.com/engine/install/) installed on your host though. diff --git a/Makefile b/Makefile index c18f0d90b519..3798365f5dab 100644 --- a/Makefile +++ b/Makefile @@ -2,8 +2,9 @@ SHELL=/bin/bash # Makefile for OpenHands project # Variables +BACKEND_HOST ?= "127.0.0.1" BACKEND_PORT = 3000 -BACKEND_HOST = "127.0.0.1:$(BACKEND_PORT)" +BACKEND_HOST_PORT = "$(BACKEND_HOST):$(BACKEND_PORT)" FRONTEND_PORT = 3001 DEFAULT_WORKSPACE_DIR = "./workspace" DEFAULT_MODEL = "gpt-4o" @@ -189,12 +190,12 @@ build-frontend: # Start backend start-backend: @echo "$(YELLOW)Starting backend...$(RESET)" - @poetry run uvicorn openhands.server.listen:app --port $(BACKEND_PORT) --reload --reload-exclude "workspace/*" + @poetry run uvicorn openhands.server.listen:app --host $(BACKEND_HOST) --port $(BACKEND_PORT) --reload --reload-exclude "workspace/*" # Start frontend start-frontend: @echo "$(YELLOW)Starting frontend...$(RESET)" - @cd frontend && VITE_BACKEND_HOST=$(BACKEND_HOST) VITE_FRONTEND_PORT=$(FRONTEND_PORT) npm run start + @cd frontend && VITE_BACKEND_HOST=$(BACKEND_HOST_PORT) VITE_FRONTEND_PORT=$(FRONTEND_PORT) npm run start # Common setup for running the app (non-callable) _run_setup: @@ -204,7 +205,7 @@ _run_setup: fi @mkdir -p logs @echo "$(YELLOW)Starting backend server...$(RESET)" - @poetry run uvicorn openhands.server.listen:app --port $(BACKEND_PORT) & + @poetry run uvicorn openhands.server.listen:app --host $(BACKEND_HOST) --port $(BACKEND_PORT) & @echo "$(YELLOW)Waiting for the backend to start...$(RESET)" @until nc -z localhost $(BACKEND_PORT); do sleep 0.1; done @echo "$(GREEN)Backend started successfully.$(RESET)" @@ -216,6 +217,20 @@ run: @cd frontend && echo "$(BLUE)Starting frontend with npm...$(RESET)" && npm run start -- --port $(FRONTEND_PORT) @echo "$(GREEN)Application started successfully.$(RESET)" +# Run the app (in docker) +docker-run: WORKSPACE_BASE ?= $(PWD)/workspace +docker-run: + @if [ -f /.dockerenv ]; then \ + echo "Running inside a Docker container. Exiting..."; \ + exit 0; \ + else \ + echo "$(YELLOW)Running the app in Docker $(OPTIONS)...$(RESET)"; \ + export WORKSPACE_BASE=${WORKSPACE_BASE}; \ + export SANDBOX_USER_ID=$(shell id -u); \ + export DATE=$(shell date +%Y%m%d%H%M%S); \ + docker compose up $(OPTIONS); \ + fi + # Run the app (WSL mode) run-wsl: @echo "$(YELLOW)Running the app in WSL mode...$(RESET)" @@ -280,6 +295,16 @@ setup-config-prompts: fi +# Develop in container +docker-dev: + @if [ -f /.dockerenv ]; then \ + echo "Running inside a Docker container. Exiting..."; \ + exit 0; \ + else \ + echo "$(YELLOW)Build and run in Docker $(OPTIONS)...$(RESET)"; \ + ./containers/dev/dev.sh $(OPTIONS); \ + fi + # Clean up all caches clean: @echo "$(YELLOW)Cleaning up caches...$(RESET)" @@ -298,7 +323,10 @@ help: @echo " $(GREEN)start-frontend$(RESET) - Start the frontend server for the OpenHands project." @echo " $(GREEN)run$(RESET) - Run the OpenHands application, starting both backend and frontend servers." @echo " Backend Log file will be stored in the 'logs' directory." + @echo " $(GREEN)docker-dev$(RESET) - Build and run the OpenHands application in Docker." + @echo " $(GREEN)docker-run$(RESET) - Run the OpenHands application, starting both backend and frontend servers in Docker." @echo " $(GREEN)help$(RESET) - Display this help message, providing information on available targets." # Phony targets .PHONY: build check-dependencies check-python check-npm check-docker check-poetry install-python-dependencies install-frontend-dependencies install-pre-commit-hooks lint start-backend start-frontend run run-wsl setup-config setup-config-prompts help +.PHONY: docker-dev docker-run diff --git a/compose.yml b/compose.yml new file mode 100644 index 000000000000..c66265e96950 --- /dev/null +++ b/compose.yml @@ -0,0 +1,22 @@ +# +services: + openhands: + build: + context: ./ + dockerfile: ./containers/app/Dockerfile + image: openhands:latest + container_name: openhands-app-${DATE:-} + environment: + - SANDBOX_RUNTIME_CONTAINER_IMAGE=${SANDBOX_RUNTIME_CONTAINER_IMAGE:-ghcr.io/all-hands-ai/runtime:0.9-nikolaik} + - SANDBOX_USER_ID=${SANDBOX_USER_ID:-1234} + - WORKSPACE_MOUNT_PATH=${WORKSPACE_BASE:-$PWD/workspace} + ports: + - "3000:3000" + extra_hosts: + - "host.docker.internal:host-gateway" + volumes: + - /var/run/docker.sock:/var/run/docker.sock + - ${WORKSPACE_BASE:-$PWD/workspace}:/opt/workspace_base + pull_policy: build + stdin_open: true + tty: true diff --git a/containers/dev/Dockerfile b/containers/dev/Dockerfile new file mode 100644 index 000000000000..72946ad2455b --- /dev/null +++ b/containers/dev/Dockerfile @@ -0,0 +1,124 @@ +# syntax=docker/dockerfile:1 + +### +FROM ubuntu:22.04 AS dind + +# https://docs.docker.com/engine/install/ubuntu/ +RUN 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 \ + && echo \ + "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu \ + $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null + +RUN apt-get update && apt-get install -y \ + docker-ce \ + docker-ce-cli \ + containerd.io \ + docker-buildx-plugin \ + docker-compose-plugin \ + && rm -rf /var/lib/apt/lists/* \ + && apt-get clean \ + && apt-get autoremove -y + +### +FROM dind AS openhands + +ENV DEBIAN_FRONTEND=noninteractive + +# +RUN apt-get update && apt-get install -y \ + bash \ + build-essential \ + curl \ + git \ + git-lfs \ + software-properties-common \ + make \ + netcat \ + sudo \ + wget \ + && rm -rf /var/lib/apt/lists/* \ + && apt-get clean \ + && apt-get autoremove -y + +# https://github.com/cli/cli/blob/trunk/docs/install_linux.md +RUN curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg | dd of=/usr/share/keyrings/githubcli-archive-keyring.gpg \ + && chmod go+r /usr/share/keyrings/githubcli-archive-keyring.gpg \ + && echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" | tee /etc/apt/sources.list.d/github-cli.list > /dev/null \ + && apt-get update && apt-get -y install \ + gh \ + && rm -rf /var/lib/apt/lists/* \ + && apt-get clean \ + && apt-get autoremove -y + +# Python 3.11 +RUN add-apt-repository ppa:deadsnakes/ppa \ + && apt-get update \ + && apt-get install -y python3.11 python3.11-venv python3.11-dev python3-pip \ + && ln -s /usr/bin/python3.11 /usr/bin/python + +# NodeJS >= 18.17.1 +RUN curl -fsSL https://deb.nodesource.com/setup_18.x | bash - \ + && apt-get install -y nodejs + +# Poetry >= 1.8 +RUN curl -fsSL https://install.python-poetry.org | python3.11 - \ + && ln -s ~/.local/bin/poetry /usr/local/bin/poetry + +# +RUN <&1 | head -n 1 +" > /version.sh +chmod a+x /version.sh +EOF + +### +FROM openhands AS dev + +RUN apt-get update && apt-get install -y \ + dnsutils \ + file \ + iproute2 \ + jq \ + lsof \ + ripgrep \ + silversearcher-ag \ + vim \ + && rm -rf /var/lib/apt/lists/* \ + && apt-get clean \ + && apt-get autoremove -y + +WORKDIR /app + +# cache build dependencies +RUN \ + --mount=type=bind,source=./,target=/app/ \ + </dev/null || get_docker +} + +function exit_if_indocker() { + if [ -f /.dockerenv ]; then + echo "Running inside a Docker container. Exiting..." + exit 1 + fi +} + +# +exit_if_indocker + +check_tools + +## +OPENHANDS_WORKSPACE=$(git rev-parse --show-toplevel) + +cd "$OPENHANDS_WORKSPACE/containers/dev/" || exit 1 + +## +export BACKEND_HOST="0.0.0.0" +# +export SANDBOX_USER_ID=$(id -u) +export WORKSPACE_BASE=${WORKSPACE_BASE:-$OPENHANDS_WORKSPACE/workspace} + +docker compose run --rm --service-ports "$@" dev + +##