Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.ouim.me/llms.txt

Use this file to discover all available pages before exploring further.

Docker is the recommended way to run Reacher in production. It handles dependencies, provides automatic restarts, and isolates the process cleanly.

Prerequisites

  • Docker installed on your host machine
  • .env file configured with your credentials (copy from .env.example)
  • reacher.config.yaml configured (copy from reacher.config.example.yaml)
Generate a strong MCP_SECRET before you start: openssl rand -hex 32
Docker Compose is the simplest path for most deployments. It builds the image, maps ports, loads your .env, and restarts automatically on crash or reboot.
1

Clone the repository

git clone --branch v0.1.0 https://github.com/thezem/reacher.git
cd reacher
2

Configure environment and config files

cp .env.example .env
cp reacher.config.example.yaml reacher.config.yaml
Edit both files with your credentials. At minimum, set:
MCP_SECRET=<random-string>
TAILSCALE_API_KEY=<your-tailscale-api-key>
GITHUB_TOKEN=<your-github-token>
PROXY_ALLOWED_DOMAINS=api.github.com
3

Start the service

docker compose up -d
Docker Compose will build the image and start the container in the background.
4

Verify it is running

docker logs mcp-server
curl "http://localhost:3000/health?token=YOUR_MCP_SECRET"
You should see {"status":"ok",...} from the health check endpoint.

docker-compose.yml

This is the full Compose file included in the repository:
docker-compose.yml
version: '3.8'

services:
  mcp-server:
    build:
      context: .
      dockerfile: Dockerfile
    container_name: mcp-server
    ports:
      - "${PORT:-3000}:${PORT:-3000}"
    environment:
      PORT: ${PORT:-3000}
      TAILSCALE_API_KEY: ${TAILSCALE_API_KEY}
      # TELEGRAM_BOT_TOKEN and DEFAULT_CHAT_ID are unused leftover vars in the compose file
      TELEGRAM_BOT_TOKEN: ${TELEGRAM_BOT_TOKEN}
      DEFAULT_CHAT_ID: ${DEFAULT_CHAT_ID}
    env_file:
      - .env
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "node", "-e", "require('http').get('http://localhost:' + (process.env.PORT || 3000), (r) => {if (r.statusCode !== 200) throw new Error(r.statusCode)})"]
      interval: 30s
      timeout: 3s
      retries: 3
      start_period: 5s
    volumes:
      - ./src:/app/src
      - ./index.js:/app/index.js
    # Uncomment for local development with auto-reload
    # command: npm run dev

Manual docker run

If you prefer to manage the container directly without Compose:
1

Build the image

docker build -t reacher .
2

Run the container

docker run -d \
  -p 3000:3000 \
  --env-file .env \
  --restart unless-stopped \
  --name reacher \
  reacher
The flags do the following:
  • -d — run in the background (detached)
  • -p 3000:3000 — map host port 3000 to container port 3000
  • --env-file .env — inject all variables from your .env file
  • --restart unless-stopped — restart automatically on crash or host reboot
  • --name reacher — give the container a stable name for log and management commands

Checking logs

docker logs reacher
docker logs reacher --follow   # stream live output
For Docker Compose deployments, use the service name:
docker compose logs -f mcp-server

Health check

The /health endpoint returns the current server status. It requires the same token as the /mcp endpoint:
curl "http://localhost:3000/health?token=YOUR_MCP_SECRET"
{
  "status": "ok",
  "timestamp": "2026-03-18T12:00:00.000Z",
  "dry_run": false
}
Docker runs its own built-in health check every 30 seconds against this endpoint. You can inspect it with:
docker inspect --format='{{.State.Health.Status}}' reacher

Updating to a new version

1

Pull the latest code

git pull
2

Rebuild and restart

docker compose up -d --build

Dockerfile reference

The included Dockerfile produces a minimal production image:
Dockerfile
FROM node:22-alpine

# Install openssh-client for ssh_exec tool
RUN apk add --no-cache openssh-client

# Set working directory
WORKDIR /app

# Copy package files
COPY package*.json ./

# Install dependencies with production flag
RUN npm install --omit=dev

# Copy application code
COPY . .

# Expose port (default 3000, can be overridden)
EXPOSE ${PORT:-3000}

# Health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
  CMD node -e "require('http').get('http://localhost:' + (process.env.PORT || 3000), (r) => {if (r.statusCode !== 200) throw new Error(r.statusCode)})"

# Start the server
CMD ["node", "index.js"]
Key details:
  • Base image: node:22-alpine — small Alpine-based Node 22 image
  • openssh-client: required for the ssh_exec tool to reach Tailscale devices
  • npm install --omit=dev: installs only production dependencies to keep the image lean
  • Built-in health check: polls the server’s HTTP port every 30 seconds