Back to Woods

Docker Setup Guide

docs/DOCKER_SETUP.md

1.2.010.7 KB
Original Source

Docker Setup Guide

This guide covers running Woods in a Dockerized Rails application — extraction, MCP server configuration, and troubleshooting.

Architecture Overview

Woods has a split architecture: extraction requires a booted Rails environment (runs inside the container), but the two MCP servers have different runtime needs.

HOST                                    CONTAINER
─────────────────────────────           ──────────────────────
Index Server (27 tools)                 Rails App
  reads JSON from disk                    bundle exec rake woods:extract
  no Rails needed                           writes to tmp/woods/
        ▲                                         │
        └──── volume mount ◀──────────────────────┘

Console Server — two modes:

  Embedded (9 tools)                    rake woods:console
  MCP client spawns via                   boots Rails, runs MCP in-process
  docker exec -i ────────────────────▶    Tier 1 read-only tools only

  Bridge (31 tools)
  woods-console-mcp on host          bridge.rb inside container
  connects via docker exec -i ────────▶  evaluates queries in Rails console
  all 4 tiers                             rolled-back transactions

Why the split? The Index Server reads static JSON files — it doesn't need Rails, ActiveRecord, or any of your app's dependencies. Running it on the host avoids container overhead and makes the extraction output available to any MCP client. The Console Server queries live application state, so it must run inside (or connect to) the Rails environment.

Installation

1. Add the gem

ruby
# Gemfile
group :development do
  gem 'woods'
end
bash
docker compose exec app bundle install

2. Run the install generator

bash
docker compose exec app bundle exec rails generate woods:install

This creates config/initializers/woods.rb with default configuration.

3. Run migrations

bash
docker compose exec app bundle exec rails db:migrate

4. Configure

Edit config/initializers/woods.rb inside the container (or on the host if the app directory is volume-mounted):

ruby
Woods.configure do |config|
  config.output_dir = Rails.root.join('tmp/woods')
end

Extraction

Run extraction inside the container:

bash
# Full extraction
docker compose exec app bundle exec rake woods:extract

# Incremental (changed files only)
docker compose exec app bundle exec rake woods:incremental

# Framework/gem sources only
docker compose exec app bundle exec rake woods:extract_framework

Volume Mount Requirement

The extraction output must be accessible on the host for the Index Server to read it. Your docker-compose.yml should volume-mount the Rails app directory (or at least the output directory):

yaml
services:
  app:
    volumes:
      - .:/app                    # Full app mount — output lands at ./tmp/woods/
      # OR mount just the output:
      # - ./tmp/woods:/app/tmp/woods

Verify Output on Host

After extraction, confirm the output is visible from the host:

bash
ls tmp/woods/manifest.json

If this file doesn't exist on the host, your volume mount isn't configured correctly.

Path Translation

When configuring paths, use the host path for the Index Server and the container path for rake tasks:

ContextPathExample
Rake tasks (inside container)Container path/app/tmp/woods
Index Server (on host)Host path./tmp/woods or /home/dev/my-app/tmp/woods
.mcp.json Index Server argHost pathSame as above

Index Server Setup

The Index Server runs on the host — it reads JSON files, not Rails. Point it at the volume-mounted extraction output using the host path.

Start manually

bash
woods-mcp-start ./tmp/woods

.mcp.json configuration

json
{
  "mcpServers": {
    "woods": {
      "command": "woods-mcp-start",
      "args": ["./tmp/woods"]
    }
  }
}

The woods-mcp-start wrapper validates the index directory, checks for manifest.json, ensures dependencies are installed, and restarts on failure. Use it instead of woods-mcp directly.

Common mistake: Using the container path (/app/tmp/woods) in .mcp.json. The Index Server runs on the host — it needs the host-side path to the volume-mounted output.

Console Server Setup

The Console Server queries live Rails state. There are two modes with different trade-offs.

Comparison

EmbeddedBridge
Where it runsInside container via docker exec -iwoods-console-mcp on host, bridge inside container
Config neededNone (just .mcp.json)console.yml + .mcp.json
Tools available9 (Tier 1 — read-only)31 (all 4 tiers)
Setup complexityMinimalModerate
Best forQuick setup, basic queriesFull diagnostics, analytics, guarded operations

Option 1: Embedded (9 Tier 1 tools)

The MCP client spawns docker exec -i directly. The container boots Rails and runs the MCP server in-process. Only Tier 1 read-only tools are available (count, sample, find, pluck, aggregate, association_count, schema, recent, status).

json
{
  "mcpServers": {
    "woods-console": {
      "command": "docker",
      "args": [
        "compose", "exec", "-i", "app",
        "bundle", "exec", "rake", "woods:console"
      ]
    }
  }
}

The -i flag is required. Without it, stdin is not attached and the MCP protocol cannot communicate with the server.

