From f37e4486ac738bf68d93704ab9a3140ca4a56955 Mon Sep 17 00:00:00 2001 From: Igor Kantor Date: Fri, 19 Jun 2026 17:04:07 -0400 Subject: [PATCH 1/3] Add optimized multi-stage Dockerfile with native CLI support --- .dockerignore | 7 +++++++ Dockerfile | 58 +++++++++++++++++++++++++++++++++++++++++++++++++++ README.md | 34 ++++++++++++++++++++++++++++++ 3 files changed, 99 insertions(+) create mode 100644 .dockerignore create mode 100644 Dockerfile diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..6c35f79 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,7 @@ +target/ +.git/ +.github/ +.claude/ +.serena/ +IMPROVEMENT_PLAN.md +README.md diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..55fb619 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,58 @@ +# ============================================================================ +# Stage 1: Chef — prepare recipe for dependency caching +# ============================================================================ +FROM rust:1.87-slim AS chef + +RUN apt-get update && apt-get install -y --no-install-recommends \ + pkg-config \ + && rm -rf /var/lib/apt/lists/* + +RUN cargo install cargo-chef --locked +WORKDIR /app + +# ============================================================================ +# Stage 2: Planner — compute dependency recipe (cache-key) +# ============================================================================ +FROM chef AS planner +COPY Cargo.toml Cargo.lock ./ +COPY src/ src/ +RUN cargo chef prepare --recipe-path recipe.json + +# ============================================================================ +# Stage 3: Builder — build dependencies (cached) then application +# ============================================================================ +FROM chef AS builder + +COPY --from=planner /app/recipe.json recipe.json + +# Build dependencies only — this layer is cached until Cargo.toml/lock change +RUN cargo chef cook --release --recipe-path recipe.json + +# Now build the actual application +COPY Cargo.toml Cargo.lock ./ +COPY src/ src/ +RUN cargo build --release \ + && strip target/release/code-mcp + +# ============================================================================ +# Stage 4: Runtime — minimal distroless-style image +# ============================================================================ +FROM debian:bookworm-slim AS runtime + +RUN apt-get update && apt-get install -y --no-install-recommends \ + ca-certificates \ + git \ + && rm -rf /var/lib/apt/lists/* \ + && useradd --create-home --shell /bin/false mcp + +COPY --from=builder /app/target/release/code-mcp /usr/local/bin/code-mcp + +USER mcp +WORKDIR /project + +EXPOSE 8080 + +# All CLI flags are passed directly via CMD/args — ENTRYPOINT is the binary +ENTRYPOINT ["code-mcp"] +# Sensible defaults: bind all interfaces, project root = /project +CMD ["--bind", "0.0.0.0:8080", "--project", "/project"] diff --git a/README.md b/README.md index d498a1c..f037c55 100644 --- a/README.md +++ b/README.md @@ -115,6 +115,40 @@ cargo build --release ./target/release/code-mcp --bind 0.0.0.0:8080 --project ./my/repo ``` +### Docker + +The included `Dockerfile` uses a multi-stage build with `cargo-chef` for optimal layer caching. The final image is based on `debian:bookworm-slim` (~80 MB) and contains only the stripped binary and `git` (needed by the `ignore` crate for `.gitignore` traversal). + +```sh +# Build +docker build -t code-mcp . + +# Run with defaults (bind 0.0.0.0:8080, project /project) +docker run -p 8080:8080 -v /path/to/repo:/project:ro code-mcp + +# Override any flag — all CLI args are supported natively via ENTRYPOINT +docker run -p 9090:9090 \ + -v /path/to/repo:/src:ro \ + -v /path/to/memories:/memories:ro \ + code-mcp \ + --bind 0.0.0.0:9090 \ + --project /src \ + --memory-dir /memories \ + --max-sessions 128 \ + --initialize-rate-per-min 20 \ + --session-idle-timeout-secs 3600 + +# With debug logging +docker run -p 8080:8080 -e RUST_LOG=debug,rmcp=info \ + -v /path/to/repo:/project:ro code-mcp +``` + +The `ENTRYPOINT` is the binary itself, so any arguments after the image name replace the default `CMD` and go directly to clap. Pass `--help` to see all options: + +```sh +docker run --rm code-mcp --help +``` + Flags: - `--bind ` — default `0.0.0.0:8080`. From 8c6ad67373b4e564a18a504212bcb55ca468039b Mon Sep 17 00:00:00 2001 From: Igor Kantor Date: Fri, 19 Jun 2026 17:06:27 -0400 Subject: [PATCH 2/3] Add GHCR Docker publish workflow (multi-arch, triggered on release tags) --- .github/workflows/docker.yml | 52 ++++++++++++++++++++++++++++++++++++ README.md | 8 ++++++ 2 files changed, 60 insertions(+) create mode 100644 .github/workflows/docker.yml diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml new file mode 100644 index 0000000..4df672a --- /dev/null +++ b/.github/workflows/docker.yml @@ -0,0 +1,52 @@ +name: Docker + +on: + push: + tags: + - '**[0-9]+.[0-9]+.[0-9]+*' + +env: + REGISTRY: ghcr.io + IMAGE_NAME: ${{ github.repository }} + +jobs: + docker: + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + steps: + - uses: actions/checkout@v6 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - 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: | + type=semver,pattern={{version}} + type=semver,pattern={{major}}.{{minor}} + type=semver,pattern={{major}} + type=sha + type=raw,value=latest,enable={{is_default_branch}} + + - name: Build and push + uses: docker/build-push-action@v6 + with: + context: . + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + cache-from: type=gha + cache-to: type=gha,mode=max + platforms: linux/amd64,linux/arm64 diff --git a/README.md b/README.md index f037c55..ea4ec7b 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,14 @@ Re-uses ripgrep crates just like ripgrep itself. ### Pre-built binaries Download the latest release from the [Releases page](https://github.com/devfire/code-mcp/releases). +### Docker (GHCR) +```bash +docker pull ghcr.io/devfire/code-mcp:latest +docker run -p 8080:8080 -v /path/to/repo:/project:ro ghcr.io/devfire/code-mcp +``` + +Multi-arch images (`linux/amd64`, `linux/arm64`) are published automatically on every release. + ### From source ```bash cargo install --git https://github.com/devfire/code-mcp.git From dee04d12f7580dc935f34d368de44ce148fa4033 Mon Sep 17 00:00:00 2001 From: Igor Kantor Date: Fri, 19 Jun 2026 17:26:05 -0400 Subject: [PATCH 3/3] release: version 0.2.0 --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 55fb619..cf339b9 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,7 +1,7 @@ # ============================================================================ # Stage 1: Chef — prepare recipe for dependency caching # ============================================================================ -FROM rust:1.87-slim AS chef +FROM rust:1.95-slim AS chef RUN apt-get update && apt-get install -y --no-install-recommends \ pkg-config \