Files
openproject-mcp/docs/specs/2026-09-06-docker-ci-pipeline-design.md

7.3 KiB

Specification: Docker Packaging & GitHub Actions CI/CD Pipeline

1. Overview & Objectives

This specification defines the Docker containerization and continuous integration (CI/CD) pipeline for openproject-mcp. The goal is to produce an official, multi-platform Docker container image published to GitHub Container Registry (ghcr.io) whenever changes land on main or semantic release tags are pushed, while validating code health and container build integrity on every pull request.

Primary Objectives

  1. Container Packaging: Provide a lean, secure, multi-stage Dockerfile and .dockerignore targeting the official oven/bun:1-slim runtime.
  2. Multi-Architecture Support: Build for both linux/amd64 and linux/arm64 using Docker Buildx and QEMU.
  3. Least Privilege Security: Run container processes under the unprivileged bun user (UID/GID 1000) and ensure credentials (.env*, tokens) are never included in the image layers.
  4. CI/CD Automation: Create a unified GitHub Actions workflow (.github/workflows/ci.yml) that runs type checking (tsc --noEmit), test suites (bun test), and multi-arch Docker image compilation.
  5. Publishing Strategy: Publish images to GitHub Container Registry (ghcr.io/<owner>/<repo>) with latest, commit SHA (sha-<hash>), and semantic version tags (vX.Y.Z, X.Y), while verifying pull requests with build-only checks without pushing.
  6. Documentation: Update README.md with Docker execution instructions and record an Architectural Decision Record in docs/DECISIONS.md.

2. Container Architecture & Packaging

2.1 Base Image & Runtime Strategy

  • Base Image: oven/bun:1-slim (Debian Bookworm slim base with Bun v1.x).
  • Rationale:
    • Official Bun Docker image maintained by Oven.
    • Includes glibc and essential CA certificates needed for HTTPS communication with OpenProject instances.
    • Slim footprint (~80-90 MB) compared to full OS images.
    • Provides a built-in unprivileged user bun (UID 1000, GID 1000).

2.2 Multi-Stage Dockerfile (Dockerfile)

# Stage 1: Dependency resolution and production prune
FROM oven/bun:1-slim AS builder
WORKDIR /app

# Copy dependency manifests
COPY package.json bun.lock ./

# Install production dependencies only with strict lockfile
RUN bun install --frozen-lockfile --production

# Stage 2: Minimal runtime image
FROM oven/bun:1-slim AS runner
WORKDIR /app

ENV NODE_ENV=production

# Copy installed production node_modules from builder
COPY --from=builder /app/node_modules ./node_modules

# Copy application configuration and source code
COPY package.json tsconfig.json ./
COPY src ./src

# Set ownership and drop root privileges
RUN chown -R bun:bun /app
USER bun

# Standard MCP stdio communication
ENTRYPOINT ["bun", "run", "src/index.ts"]

2.3 Context Hygiene (.dockerignore)

The .dockerignore file prevents build context bloat and guarantees sensitive local files are excluded:

node_modules
.git
.github
.env*
coverage
tests
docs
scripts
.superpowers
*.log
*.md
docker-compose.yml

3. GitHub Actions CI/CD Pipeline (.github/workflows/ci.yml)

3.1 Pipeline Triggers & Permissions

  • Triggers:
    • push: branches [main], tags ['v*.*.*']
    • pull_request: branches [main]
    • workflow_dispatch: manual execution
  • Permissions:
    • contents: read
    • packages: write (for publishing container images to GHCR)

3.2 Pipeline Jobs

