Skip to content

feat: add Dockerfile and container image CI#25

Open
SzymonIwaniuk wants to merge 3 commits into
kubeflow:mainfrom
SzymonIwaniuk:feat/dockerfile-and-ci
Open

feat: add Dockerfile and container image CI#25
SzymonIwaniuk wants to merge 3 commits into
kubeflow:mainfrom
SzymonIwaniuk:feat/dockerfile-and-ci

Conversation

@SzymonIwaniuk
Copy link
Copy Markdown

@SzymonIwaniuk SzymonIwaniuk commented May 17, 2026

fixes #17

Description

Implements a production-ready container image and CI publish pipeline for in-cluster deployment of the Kubeflow MCP Server.

Dockerfile

  • Multi-stage build: builder (uv install) -> runtime (python:3.12-slim)
  • Non-root user kubeflow-mcp (uid 65532) for hardened security
  • Only the pre-built .venv copied to the final image - no compiler, no uv, no source
  • Exposes port 8000 with kubeflow-mcp serve --transport http as the default entrypoint
  • KUBEFLOW_MCP_AUTH_TOKEN and KUBEFLOW_MCP_LOG_FORMAT=json configurable via env

CI Workflow (.github/workflows/docker-publish.yaml)

  • Triggers on v*.*.* release tags and workflow_dispatch
  • Builds multi-arch image (linux/amd64, linux/arm64) via Docker Buildx + QEMU
  • Pushes to ghcr.io/kubeflow/mcp-server with semver tags (1.2.3, 1.2, 1, latest)
  • No manual secrets - authenticates to GHCR using GITHUB_TOKEN
  • GHA layer cache for fast subsequent builds
  • Trivy scan for CRITICAL CVEs after push - fails the release if any are found

Documentation

  • New "Run with Docker" section in README.md with docker run one-liner, full env var reference table, and MCP client config snippet for HTTP transport

Checklist

  • docker build -t kubeflow-mcp:local . succeeds
  • Container starts and serves on port 8000
  • Linting passes (make verify)
  • Commit messages follow conventional format

cc: @abhijeet-dhumal

Signed-off-by: SzymonIwaniuk <szymon.iwaniuk@joinmoxie.com>
@google-oss-prow
Copy link
Copy Markdown

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by:
Once this PR has been reviewed and has the lgtm label, please assign andreyvelich for approval. For more information see the Kubernetes Code Review Process.

The full list of commands accepted by this bot can be found here.

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@SzymonIwaniuk SzymonIwaniuk changed the title feat: Add Dockerfile, CI publish workflow, and Docker docs feat: add Dockerfile and container image CI May 17, 2026
@abhijeet-dhumal
Copy link
Copy Markdown
Member

Hey @Krishna-kg732, can you take a look at this PR, this will be part of release workflow you are working on right?
#16

type=semver,pattern={{major}}
type=raw,value=latest

- name: Build and push
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you @SzymonIwaniuk . should we consider injecting the release version into the image (via build args / OCI labels)? Right now versioning is only at the tag level, which can make runtime debugging harder.

Copy link
Copy Markdown
Author

@SzymonIwaniuk SzymonIwaniuk May 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, great idea - agree with that

Comment thread .dockerignore Outdated
Comment on lines +45 to +46
*.yaml
*.yml
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
*.yaml
*.yml

Comment thread Dockerfile Outdated

FROM python:3.12-slim AS builder

COPY --from=ghcr.io/astral-sh/uv:latest /uv /usr/local/bin/uv
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
COPY --from=ghcr.io/astral-sh/uv:latest /uv /usr/local/bin/uv
COPY --from=ghcr.io/astral-sh/uv:0.11.14/uv /usr/local/bin/uv

Comment thread Dockerfile Outdated

USER 65532:65532

ENTRYPOINT ["kubeflow-mcp", "serve", "--transport", "http"]
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
ENTRYPOINT ["kubeflow-mcp", "serve", "--transport", "http"]
ENTRYPOINT ["sh", "-c", "exec kubeflow-mcp serve --transport ${MCP_TRANSPORT}"]

@Krishna-kg732
Copy link
Copy Markdown

Hey @SzymonIwaniuk , few nits , rest looks good
Thanks!

cc: @abhijeet-dhumal

@SzymonIwaniuk
Copy link
Copy Markdown
Author

Hey @Krishna-kg732, appreciate suggestions and feedback, thanks - gonna apply them 🚀

Signed-off-by: SzymonIwaniuk <szymon.iwaniuk@joinmoxie.com>
Copilot AI review requested due to automatic review settings May 19, 2026 07:36
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a production container image build plus an automated GitHub Actions pipeline to publish multi-arch images to GHCR, along with Docker run documentation to support in-cluster HTTP deployments of the Kubeflow MCP Server.

Changes:

  • Introduces a multi-stage Dockerfile (builder + slim runtime) running as a non-root user.
  • Adds a GH Actions workflow to build/push multi-arch images on semver tags (and manual dispatch) and run a post-push Trivy scan.
  • Updates README.md with “Run with Docker” instructions and environment variable reference.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.

File Description
README.md Adds Docker usage docs and MCP HTTP client config snippet.
Dockerfile Defines multi-stage build and hardened runtime image defaults/entrypoint.
.github/workflows/docker-publish.yaml Adds CI to build/push multi-arch images to GHCR and scan with Trivy.
.dockerignore Reduces Docker build context by excluding dev/test artifacts and tooling files.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread Dockerfile Outdated
Comment thread Dockerfile
Comment thread README.md Outdated
@SzymonIwaniuk
Copy link
Copy Markdown
Author

SzymonIwaniuk commented May 19, 2026

Applied all suggestions, let me know WDYT @Krishna-kg732

One thing Copilot flagged: KUBEFLOW_MCP_LOG_FORMAT in the Dockerfile has no effect since kubeflow_mcp/core/config.py only reads LOG_FORMAT. I can either fix the Dockerfile to use LOG_FORMAT=jsonor add KUBEFLOW_MCP_LOG_FORMAT support to the config loader with LOG_FORMAT as a fallback which one more suits to you?

@Krishna-kg732
Copy link
Copy Markdown

lgtm,

Let’s keep the config surface simple and align with what the code already expects — I’d go with updating the Dockerfile to use LOG_FORMAT=json. for now .

Signed-off-by: SzymonIwaniuk <szymon.iwaniuk@joinmoxie.com>
@SzymonIwaniuk
Copy link
Copy Markdown
Author

Done

Comment thread Dockerfile
# See the License for the specific language governing permissions and
# limitations under the License.

FROM python:3.12-slim AS builder
Copy link
Copy Markdown
Member

@abhijeet-dhumal abhijeet-dhumal May 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it seems python:3.12-slim is floating tag here. can we pin to a patch version so two builds don't silently pull different base images?

push:
tags:
- "v*.*.*"
workflow_dispatch:
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like there's no PR build path, so a broken Dockerfile would only surface when a release tag is pushed. Would it make sense to add a pull_request trigger with push: false so we at least validate the build on PRs?

uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will tag a v1.0.0-rc.1 as latest too - is that intentional? We could add an enable condition to only update latest on non-prerelease tags:
type=raw,value=latest,enable=${{ github.ref_type == 'tag' && !contains(github.ref_name, '-') }}

- name: Scan image for critical CVEs (Trivy)
uses: aquasecurity/trivy-action@0.30.0
with:
image-ref: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.meta.outputs.version }}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor nit - scanning by tag means there's a small window where another concurrent push could overwrite the tag between the build and the scan. Using the digest directly would be safer, wdyt?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: add Dockerfile and container image CI

4 participants