7.3 KiB
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
- Container Packaging: Provide a lean, secure, multi-stage
Dockerfileand.dockerignoretargeting the officialoven/bun:1-slimruntime. - Multi-Architecture Support: Build for both
linux/amd64andlinux/arm64using Docker Buildx and QEMU. - Least Privilege Security: Run container processes under the unprivileged
bunuser (UID/GID 1000) and ensure credentials (.env*, tokens) are never included in the image layers. - 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. - Publishing Strategy: Publish images to GitHub Container Registry (
ghcr.io/<owner>/<repo>) withlatest, commit SHA (sha-<hash>), and semantic version tags (vX.Y.Z,X.Y), while verifying pull requests with build-only checks without pushing. - Documentation: Update
README.mdwith Docker execution instructions and record an Architectural Decision Record indocs/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: readpackages: 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:
actions/checkout@v4oven-sh/setup-bun@v2withbun-version: 1.3.14bun install --frozen-lockfilebun run typecheck(tsc --noEmit)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:
actions/checkout@v4docker/setup-qemu-action@v3(enables multi-arch emulation forlinux/amd64andlinux/arm64)docker/setup-buildx-action@v3docker/metadata-action@v5:- Image base:
ghcr.io/${{ github.repository }} - Flavor:
latest=auto - Tags:
type=ref,event=branchtype=ref,event=prtype=semver,pattern={{version}}type=semver,pattern={{major}}.{{minor}}type=sha,prefix=sha-type=raw,value=latest,enable=${{ github.ref == 'refs/heads/main' }}
- Image base:
docker/login-action@v3(run only ifgithub.event_name != 'pull_request'):- Registry:
ghcr.io - Username:
${{ github.actor }} - Password:
${{ secrets.GITHUB_TOKEN }}
- Registry:
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
- Context:
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
- Local Docker Build: Run
docker build -t openproject-mcp:test .to verify multi-stage build finishes with zero errors. - 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.
- Run container with test environment variables:
- Workflow Syntax & Linting:
- Validate
.github/workflows/ci.ymlYAML syntax and structure.
- Validate
- Documentation Check:
- Record ADR-015 in
docs/DECISIONS.md. - Update
README.mdwith Docker usage guide. - Update
docs/TODO.mdtracking the CI/CD pipeline completion.
- Record ADR-015 in