Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 54 additions & 0 deletions .github/workflows/docker-publish.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
name: Docker

on:
push:
tags:
- "v*" # Publish on every release tag, e.g. v0.1.4
workflow_dispatch: # Allow manual trigger from the Actions UI

env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }} # ghcr.io/cocoindex-io/cocoindex-code

jobs:
build-and-push:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write

steps:
- name: Checkout
uses: actions/checkout@v4

- name: Log in to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Extract metadata (tags, labels)
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
# e.g. v0.1.4 → :0.1.4 and :latest
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=raw,value=latest,enable=${{ github.ref == format('refs/tags/{0}', github.ref_name) }}
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

- name: Build and push
uses: docker/build-push-action@v6
with:
context: .
file: docker/Dockerfile
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
113 changes: 113 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,119 @@ ccc search --refresh database schema # update index first, then

By default, `ccc search` scopes results to your current working directory (relative to the project root). Use `--path` to override.

## Docker

A Docker image is available for teams who want a reproducible, dependency-free
setup — no Python, `uv`, or system dependencies required on the host.

### Pull the image

```bash
docker pull ghcr.io/cocoindex-io/cocoindex-code:latest
```

### Claude Code

```bash
claude mcp add cocoindex-code \
-- docker run --rm --interactive \
--volume "$(pwd):/workspace" \
--volume cocoindex-model-cache:/root/.cache \
ghcr.io/cocoindex-io/cocoindex-code:latest
```

### Codex

```bash
codex mcp add cocoindex-code \
-- docker run --rm --interactive \
--volume "$(pwd):/workspace" \
--volume cocoindex-model-cache:/root/.cache \
ghcr.io/cocoindex-io/cocoindex-code:latest
```

