diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml new file mode 100644 index 0000000..7e15e4c --- /dev/null +++ b/.github/workflows/docker.yml @@ -0,0 +1,68 @@ +name: Build and Push Docker Images + +on: + push: + branches: [ main ] + tags: [ 'v*' ] + pull_request: + branches: [ main ] + +env: + REGISTRY: ghcr.io + IMAGE_NAME: ${{ github.repository }} + +jobs: + build-and-push: + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Log in to Container Registry + if: github.event_name != 'pull_request' + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract metadata + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + tags: | + type=ref,event=branch + type=ref,event=pr + type=semver,pattern={{version}} + type=semver,pattern={{major}}.{{minor}} + type=raw,value=latest,enable={{is_default_branch}} + + - name: Build and push production image + uses: docker/build-push-action@v5 + with: + context: . + target: production + push: ${{ github.event_name != 'pull_request' }} + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + cache-from: type=gha + cache-to: type=gha,mode=max + + - name: Build and push development image + uses: docker/build-push-action@v5 + with: + context: . + target: development + push: ${{ github.event_name != 'pull_request' }} + tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:dev + labels: ${{ steps.meta.outputs.labels }} + cache-from: type=gha + cache-to: type=gha,mode=max \ No newline at end of file diff --git a/DOCKER.md b/DOCKER.md new file mode 100644 index 0000000..45e0f5c --- /dev/null +++ b/DOCKER.md @@ -0,0 +1,161 @@ +# Docker Setup for Serena (Experimental) + +⚠️ **EXPERIMENTAL FEATURE**: The Docker setup for Serena is currently experimental and has several limitations. Please read this entire document before using Docker with Serena. + +## Overview + +Docker support allows you to run Serena in an isolated container environment, which provides better security isolation for the shell tool and consistent dependencies across different systems. + +## Benefits + +- **Safer shell tool execution**: Commands run in an isolated container environment +- **Consistent dependencies**: No need to manage language servers and dependencies on your host system +- **Cross-platform support**: Works consistently across Windows, macOS, and Linux + +## Important Limitations and Caveats + +### 1. Configuration File Conflicts + +⚠️ **Critical**: Docker uses a separate configuration file (`serena_config.docker.yml`) to avoid path conflicts. When running in Docker: +- Container paths will be stored in the configuration (e.g., `/workspaces/serena/...`) +- These paths are incompatible with non-Docker usage +- After using Docker, you cannot directly switch back to non-Docker usage without manual configuration adjustment + +### 2. Project Activation Limitations + +- **Only mounted directories work**: Projects must be mounted as volumes to be accessible +- Projects outside the mounted directories cannot be activated or accessed +- Default setup only mounts the current directory + +### 3. GUI Window Disabled + +- The GUI log window option is automatically disabled in Docker environments +- Use the web dashboard instead (see below) + +### 4. Dashboard Port Configuration + +The web dashboard runs on port 24282 (0x5EDA) by default. You can configure this using environment variables: + +```bash +# Use default ports +docker-compose up serena + +# Use custom ports +SERENA_DASHBOARD_PORT=8080 docker-compose up serena +``` + +⚠️ **Note**: If the local port is occupied, you'll need to specify a different port using the environment variable. + +### 5. Line Ending Issues on Windows + +⚠️ **Windows Users**: Be aware of potential line ending inconsistencies: +- Files edited within the Docker container may use Unix line endings (LF) +- Your Windows system may expect Windows line endings (CRLF) +- This can cause issues with version control and text editors +- Configure your Git settings appropriately: `git config core.autocrlf true` + +## Quick Start + +### Using Docker Compose (Recommended) + +1. **Production mode** (for using Serena as MCP server): + ```bash + docker-compose up serena + ``` + +2. **Development mode** (with source code mounted): + ```bash + docker-compose up serena-dev + ``` + +### Using Docker directly + +```bash +# Build the image +docker build -t serena . + +# Run with current directory mounted +docker run -it --rm \ + -v "$(pwd)":/workspace \ + -p 9121:9121 \ + -p 24282:24282 \ + -e SERENA_DOCKER=1 \ + serena +``` + +## Accessing the Dashboard + +Once running, access the web dashboard at: +- Default: http://localhost:24282/dashboard +- Custom port: http://localhost:${SERENA_DASHBOARD_PORT}/dashboard + +## Volume Mounting + +To work with projects, you must mount them as volumes: + +```yaml +# In compose.yaml +volumes: + - ./my-project:/workspace/my-project + - /path/to/another/project:/workspace/another-project +``` + +## Environment Variables + +- `SERENA_DOCKER=1`: Set automatically to indicate Docker environment +- `SERENA_PORT`: MCP server port (default: 9121) +- `SERENA_DASHBOARD_PORT`: Web dashboard port (default: 24282) + +## Troubleshooting + +### Port Already in Use + +If you see "port already in use" errors: +```bash +# Check what's using the port +lsof -i :24282 # macOS/Linux +netstat -ano | findstr :24282 # Windows + +# Use a different port +SERENA_DASHBOARD_PORT=8080 docker-compose up serena +``` + +### Configuration Issues + +If you need to reset Docker configuration: +```bash +# Remove Docker-specific config +rm serena_config.docker.yml + +# Serena will auto-generate a new one on next run +``` + +### Project Access Issues + +Ensure projects are properly mounted: +- Check volume mounts in `docker-compose.yaml` +- Use absolute paths for external projects +- Verify permissions on mounted directories + +## Migration Path + +To switch between Docker and non-Docker usage: + +1. **Docker to Non-Docker**: + - Manually edit project paths in `serena_config.yml` + - Change container paths to host paths + - Or use separate config files for each environment + +2. **Non-Docker to Docker**: + - Projects will be re-registered with container paths + - Original config remains unchanged + +## Future Improvements + +We're working on: +- Automatic config migration between environments +- Better project path handling +- Dynamic port allocation +- Windows line-ending handling + +For updates and issues, please check the [GitHub repository](https://github.com/diazoxide/serena). diff --git a/Dockerfile b/Dockerfile index 4521867..d88d4e2 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ -# Use the official Python image for the base image. -FROM python:3.11-slim +# Base stage with common dependencies +FROM python:3.11-slim AS base SHELL ["/bin/bash", "-c"] # Set environment variables to make Python print directly to the terminal and avoid .pyc files. @@ -21,18 +21,18 @@ RUN python3 -m pip install --no-cache-dir pipx \ # Add local bin to the path ENV PATH="${PATH}:/root/.local/bin" - # Install the latest version of uv RUN curl -LsSf https://astral.sh/uv/install.sh | sh # Set the working directory WORKDIR /workspaces/serena -# Copy required files into the image -COPY pyproject.toml /workspaces/serena/ -COPY README.md /workspaces/serena/ +# Development target +FROM base AS development +# Copy all files for development +COPY . /workspaces/serena/ -# Create virtual environment and install dependencies +# Create virtual environment and install dependencies with dev extras RUN uv venv RUN . .venv/bin/activate RUN uv pip install --all-extras -r pyproject.toml -e . @@ -41,3 +41,19 @@ ENV PATH="/workspaces/serena/.venv/bin:${PATH}" # Entrypoint to ensure environment is activated ENTRYPOINT ["/bin/bash", "-c", "source .venv/bin/activate && $0 $@"] +# Production target +FROM base AS production +# Copy only necessary files for production +COPY pyproject.toml /workspaces/serena/ +COPY README.md /workspaces/serena/ +COPY src/ /workspaces/serena/src/ + +# Create virtual environment and install dependencies (production only) +RUN uv venv +RUN . .venv/bin/activate +RUN uv pip install -r pyproject.toml -e . +ENV PATH="/workspaces/serena/.venv/bin:${PATH}" + +# Entrypoint to ensure environment is activated +ENTRYPOINT ["/bin/bash", "-c", "source .venv/bin/activate && $0 $@"] + diff --git a/README.md b/README.md index 6d48de7..9b832ad 100644 --- a/README.md +++ b/README.md @@ -243,6 +243,8 @@ Configure the MCP server in your client. For [Claude Desktop](https://claude.ai/download) (available for Windows and macOS), go to File / Settings / Developer / MCP Servers / Edit Config, which will let you open the JSON file `claude_desktop_config.json`. Add the following (with adjusted paths) to enable Serena: +#### Local Installation + ```json { "mcpServers": { @@ -254,6 +256,30 @@ which will let you open the JSON file `claude_desktop_config.json`. Add the foll } ``` +#### Docker Installation (Experimental) + +⚠️ **EXPERIMENTAL**: Docker support is currently experimental with several limitations. Please read the [Docker documentation](DOCKER.md) for important caveats before using. + +Alternatively, you can run Serena using Docker: + +```json +{ + "mcpServers": { + "serena": { + "command": "docker", + "args": ["run", "--rm", "-i", "--network", "host", "-v", "/path/to/your/projects:/workspaces/projects", "ghcr.io/oraios/serena:latest", "serena-mcp-server", "--transport", "stdio"] + } + } +} +``` + +Replace `/path/to/your/projects` with the absolute path to your projects directory. The Docker approach provides: +- Better security isolation for shell command execution +- No need to install language servers and dependencies locally +- Consistent environment across different systems + +See the [Docker documentation](DOCKER.md) for detailed setup instructions, configuration options, and known limitations. + If you are using paths containing backslashes for paths on Windows (note that you can also just use forward slashes), be sure to escape them correctly (`\\`). @@ -265,16 +291,18 @@ That's it! Save the config and then restart Claude Desktop. You are ready for ac uv run serena-mcp-server --help ``` -ℹ️ You can use Serena without cloning or configuring it explicitly by +ℹ️ You can use Serena without cloning or configuring it explicitly by using the Docker image above or: +```json { "mcpServers": { "serena": { - "command": "/abs/path/to/uv", - "args": ["run", "--directory", "/abs/path/to/serena", "serena-mcp-server"] + "command": "uvx", + "args": ["--from", "git+https://github.com/oraios/serena", "serena-mcp-server"] } } } +``` #### Troubleshooting @@ -399,7 +427,7 @@ Here's how it works (see also [Agno's documentation](https://docs.agno.com/intro 3. Copy `.env.example` to `.env` and fill in the API keys for the provider(s) you intend to use. -5. Start the agno agent app with +4. Start the agno agent app with ```shell uv run python scripts/agno_agent.py ``` diff --git a/compose.yaml b/compose.yaml new file mode 100644 index 0000000..bb8f683 --- /dev/null +++ b/compose.yaml @@ -0,0 +1,32 @@ +services: + serena: + image: serena:latest + build: + context: ./ + dockerfile: Dockerfile + target: production + ports: + - "${SERENA_PORT:-9121}:9121" # MCP server port + - "${SERENA_DASHBOARD_PORT:-24282}:24282" # Dashboard port (default 0x5EDA = 24282) + environment: + - SERENA_DOCKER=1 + command: + - "uv run --directory . serena-mcp-server --transport sse --port 9121 --host 0.0.0.0" + + serena-dev: + image: serena:dev + build: + context: ./ + dockerfile: Dockerfile + target: development + tty: true + stdin_open: true + environment: + - SERENA_DOCKER=1 + volumes: + - .:/workspaces/serena + ports: + - "${SERENA_PORT:-9121}:9121" # MCP server port + - "${SERENA_DASHBOARD_PORT:-24282}:24282" # Dashboard port + command: + - "uv run --directory . serena-mcp-server" diff --git a/src/serena/agent.py b/src/serena/agent.py index 1d8acd4..9416a35 100644 --- a/src/serena/agent.py +++ b/src/serena/agent.py @@ -103,6 +103,19 @@ def get_serena_managed_dir(project_root: str | Path) -> str: return os.path.join(project_root, SERENA_MANAGED_DIR_NAME) +def is_running_in_docker() -> bool: + """Check if we're running inside a Docker container.""" + # Check for Docker-specific files + if os.path.exists('/.dockerenv'): + return True + # Check cgroup for docker references + try: + with open('/proc/self/cgroup', 'r') as f: + return 'docker' in f.read() + except FileNotFoundError: + return False + + @dataclass class ProjectConfig(ToStringMixin): project_name: str @@ -316,6 +329,7 @@ class SerenaConfig(SerenaConfigBase): loaded_commented_yaml: CommentedMap CONFIG_FILE = "serena_config.yml" + CONFIG_FILE_DOCKER = "serena_config.docker.yml" # Docker-specific config file; auto-generated if missing, mounted via docker-compose for user customization @classmethod def autogenerate(cls) -> None: @@ -329,7 +343,8 @@ class SerenaConfig(SerenaConfigBase): @classmethod def get_config_file_path(cls) -> str: - return os.path.join(REPO_ROOT, cls.CONFIG_FILE) + config_file = cls.CONFIG_FILE_DOCKER if is_running_in_docker() else cls.CONFIG_FILE + return os.path.join(REPO_ROOT, config_file) @classmethod def from_config_file(cls, generate_if_missing: bool = True) -> "SerenaConfig": @@ -371,7 +386,11 @@ class SerenaConfig(SerenaConfigBase): project = Project.load(path) instance.projects.append(project) - instance.gui_log_window_enabled = loaded_commented_yaml.get("gui_log_window", False) + # Force disable GUI in Docker environment + if is_running_in_docker(): + instance.gui_log_window_enabled = False + else: + instance.gui_log_window_enabled = loaded_commented_yaml.get("gui_log_window", False) instance.log_level = loaded_commented_yaml.get("log_level", loaded_commented_yaml.get("gui_log_level", logging.INFO)) instance.web_dashboard = loaded_commented_yaml.get("web_dashboard", True) instance.tool_timeout = loaded_commented_yaml.get("tool_timeout", DEFAULT_TOOL_TIMEOUT) diff --git a/src/serena/dashboard.py b/src/serena/dashboard.py index 4b0419a..622bef1 100644 --- a/src/serena/dashboard.py +++ b/src/serena/dashboard.py @@ -109,7 +109,7 @@ class SerenaDashboardAPI: raise RuntimeError(f"No free ports found starting from {start_port}") - def run(self, host: str = "127.0.0.1", port: int = 0x5EDA) -> int: + def run(self, host: str = "0.0.0.0", port: int = 0x5EDA) -> int: """ Runs the dashboard on the given host and port and returns the port number. """