diff --git a/.goreleaser.yaml b/.goreleaser.yaml index b769b92..0d4afdf 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -51,8 +51,47 @@ release: dockers: - image_templates: - - "streamnative/snmcp:{{ .Tag }}" - - "streamnative/snmcp:latest" + - "streamnative/snmcp:{{ .Tag }}-amd64" + - "streamnative/snmcp:latest-amd64" + dockerfile: Dockerfile.goreleaser + goos: linux + goarch: amd64 + - image_templates: + - "streamnative/snmcp:{{ .Tag }}-arm64" + - "streamnative/snmcp:latest-arm64" + dockerfile: Dockerfile.goreleaser + goos: linux + goarch: arm64 + - image_templates: + - "streamnative/mcp-server:{{ .Tag }}-amd64" + - "streamnative/mcp-server:latest-amd64" + dockerfile: Dockerfile.goreleaser + goos: linux + goarch: amd64 + - image_templates: + - "streamnative/mcp-server:{{ .Tag }}-arm64" + - "streamnative/mcp-server:latest-arm64" + dockerfile: Dockerfile.goreleaser + goos: linux + goarch: arm64 + +docker_manifests: + - name_template: "streamnative/snmcp:{{ .Tag }}" + image_templates: + - "streamnative/snmcp:{{ .Tag }}-amd64" + - "streamnative/snmcp:{{ .Tag }}-arm64" + - name_template: "streamnative/snmcp:latest" + image_templates: + - "streamnative/snmcp:latest-amd64" + - "streamnative/snmcp:latest-arm64" + - name_template: "streamnative/mcp-server:{{ .Tag }}" + image_templates: + - "streamnative/mcp-server:{{ .Tag }}-amd64" + - "streamnative/mcp-server:{{ .Tag }}-arm64" + - name_template: "streamnative/mcp-server:latest" + image_templates: + - "streamnative/mcp-server:latest-amd64" + - "streamnative/mcp-server:latest-arm64" brews: - name: snmcp diff --git a/DOCKER_BUILD.md b/DOCKER_BUILD.md new file mode 100644 index 0000000..cbb7420 --- /dev/null +++ b/DOCKER_BUILD.md @@ -0,0 +1,165 @@ +# Building Multi-Platform Docker Images + +This document explains how to build Docker images that work on both Linux and Mac (including M1/M2/M3 Macs). + +## Prerequisites + +- Docker 19.03 or newer with buildx support +- Docker buildx plugin enabled + +## Quick Start + +### Build for Local Platform + +To build a Docker image for your current platform: + +```bash +make docker-build +``` + +This creates images with both names for backward compatibility: +- `docker.io/streamnative/mcp-server:latest` +- `docker.io/streamnative/mcp-server:` +- `docker.io/streamnative/snmcp:latest` (legacy name) +- `docker.io/streamnative/snmcp:` (legacy name) + +### Build Multi-Platform Images + +To build images for both Linux (amd64) and Mac (arm64): + +```bash +make docker-build-multiplatform +``` + +### Build and Push to Registry + +To build multi-platform images and push them to a registry: + +```bash +make docker-build-push +``` + +## Customization + +You can customize the build using environment variables: + +```bash +# Use custom registry +DOCKER_REGISTRY=myregistry.io make docker-build-push + +# Use custom image names +DOCKER_IMAGE=my-mcp-server DOCKER_IMAGE_LEGACY=my-snmcp make docker-build + +# Build for specific platforms +DOCKER_PLATFORMS="linux/amd64,linux/arm64,linux/arm/v7" make docker-build-multiplatform +``` + +## Platform Support + +By default, images are built for: +- `linux/amd64` - Intel/AMD 64-bit processors (most Linux servers and older Macs) +- `linux/arm64` - ARM 64-bit processors (Apple Silicon Macs, ARM-based servers) + +## Running the Image + +You can use either `mcp-server` or `snmcp` image names - they are identical: + +### On Linux (amd64/x86_64) + +```bash +# Using the new name +docker run --rm -it docker.io/streamnative/mcp-server:latest + +# Using the legacy name (backward compatibility) +docker run --rm -it docker.io/streamnative/snmcp:latest +``` + +### On Mac (Intel) + +```bash +# Using the new name +docker run --rm -it docker.io/streamnative/mcp-server:latest + +# Using the legacy name (backward compatibility) +docker run --rm -it docker.io/streamnative/snmcp:latest +``` + +### On Mac (Apple Silicon M1/M2/M3) + +```bash +# Using the new name +docker run --rm -it --platform linux/arm64 docker.io/streamnative/mcp-server:latest + +# Using the legacy name (backward compatibility) +docker run --rm -it --platform linux/arm64 docker.io/streamnative/snmcp:latest +``` + +## Image Names + +For backward compatibility, all builds create images with two names: +- `streamnative/mcp-server` - The new, preferred name +- `streamnative/snmcp` - The legacy name for backward compatibility + +Both names point to the exact same image and can be used interchangeably. + +## Troubleshooting + +### Enable Docker Buildx + +If buildx is not available, enable it: + +```bash +# Check if buildx is available +docker buildx version + +# Create and use a new builder +docker buildx create --use +``` + +### Clean Build Environment + +If you encounter issues, clean the buildx environment: + +```bash +make docker-buildx-clean +make docker-buildx-setup +``` + +## Build Arguments + +The Dockerfile accepts the following build arguments: +- `VERSION` - Version string for the binary +- `COMMIT` - Git commit hash +- `BUILD_DATE` - Build timestamp + +These are automatically set by the Makefile based on git information. + +## Security + +The Docker image: +- Runs as a non-root user (uid: 1000, gid: 1000) +- Uses minimal Alpine Linux base image +- Includes only necessary CA certificates for HTTPS connections +- Has a read-only root filesystem (can be enforced with `--read-only` flag) + +## Example: Complete Build and Run + +```bash +# Setup buildx (first time only) +make docker-buildx-setup + +# Build multi-platform image (creates both mcp-server and snmcp tags) +make docker-build-multiplatform + +# Run on current platform (using new name) +docker run --rm -it docker.io/streamnative/mcp-server:latest + +# Run on current platform (using legacy name) +docker run --rm -it docker.io/streamnative/snmcp:latest + +# Run with custom configuration +docker run --rm -it \ + -v $(pwd)/config.yaml:/server/config.yaml:ro \ + docker.io/streamnative/mcp-server:latest \ + --config /server/config.yaml +``` \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 5f7ea33..379dc67 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,60 @@ +# Multi-stage build for multi-platform support +FROM --platform=$BUILDPLATFORM golang:1.24-alpine AS builder + +# Install build dependencies +RUN apk add --no-cache git make + +# Set working directory +WORKDIR /build + +# Copy go mod files and SDK directories first +COPY go.mod go.sum go.work go.work.sum ./ +COPY sdk/ ./sdk/ + +# Download dependencies +RUN go mod download + +# Copy source code +COPY . . + +# Build arguments for cross-compilation +ARG TARGETOS +ARG TARGETARCH +ARG VERSION +ARG COMMIT +ARG BUILD_DATE + +# Build the binary for the target platform +RUN CGO_ENABLED=0 GOOS=${TARGETOS} GOARCH=${TARGETARCH} \ + go build -ldflags "\ + -X main.version=${VERSION} \ + -X main.commit=${COMMIT} \ + -X main.date=${BUILD_DATE}" \ + -o snmcp cmd/streamnative-mcp-server/main.go + +# Final stage - minimal Alpine image FROM alpine:3.21 + +# Install CA certificates for HTTPS connections RUN apk --no-cache add ca-certificates + +# Create non-root user +RUN addgroup -g 1000 snmcp && \ + adduser -D -u 1000 -G snmcp snmcp + +# Set working directory WORKDIR /server -COPY snmcp /server/snmcp + +# Copy binary from builder +COPY --from=builder /build/snmcp /server/snmcp + +# Change ownership +RUN chown -R snmcp:snmcp /server + +# Switch to non-root user +USER snmcp + +# Expose port if needed (adjust based on your application) +# EXPOSE 8080 ENTRYPOINT ["/server/snmcp"] diff --git a/Dockerfile.goreleaser b/Dockerfile.goreleaser new file mode 100644 index 0000000..738e44c --- /dev/null +++ b/Dockerfile.goreleaser @@ -0,0 +1,29 @@ +# Minimal Alpine image for GoReleaser builds +FROM alpine:3.21 + +# Install CA certificates for HTTPS connections +RUN apk --no-cache add ca-certificates + +# Create non-root user +RUN addgroup -g 1000 snmcp && \ + adduser -D -u 1000 -G snmcp snmcp + +# Set working directory +WORKDIR /server + +# Copy pre-built binary from GoReleaser +COPY snmcp /server/snmcp + +# Make binary executable +RUN chmod +x /server/snmcp + +# Change ownership +RUN chown -R snmcp:snmcp /server + +# Switch to non-root user +USER snmcp + +# Expose port if needed (adjust based on your application) +# EXPOSE 8080 + +ENTRYPOINT ["/server/snmcp"] \ No newline at end of file diff --git a/Makefile b/Makefile index 569cb67..6aa2a26 100644 --- a/Makefile +++ b/Makefile @@ -5,6 +5,13 @@ GIT_COMMIT=$(shell git rev-parse HEAD) BUILD_DATE=$(shell date -u +"%Y-%m-%dT%H:%M:%SZ") MKDIR_P = mkdir -p +# Docker configuration +DOCKER_REGISTRY ?= docker.io +DOCKER_IMAGE ?= streamnative/mcp-server +DOCKER_IMAGE_LEGACY ?= streamnative/snmcp +DOCKER_TAG ?= $(GIT_VERSION) +DOCKER_PLATFORMS ?= linux/amd64,linux/arm64 + export GOPRIVATE=github.com/streamnative .PHONY: all @@ -19,6 +26,64 @@ build: -X ${VERSION_PATH}.date=${BUILD_DATE}" \ -o bin/snmcp cmd/streamnative-mcp-server/main.go +# Build Docker image for local platform with both names +.PHONY: docker-build +docker-build: + docker build \ + --build-arg VERSION=$(GIT_VERSION) \ + --build-arg COMMIT=$(GIT_COMMIT) \ + --build-arg BUILD_DATE=$(BUILD_DATE) \ + -t $(DOCKER_REGISTRY)/$(DOCKER_IMAGE):$(DOCKER_TAG) \ + -t $(DOCKER_REGISTRY)/$(DOCKER_IMAGE):latest \ + -t $(DOCKER_REGISTRY)/$(DOCKER_IMAGE_LEGACY):$(DOCKER_TAG) \ + -t $(DOCKER_REGISTRY)/$(DOCKER_IMAGE_LEGACY):latest \ + . + +# Build multi-platform Docker image and push to registry with both names +.PHONY: docker-build-push +docker-build-push: docker-buildx-setup + docker buildx build \ + --platform $(DOCKER_PLATFORMS) \ + --build-arg VERSION=$(GIT_VERSION) \ + --build-arg COMMIT=$(GIT_COMMIT) \ + --build-arg BUILD_DATE=$(BUILD_DATE) \ + -t $(DOCKER_REGISTRY)/$(DOCKER_IMAGE):$(DOCKER_TAG) \ + -t $(DOCKER_REGISTRY)/$(DOCKER_IMAGE):latest \ + -t $(DOCKER_REGISTRY)/$(DOCKER_IMAGE_LEGACY):$(DOCKER_TAG) \ + -t $(DOCKER_REGISTRY)/$(DOCKER_IMAGE_LEGACY):latest \ + --push \ + . + +# Build multi-platform Docker image without pushing (for testing) with both names +.PHONY: docker-build-multiplatform +docker-build-multiplatform: docker-buildx-setup + docker buildx build \ + --platform $(DOCKER_PLATFORMS) \ + --build-arg VERSION=$(GIT_VERSION) \ + --build-arg COMMIT=$(GIT_COMMIT) \ + --build-arg BUILD_DATE=$(BUILD_DATE) \ + -t $(DOCKER_REGISTRY)/$(DOCKER_IMAGE):$(DOCKER_TAG) \ + -t $(DOCKER_REGISTRY)/$(DOCKER_IMAGE):latest \ + -t $(DOCKER_REGISTRY)/$(DOCKER_IMAGE_LEGACY):$(DOCKER_TAG) \ + -t $(DOCKER_REGISTRY)/$(DOCKER_IMAGE_LEGACY):latest \ + --load \ + . + +# Setup Docker buildx for multi-platform builds +.PHONY: docker-buildx-setup +docker-buildx-setup: + @if ! docker buildx ls | grep -q mcp-builder; then \ + docker buildx create --name mcp-builder --use; \ + docker buildx inspect --bootstrap; \ + else \ + docker buildx use mcp-builder; \ + fi + +# Clean Docker buildx builder +.PHONY: docker-buildx-clean +docker-buildx-clean: + -docker buildx rm mcp-builder + .PHONY: license-check license-check: license-eye header check