Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
91 changes: 70 additions & 21 deletions pkg/container/templates/go.tmpl
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM golang:1.25-alpine
FROM golang:1.25-alpine AS builder

{{if .CACertContent}}
# Add custom CA certificate BEFORE any network operations
Expand All @@ -8,19 +8,8 @@ RUN cat /tmp/custom-ca.crt >> /etc/ssl/certs/ca-certificates.crt && \
rm /tmp/custom-ca.crt
{{end}}

# Install CA certificates
RUN apk add --no-cache ca-certificates

# Set working directory
WORKDIR /app

# Create a non-root user to run the application and set proper permissions
RUN addgroup -S appgroup && \
adduser -S appuser -G appgroup && \
mkdir -p /app && \
chown -R appuser:appgroup /app && \
mkdir -p /home/appuser/.cache && \
chown -R appuser:appgroup /home/appuser
# Install CA certificates and git (needed for go modules)
RUN apk add --no-cache ca-certificates git

{{if .CACertContent}}
# Properly install the custom CA certificate using standard tools
Expand All @@ -37,18 +26,78 @@ ENV CGO_ENABLED=0 \
GOARCH=amd64 \
GO111MODULE=on

# Set working directory
WORKDIR /build

{{if .IsLocalPath}}
# Copy the local source code
COPY . /app/
COPY . /build/

# Build the application
RUN go build -o /app/mcp-server {{.MCPPackage}}
{{else}}
# Pre-install the Go package at build time
# This downloads all dependencies and builds the binary
# Check if the package already has a version specifier
RUN package="{{.MCPPackage}}"; \
# If package doesn't have @ version specifier, add @latest
if ! echo "$package" | grep -q '@'; then \
package="${package}@latest"; \
fi; \
# Create the app directory first
mkdir -p /app && \
# Install the package
go install "$package" && \
# Move the installed binary to a known location
mv $GOPATH/bin/* /app/mcp-server 2>/dev/null || \
# If the package name differs from binary name, try to find it
find $GOPATH/bin -type f -executable -exec mv {} /app/mcp-server \; 2>/dev/null || \
# As a fallback, build it directly (strip version for go build)
(base_package=$(echo "$package" | sed 's/@.*//'); \
go get "$package" && go build -o /app/mcp-server "$base_package")
{{end}}

# Final stage - minimal runtime image
FROM alpine:3.20

{{if .CACertContent}}
# Add custom CA certificate for runtime
COPY ca-cert.crt /tmp/custom-ca.crt
RUN cat /tmp/custom-ca.crt >> /etc/ssl/certs/ca-certificates.crt && \
rm /tmp/custom-ca.crt
{{end}}

# Change ownership of copied files to appuser
USER root
RUN chown -R appuser:appgroup /app
# Install only runtime dependencies
RUN apk add --no-cache ca-certificates

# Set working directory
WORKDIR /app

# Create a non-root user to run the application
RUN addgroup -S appgroup && \
adduser -S appuser -G appgroup && \
mkdir -p /app && \
chown -R appuser:appgroup /app

{{if .CACertContent}}
# Install CA certificate for runtime
RUN mkdir -p /usr/local/share/ca-certificates && \
cp /tmp/custom-ca.crt /usr/local/share/ca-certificates/custom-ca.crt 2>/dev/null || \
echo "CA cert already added to bundle" && \
chmod 644 /usr/local/share/ca-certificates/custom-ca.crt 2>/dev/null || true && \
update-ca-certificates
{{end}}

# Copy the pre-built binary from builder stage
COPY --from=builder --chown=appuser:appgroup /app/mcp-server /app/mcp-server

{{if .IsLocalPath}}
# Copy any additional files that might be needed at runtime
COPY --from=builder --chown=appuser:appgroup /build/ /app/
{{end}}

# Switch to non-root user
USER appuser

# Run the MCP server using go
# The entrypoint will be constructed dynamically based on the package and arguments
ENTRYPOINT ["go", "run", "{{.MCPPackage}}"{{range .MCPArgs}}, "{{.}}"{{end}}]
# Run the pre-built MCP server binary
ENTRYPOINT ["/app/mcp-server"{{range .MCPArgs}}, "{{.}}"{{end}}]
81 changes: 66 additions & 15 deletions pkg/container/templates/npx.tmpl
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM node:22-alpine
FROM node:22-alpine AS builder

