From 7ce86012d0cae88b15dfbd9ebb336d8edcb4754b Mon Sep 17 00:00:00 2001 From: Lucas Winther Date: Sun, 6 Sep 2026 20:36:43 +0200 Subject: [PATCH] docs(spec): add specification for Docker packaging and GitHub Actions CI pipeline --- .../2026-09-06-docker-ci-pipeline-design.md | 209 ++++++++++++++++++ 1 file changed, 209 insertions(+) create mode 100644 docs/specs/2026-09-06-docker-ci-pipeline-design.md diff --git a/docs/specs/2026-09-06-docker-ci-pipeline-design.md b/docs/specs/2026-09-06-docker-ci-pipeline-design.md new file mode 100644 index 0000000..0b2b814 --- /dev/null +++ b/docs/specs/2026-09-06-docker-ci-pipeline-design.md @@ -0,0 +1,209 @@ +# 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//`) with `latest`, commit SHA (`sha-`), 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`) +```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 + +```mermaid +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: +```bash +docker run -i --rm \ + -e OPENPROJECT_BASE_URL="https://openproject.example.com" \ + -e OPENPROJECT_API_KEY="your-api-key" \ + ghcr.io//openproject-mcp:latest +``` + +Running in read-only mode: +```bash +docker run -i --rm \ + -e OPENPROJECT_BASE_URL="https://openproject.example.com" \ + -e OPENPROJECT_API_KEY="your-api-key" \ + ghcr.io//openproject-mcp:latest --read-only +``` + +### 4.2 MCP Client Configuration Example (Claude Desktop / Cursor) +```json +{ + "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//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: + ```bash + 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.