> **Tip — pre-warm the index before first use:**
> The MCP server indexes on demand, but for large codebases you can run the
> indexer once in the foreground to watch progress and ensure everything is
> ready:
>
> ```bash
> docker run --rm \
> --volume "$(pwd):/workspace" \
> --volume cocoindex-model-cache:/root/.cache \
> --entrypoint cocoindex-code \
> ghcr.io/cocoindex-io/cocoindex-code:latest index
> ```
### Configuration via environment variables
All [configuration variables](#configuration) work identically in Docker — pass
them with `--env` / `-e`:
```bash
# Extra extensions (e.g. Typesafe Config, SBT build files)
-e COCOINDEX_CODE_EXTRA_EXTENSIONS="conf,sbt"
# Exclude build artefacts (Scala/SBT example)
-e COCOINDEX_CODE_EXCLUDE_PATTERNS='["**/target/**","**/.bloop/**","**/.metals/**"]'
# Swap in a code-optimised embedding model
-e COCOINDEX_CODE_EMBEDDING_MODEL=voyage/voyage-code-3
-e VOYAGE_API_KEY=your-key
```
Full example with all options:
```bash
docker run --rm --interactive \
--volume "$(pwd):/workspace" \
--volume cocoindex-model-cache:/root/.cache \
-e COCOINDEX_CODE_EXTRA_EXTENSIONS="conf,sbt" \
-e COCOINDEX_CODE_EXCLUDE_PATTERNS='["**/target/**","**/.bloop/**"]' \
-e COCOINDEX_CODE_EMBEDDING_MODEL=voyage/voyage-code-3 \
-e VOYAGE_API_KEY=your-key \
ghcr.io/cocoindex-io/cocoindex-code:latest
```
### Named volume for the model cache
The default embedding model (~90 MB) is baked into the image. If you override
`COCOINDEX_CODE_EMBEDDING_MODEL` with an external model, use a named volume so
it is downloaded only once:
```bash
docker volume create cocoindex-model-cache
```
### Build the image locally
```bash
docker build -t cocoindex-code:local -f docker/Dockerfile .
```
### `.mcp.json` (Claude Code project config)
```json
{
"mcpServers": {
"cocoindex-code": {
"type": "stdio",
"command": "docker",
"args": [
"run", "--rm", "--interactive",
"--volume", "/path/to/your/project:/workspace",
"--volume", "cocoindex-model-cache:/root/.cache",
"ghcr.io/cocoindex-io/cocoindex-code:latest"
]
}
}
}
```
> **Note:** The index (`.cocoindex_code/`) is written inside `/workspace`, which
> maps to your project root on the host. Incremental updates persist between
> sessions. Add `.cocoindex_code/` to your `.gitignore`.
## Features
- **Semantic Code Search**: Find relevant code using natural language queries when grep doesn't work well, and save tokens immediately.
- **Ultra Performant**: ⚡ Built on top of ultra performant [Rust indexing engine](https://github.com/cocoindex-io/cocoindex). Only re-indexes changed files for fast updates.
Expand Down
4 changes: 4 additions & 0 deletions docker/.dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Ignore everything that isn't needed to build the image.
# The Dockerfile installs cocoindex-code from PyPI — no source files are needed.
**
!docker/Dockerfile
56 changes: 56 additions & 0 deletions docker/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# ─── Stage 1: install dependencies ───────────────────────────────────────────
# Use slim (glibc-based) — cocoindex ships pre-built Rust wheels that need glibc.
# Alpine / musl-libc would require building from source.
FROM python:3.12-slim AS builder

# Install uv via pip — avoids a ghcr.io pull during build
RUN pip install --quiet uv

WORKDIR /build

RUN uv pip install --system --prerelease=allow \
"cocoindex>=1.0.0a33" \
"cocoindex-code" \
"sentence-transformers>=3.3.1"

# ─── Stage 2: pre-bake the default embedding model ────────────────────────────
# Bakes sentence-transformers/all-MiniLM-L6-v2 into the image so cold container
# starts don't trigger a ~90 MB download. Skip this stage (--target builder) if
# you always supply COCOINDEX_CODE_EMBEDDING_MODEL pointing at an external model.
FROM builder AS model_cache

RUN python -c "from sentence_transformers import SentenceTransformer; SentenceTransformer('sentence-transformers/all-MiniLM-L6-v2'); print('Model cached.')"

# ─── Stage 3: runtime ─────────────────────────────────────────────────────────
FROM python:3.12-slim AS runtime

# Copy installed packages and cached model from previous stages
COPY --from=model_cache /usr/local/lib/python3.12 /usr/local/lib/python3.12
COPY --from=model_cache /usr/local/bin/cocoindex-code /usr/local/bin/cocoindex-code
COPY --from=model_cache /usr/local/bin/ccc /usr/local/bin/ccc
COPY --from=model_cache /root/.cache /root/.cache

# The codebase is mounted at runtime — nothing project-specific lives here.
WORKDIR /workspace

# ── Runtime defaults (all overridable via -e / --env) ─────────────────────────
# Root path: point at the mounted workspace.
ENV COCOINDEX_CODE_ROOT_PATH=/workspace

# Additional extensions: add project-specific extras at runtime, e.g.:
# -e COCOINDEX_CODE_EXTRA_EXTENSIONS="conf,sbt"
# See README for ext:lang syntax to map extensions to existing parsers.

# Exclude patterns: override at runtime for your build system, e.g.:
# -e COCOINDEX_CODE_EXCLUDE_PATTERNS='["**/target/**","**/node_modules/**"]'

# Embedding model: defaults to local SentenceTransformers (no API key needed).
# Override for a code-optimised model, e.g.:
# -e COCOINDEX_CODE_EMBEDDING_MODEL=voyage/voyage-code-3
# -e VOYAGE_API_KEY=your-key

# ── MCP stdio entrypoint ──────────────────────────────────────────────────────
# Runs the MCP server over stdio (JSON-RPC on stdin/stdout).
# Auto-reads env vars, creates settings, and starts the internal daemon.
# One-shot indexer: override with --entrypoint cocoindex-code ... index
ENTRYPOINT ["cocoindex-code"]
Loading