{{if .CACertContent}}
# Add custom CA certificate BEFORE any network operations
Expand All @@ -11,14 +11,14 @@ RUN cat /tmp/custom-ca.crt >> /etc/ssl/certs/ca-certificates.crt && \
# Install git for package installation support
RUN apk add --no-cache git ca-certificates

# Set working directory
WORKDIR /app

# Create a non-root user to run the application and set proper permissions
RUN addgroup -S appgroup && \
adduser -S appuser -G appgroup && \
mkdir -p /app && \
chown -R appuser:appgroup /app
{{if .CACertContent}}
# Properly install the custom CA certificate using standard tools
RUN mkdir -p /usr/local/share/ca-certificates && \
cp /tmp/custom-ca.crt /usr/local/share/ca-certificates/custom-ca.crt 2>/dev/null || \
echo "CA cert already added to bundle" && \
chmod 644 /usr/local/share/ca-certificates/custom-ca.crt 2>/dev/null || true && \
update-ca-certificates
{{end}}

# Configure npm for faster installations in containerized environments
ENV NODE_ENV=production \
Expand All @@ -28,21 +28,72 @@ ENV NODE_ENV=production \
NPM_CONFIG_UPDATE_NOTIFIER=false \
NPM_CONFIG_PROGRESS=false

# Set working directory for package installation
WORKDIR /build

{{if .IsLocalPath}}
# Copy the local source code
COPY . /build/
# Install dependencies if package.json exists
RUN if [ -f package.json ]; then npm ci --only=production || npm install --production; fi
{{else}}
# Create a package.json to install the MCP package
RUN echo '{"name":"mcp-container","version":"1.0.0"}' > package.json

# Install the MCP package and its dependencies at build time
# This ensures all dependencies are downloaded during the build phase
RUN npm install --save {{.MCPPackage}}
{{end}}

# Final stage - runtime image with pre-installed packages
FROM node:22-alpine

{{if .CACertContent}}
# Properly install the custom CA certificate using standard tools
# Add custom CA certificate for runtime
COPY ca-cert.crt /tmp/custom-ca.crt
RUN cat /tmp/custom-ca.crt >> /etc/ssl/certs/ca-certificates.crt && \
rm /tmp/custom-ca.crt
{{end}}

# Install runtime dependencies
RUN apk add --no-cache ca-certificates

# Set working directory
WORKDIR /app

# Create a non-root user to run the application
RUN addgroup -S appgroup && \
adduser -S appuser -G appgroup && \
mkdir -p /app && \
chown -R appuser:appgroup /app

{{if .CACertContent}}
# Install CA certificate for runtime
RUN mkdir -p /usr/local/share/ca-certificates && \
cp /tmp/custom-ca.crt /usr/local/share/ca-certificates/custom-ca.crt 2>/dev/null || \
echo "CA cert already added to bundle" && \
chmod 644 /usr/local/share/ca-certificates/custom-ca.crt 2>/dev/null || true && \
update-ca-certificates
{{end}}

# Run the MCP server using npx
# The entrypoint will be constructed dynamically based on the package and arguments
# Using the form: npx -- <pkg>[@<version>] [args...]
# The -- separates npx options from the package name and arguments
# Copy the installed node_modules from builder stage
COPY --from=builder --chown=appuser:appgroup /build/node_modules /app/node_modules

{{if .IsLocalPath}}
# Copy the local application files
COPY --from=builder --chown=appuser:appgroup /build/ /app/
{{else}}
# Copy package.json to maintain the dependency tree
COPY --from=builder --chown=appuser:appgroup /build/package.json /app/package.json
{{end}}

# Set NODE_PATH to find the pre-installed modules
ENV NODE_PATH=/app/node_modules \
PATH=/app/node_modules/.bin:$PATH

# Switch to non-root user
USER appuser