If you use docker exec (not docker compose exec), provide the exact container name:

json
{
  "mcpServers": {
    "woods-console": {
      "command": "docker",
      "args": [
        "exec", "-i", "my_app_web_1",
        "bundle", "exec", "rake", "woods:console"
      ]
    }
  }
}

Option 2: Bridge (all 31 tools)

The woods-console-mcp binary runs on the host and connects to a bridge process inside the container via docker exec -i. This enables all 4 tool tiers: read-only, domain-aware, analytics, and guarded operations.

Step 1: Create console.yml

yaml
# ~/.woods/console.yml
mode: docker
container: my_app_web_1

Find your container name with:

bash
docker ps --format '{{.Names}}'

For Docker Compose, names follow the pattern <project>-<service>-<number> (e.g., my_app-app-1).

Step 2: Configure .mcp.json

json
{
  "mcpServers": {
    "woods-console": {
      "command": "woods-console-mcp"
    }
  }
}

The bridge reads ~/.woods/console.yml by default. To use a different path:

json
{
  "mcpServers": {
    "woods-console": {
      "command": "woods-console-mcp",
      "env": {
        "CODEBASE_CONSOLE_CONFIG": "/path/to/console.yml"
      }
    }
  }
}

Complete .mcp.json Example

Both servers configured together for a Docker environment:

json
{
  "mcpServers": {
    "woods": {
      "command": "woods-mcp-start",
      "args": ["./tmp/woods"]
    },
    "woods-console": {
      "command": "docker",
      "args": [
        "compose", "exec", "-i", "app",
        "bundle", "exec", "rake", "woods:console"
      ]
    }
  }
}

This uses the embedded console (9 tools). To use the bridge (31 tools), replace the codebase-console entry:

json
{
  "mcpServers": {
    "woods": {
      "command": "woods-mcp-start",
      "args": ["./tmp/woods"]
    },
    "woods-console": {
      "command": "woods-console-mcp"
    }
  }
}

Task Reference

Which tasks need Docker and which don't:

TaskNeeds Rails?Run via
woods:extractYesdocker compose exec app bundle exec rake ...
woods:incrementalYesdocker compose exec app bundle exec rake ...
woods:extract_frameworkYesdocker compose exec app bundle exec rake ...
woods:embedYesdocker compose exec app bundle exec rake ...
woods:embed_incrementalYesdocker compose exec app bundle exec rake ...
woods:consoleYesdocker compose exec app bundle exec rake ...
woods:flow[EntryPoint]Yesdocker compose exec app bundle exec rake ...
woods:notion_syncYesdocker compose exec app bundle exec rake ...
woods:validateNoHost or container
woods:statsNoHost or container
woods:cleanNoHost or container

Container Name Discovery

Docker Compose generates container names using the pattern <project>-<service>-<number>:

bash
# List all running containers
docker ps --format '{{.Names}}'

# Filter for your app service
docker ps --format '{{.Names}}' | grep app

The project name defaults to the directory name of the docker-compose.yml file. Override it with COMPOSE_PROJECT_NAME or the name: key in docker-compose.yml.

Troubleshooting

Extraction output not visible on host

Symptom: ls tmp/woods/manifest.json fails on the host after extraction.

Fix: Ensure your docker-compose.yml volume-mounts the app directory:

yaml
volumes:
  - .:/app

Then re-run extraction.

MCP client shows "connection refused" or no tools

Symptom: The Index or Console server doesn't respond.

Check:

  1. Container is running: docker ps
  2. For embedded console, the -i flag is present in the args
  3. For the Index Server, the path in .mcp.json is the host path, not the container path

Missing -i flag on docker exec

Symptom: Console server starts but immediately exits, or MCP client reports "broken pipe."

Fix: Add -i to keep stdin open:

json
"args": ["compose", "exec", "-i", "app", ...]

Wrong container name

Symptom: Error response from daemon: No such container: ...

Fix: Check the actual name with docker ps --format '{{.Names}}' and update your .mcp.json or console.yml.

Path confusion between host and container

Symptom: Index Server reports "No manifest.json" even though extraction succeeded.

Fix: The Index Server runs on the host. Use the host-side path:

# Wrong (container path):
"args": ["/app/tmp/woods"]

# Right (host path):
"args": ["./tmp/woods"]

Rails boot noise breaks MCP protocol

Symptom: MCP client shows JSON parse errors.

Fix: The woods:console rake task redirects stdout to stderr before Rails boots. If you still see issues, check for puts or print calls in your initializers that run before the task captures stdout.

Tier 2-4 tools return "unsupported in embedded mode"

Expected behavior. The embedded console (Option 1) only supports 9 Tier 1 tools. Switch to the bridge (Option 2) for the full 31 tools.

See CONSOLE_MCP_SETUP.md for detailed console server documentation.