graph TD
    A[Trigger: Push / PR / Tag] --> B[Job 1: test]
    B --> C[Setup Bun 1.3.14]
    C --> D[bun install --frozen-lockfile]
    D --> E[bun run typecheck]
    E --> F[bun test]
    F --> G{All Tests Passed?}
    G -- Yes --> H[Job 2: docker]
    G -- No --> Z[Fail Pipeline]
    H --> I[Setup QEMU & Buildx]
    H --> J[Compute Docker Metadata & Tags]
    H --> K{Is PR?}
    K -- Yes --> L[Build Multi-Arch (amd64, arm64) with GHA Cache - Push=False]
    K -- No --> M[Login to GHCR with GITHUB_TOKEN]
    M --> N[Build & Push Multi-Arch (amd64, arm64) to GHCR]

Job 1: test

  • Environment: ubuntu-latest
  • Steps:
    1. actions/checkout@v4
    2. oven-sh/setup-bun@v2 with bun-version: 1.3.14
    3. bun install --frozen-lockfile
    4. bun run typecheck (tsc --noEmit)
    5. bun test (all mock unit and integration tests execute; live container tests gracefully skip if environment variables are unset)

Job 2: docker

  • Depends on: test (will not run if tests or typecheck fail)
  • Environment: ubuntu-latest
  • Steps:
    1. actions/checkout@v4
    2. docker/setup-qemu-action@v3 (enables multi-arch emulation for linux/amd64 and linux/arm64)
    3. docker/setup-buildx-action@v3
    4. docker/metadata-action@v5:
      • Image base: ghcr.io/${{ github.repository }}
      • Flavor: latest=auto
      • Tags:
        • type=ref,event=branch
        • type=ref,event=pr
        • type=semver,pattern={{version}}
        • type=semver,pattern={{major}}.{{minor}}
        • type=sha,prefix=sha-
        • type=raw,value=latest,enable=${{ github.ref == 'refs/heads/main' }}
    5. docker/login-action@v3 (run only if github.event_name != 'pull_request'):
      • Registry: ghcr.io
      • Username: ${{ github.actor }}
      • Password: ${{ secrets.GITHUB_TOKEN }}
    6. docker/build-push-action@v6:
      • Context: .
      • File: ./Dockerfile
      • Platforms: linux/amd64,linux/arm64
      • 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

4. Operational Usage

4.1 Running the Docker Image

Users can run the container image via interactive stdio transport:

docker run -i --rm \
  -e OPENPROJECT_BASE_URL="https://openproject.example.com" \
  -e OPENPROJECT_API_KEY="your-api-key" \
  ghcr.io/<owner>/openproject-mcp:latest

Running in read-only mode:

docker run -i --rm \
  -e OPENPROJECT_BASE_URL="https://openproject.example.com" \
  -e OPENPROJECT_API_KEY="your-api-key" \
  ghcr.io/<owner>/openproject-mcp:latest --read-only

4.2 MCP Client Configuration Example (Claude Desktop / Cursor)

{
  "mcpServers": {
    "openproject": {
      "command": "docker",
      "args": [
        "run",
        "-i",
        "--rm",
        "-e", "OPENPROJECT_BASE_URL=https://openproject.example.com",
        "-e", "OPENPROJECT_API_KEY=your-api-key",
        "ghcr.io/<owner>/openproject-mcp:latest"
      ]
    }
  }
}

5. Verification & Testing Plan

  1. Local Docker Build: Run docker build -t openproject-mcp:test . to verify multi-stage build finishes with zero errors.
  2. Local Execution & Stdio Verification:
    • Run container with test environment variables:
      docker run -i --rm \
        -e OPENPROJECT_BASE_URL="http://localhost:8080" \
        -e OPENPROJECT_API_KEY="test-token" \
        openproject-mcp:test --read-only
      
    • Verify that container starts as non-root user bun, logs [openproject-mcp] Server started (readOnly=true) to stderr, and keeps stdout clean for JSON-RPC messages.
  3. Workflow Syntax & Linting:
    • Validate .github/workflows/ci.yml YAML syntax and structure.
  4. Documentation Check:
    • Record ADR-015 in docs/DECISIONS.md.
    • Update README.md with Docker usage guide.
    • Update docs/TODO.md tracking the CI/CD pipeline completion.