ENTRYPOINT ["npx", "--yes", "--", "{{.MCPPackage}}"{{range .MCPArgs}}, "{{.}}"{{end}}]
# Run the pre-installed MCP package directly using npx
# The package is already installed, so npx will use the local version without downloading
ENTRYPOINT ["npx", "--no-install", "{{.MCPPackage}}"{{range .MCPArgs}}, "{{.}}"{{end}}]
81 changes: 61 additions & 20 deletions pkg/container/templates/templates_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,17 @@ func TestGetDockerfileTemplate(t *testing.T) {
MCPArgs: []string{"--arg1", "--arg2", "value"},
},
wantContains: []string{
"apt-get install -y --no-install-recommends ca-certificates",
"FROM python:3.13-slim AS builder",
"apt-get install -y --no-install-recommends",
"pip install --no-cache-dir uv",
"ENTRYPOINT [\"uvx\", \"example-package\", \"--arg1\", \"--arg2\", \"value\"]",
"package_spec=$(echo \"$package\" | sed 's/@/==/')",
"uv tool install \"$package_spec\"",
"COPY --from=builder --chown=appuser:appgroup /opt/uv-tools /opt/uv-tools",
"ENTRYPOINT [\"sh\", \"-c\", \"package='example-package'; exec \\\"${package%%@*}\\\" \\\"--arg1\\\" \\\"--arg2\\\" \\\"value\\\" \\\"$@\\\"\", \"--\"]",
},
wantMatches: []string{
`FROM python:\d+\.\d+-slim`, // Match any Python version
`FROM python:\d+\.\d+-slim AS builder`, // Match builder stage
`FROM python:\d+\.\d+-slim`, // Match runtime stage
},
wantNotContains: []string{
"Add custom CA certificate",
Expand All @@ -47,16 +52,21 @@ func TestGetDockerfileTemplate(t *testing.T) {
CACertContent: "-----BEGIN CERTIFICATE-----\nMIICertificateContent\n-----END CERTIFICATE-----",
},
wantContains: []string{
"apt-get install -y --no-install-recommends ca-certificates",
"FROM python:3.13-slim AS builder",
"apt-get install -y --no-install-recommends",
"pip install --no-cache-dir uv",
"ENTRYPOINT [\"uvx\", \"example-package\", \"--arg1\", \"--arg2\", \"value\"]",
"package_spec=$(echo \"$package\" | sed 's/@/==/')",
"uv tool install \"$package_spec\"",
"COPY --from=builder --chown=appuser:appgroup /opt/uv-tools /opt/uv-tools",
"ENTRYPOINT [\"sh\", \"-c\", \"package='example-package'; exec \\\"${package%%@*}\\\" \\\"--arg1\\\" \\\"--arg2\\\" \\\"value\\\" \\\"$@\\\"\", \"--\"]",
"Add custom CA certificate BEFORE any network operations",
"COPY ca-cert.crt /tmp/custom-ca.crt",
"cat /tmp/custom-ca.crt >> /etc/ssl/certs/ca-certificates.crt",
"update-ca-certificates",
},
wantMatches: []string{
`FROM python:\d+\.\d+-slim`, // Match any Python version
`FROM python:\d+\.\d+-slim AS builder`, // Match builder stage
`FROM python:\d+\.\d+-slim`, // Match runtime stage
},
wantNotContains: []string{},
wantErr: false,
Expand All @@ -69,10 +79,14 @@ func TestGetDockerfileTemplate(t *testing.T) {
MCPArgs: []string{"--arg1", "--arg2", "value"},
},
wantContains: []string{
"ENTRYPOINT [\"npx\", \"--yes\", \"--\", \"example-package\", \"--arg1\", \"--arg2\", \"value\"]",
"FROM node:22-alpine AS builder",
"npm install --save example-package",
"COPY --from=builder --chown=appuser:appgroup /build/node_modules /app/node_modules",
"ENTRYPOINT [\"npx\", \"--no-install\", \"example-package\", \"--arg1\", \"--arg2\", \"value\"]",
},
wantMatches: []string{
`FROM node:\d+-alpine`, // Match any Node version
`FROM node:\d+-alpine AS builder`, // Match builder stage
`FROM node:\d+-alpine`, // Match runtime stage
},
wantNotContains: []string{
"Add custom CA certificate",
Expand All @@ -89,14 +103,17 @@ func TestGetDockerfileTemplate(t *testing.T) {
CACertContent: "-----BEGIN CERTIFICATE-----\nMIICertificateContent\n-----END CERTIFICATE-----",
},
wantContains: []string{
"ENTRYPOINT [\"npx\", \"--yes\", \"--\", \"example-package\", \"--arg1\", \"--arg2\", \"value\"]",
"FROM node:22-alpine AS builder",
"npm install --save example-package",
"ENTRYPOINT [\"npx\", \"--no-install\", \"example-package\", \"--arg1\", \"--arg2\", \"value\"]",
"Add custom CA certificate BEFORE any network operations",
"COPY ca-cert.crt /tmp/custom-ca.crt",
"cat /tmp/custom-ca.crt >> /etc/ssl/certs/ca-certificates.crt",
"update-ca-certificates",
},
wantMatches: []string{
`FROM node:\d+-alpine`, // Match any Node version
`FROM node:\d+-alpine AS builder`, // Match builder stage
`FROM node:\d+-alpine`, // Match runtime stage
},
wantNotContains: []string{},
wantErr: false,
Expand All @@ -109,10 +126,17 @@ func TestGetDockerfileTemplate(t *testing.T) {
MCPArgs: []string{"--arg1", "--arg2", "value"},
},
wantContains: []string{
"ENTRYPOINT [\"go\", \"run\", \"example-package\", \"--arg1\", \"--arg2\", \"value\"]",
"FROM golang:1.25-alpine AS builder",
"if ! echo \"$package\" | grep -q '@'; then",
"package=\"${package}@latest\"",
"go install \"$package\"",
"FROM alpine:3.20",
"COPY --from=builder --chown=appuser:appgroup /app/mcp-server /app/mcp-server",
"ENTRYPOINT [\"/app/mcp-server\", \"--arg1\", \"--arg2\", \"value\"]",
},
wantMatches: []string{
`FROM golang:\d+\.\d+-alpine`, // Match any Go version
`FROM golang:\d+\.\d+-alpine AS builder`, // Match builder stage
`FROM alpine:\d+\.\d+`, // Match runtime stage
},
wantNotContains: []string{
"Add custom CA certificate",
Expand All @@ -129,14 +153,20 @@ func TestGetDockerfileTemplate(t *testing.T) {
CACertContent: "-----BEGIN CERTIFICATE-----\nMIICertificateContent\n-----END CERTIFICATE-----",
},
wantContains: []string{
"ENTRYPOINT [\"go\", \"run\", \"example-package\", \"--arg1\", \"--arg2\", \"value\"]",
"FROM golang:1.25-alpine AS builder",
"if ! echo \"$package\" | grep -q '@'; then",
"package=\"${package}@latest\"",
"go install \"$package\"",
"FROM alpine:3.20",
"ENTRYPOINT [\"/app/mcp-server\", \"--arg1\", \"--arg2\", \"value\"]",
"Add custom CA certificate BEFORE any network operations",
"COPY ca-cert.crt /tmp/custom-ca.crt",
"cat /tmp/custom-ca.crt >> /etc/ssl/certs/ca-certificates.crt",
"update-ca-certificates",
},
wantMatches: []string{
`FROM golang:\d+\.\d+-alpine`, // Match any Go version
`FROM golang:\d+\.\d+-alpine AS builder`, // Match builder stage
`FROM alpine:\d+\.\d+`, // Match runtime stage
},
wantNotContains: []string{},
wantErr: false,
Expand All @@ -150,11 +180,17 @@ func TestGetDockerfileTemplate(t *testing.T) {
IsLocalPath: true,
},
wantContains: []string{
"COPY . /app/",
"ENTRYPOINT [\"go\", \"run\", \"./cmd/server\", \"--arg1\", \"value\"]",
"FROM golang:1.25-alpine AS builder",
"COPY . /build/",
"go build -o /app/mcp-server ./cmd/server",
"FROM alpine:3.20",
"COPY --from=builder --chown=appuser:appgroup /app/mcp-server /app/mcp-server",
"COPY --from=builder --chown=appuser:appgroup /build/ /app/",
"ENTRYPOINT [\"/app/mcp-server\", \"--arg1\", \"value\"]",
},
wantMatches: []string{
`FROM golang:\d+\.\d+-alpine`, // Match any Go version
`FROM golang:\d+\.\d+-alpine AS builder`, // Match builder stage
`FROM alpine:\d+\.\d+`, // Match runtime stage
},
wantNotContains: []string{
"Add custom CA certificate",
Expand All @@ -170,11 +206,16 @@ func TestGetDockerfileTemplate(t *testing.T) {
IsLocalPath: true,
},
wantContains: []string{
"COPY . /app/",
"ENTRYPOINT [\"go\", \"run\", \".\"]",
"FROM golang:1.25-alpine AS builder",
"COPY . /build/",
"go build -o /app/mcp-server .",
"FROM alpine:3.20",
"COPY --from=builder --chown=appuser:appgroup /app/mcp-server /app/mcp-server",
"ENTRYPOINT [\"/app/mcp-server\"]",
},
wantMatches: []string{
`FROM golang:\d+\.\d+-alpine`, // Match any Go version
`FROM golang:\d+\.\d+-alpine AS builder`, // Match builder stage
`FROM alpine:\d+\.\d+`, // Match runtime stage
},
wantNotContains: []string{
"Add custom CA certificate",
Expand Down
Loading
Loading