-
Notifications
You must be signed in to change notification settings - Fork 636
Expand file tree
/
Copy pathContainerfile.lite
More file actions
354 lines (314 loc) · 16.3 KB
/
Containerfile.lite
File metadata and controls
354 lines (314 loc) · 16.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
# syntax=docker/dockerfile:1.7
###############################################################################
# ContextForge (lite) - OCI-compliant container build
#
# This multi-stage Dockerfile produces a minimal runtime image using
# ubi10-minimal as the base, supporting multiplatform builds (amd64, arm64,
# s390x, ppc64le) including QEMU emulation for cross-platform builds.
#
# Key design points:
# - Builder stage has full DNF + devel headers for wheel compilation
# - Runtime stage uses ubi10-minimal for cross-platform compatibility
# - Development headers are dropped from the final image
# - Hadolint DL3041 is suppressed to allow "latest patch" RPM usage
###############################################################################
###########################
# Build-time arguments
###########################
# Python major.minor series to track
ARG PYTHON_VERSION=3.12
ARG ENABLE_RUST=false
ARG ENABLE_RUST_MCP_RMCP=false
# Enable profiling tools (memray, py-spy) - off by default for smaller images
# To enable: docker build --build-arg ENABLE_PROFILING=true -f Containerfile.lite .
# Usage after enabling:
# memray: docker exec -it <container> memray attach <PID> -o /tmp/profile.bin
# py-spy: sudo py-spy top --pid $(docker inspect --format '{{.State.Pid}}' <container>)
# Note: Container must have SYS_PTRACE capability for attaching to processes
# See docs/docs/development/profiling.md for detailed usage
ARG ENABLE_PROFILING=false
###############################################################################
# Rust builder stage - builds Rust plugins in manylinux2014 container
# To build WITH Rust: docker build --build-arg ENABLE_RUST=true -f Containerfile.lite .
# To build WITHOUT Rust (default): docker build -f Containerfile.lite .
###############################################################################
FROM quay.io/pypa/manylinux2014:2026.03.06-3 AS rust-builder-base
ARG ENABLE_RUST
ARG ENABLE_RUST_MCP_RMCP
# Set shell with pipefail for safety
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
# Only build if ENABLE_RUST=true
RUN if [ "$ENABLE_RUST" != "true" ]; then \
echo "⏭️ Rust builds disabled (set --build-arg ENABLE_RUST=true to enable)"; \
mkdir -p /build/rust-wheels /build/tools_rust/mcp_runtime/target/release; \
printf '#!/usr/bin/env sh\n' > /build/tools_rust/mcp_runtime/target/release/contextforge-mcp-runtime; \
printf 'echo "Rust MCP runtime not built into this image. Rebuild with --build-arg ENABLE_RUST=true." >&2\n' >> /build/tools_rust/mcp_runtime/target/release/contextforge-mcp-runtime; \
printf 'exit 1\n' >> /build/tools_rust/mcp_runtime/target/release/contextforge-mcp-runtime; \
chmod +x /build/tools_rust/mcp_runtime/target/release/contextforge-mcp-runtime; \
exit 0; \
fi
# Install Rust toolchain (only if ENABLE_RUST=true)
RUN if [ "$ENABLE_RUST" = "true" ]; then \
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain stable; \
fi
ENV PATH="/root/.cargo/bin:$PATH"
WORKDIR /build
# Copy only Rust plugin/runtime files (only if ENABLE_RUST=true)
COPY plugins_rust/ /build/plugins_rust/
COPY tools_rust/mcp_runtime/ /build/tools_rust/mcp_runtime/
# Build each Rust plugin independently using Python 3.12 from manylinux image
RUN if [ "$ENABLE_RUST" = "true" ]; then \
mkdir -p /build/rust-wheels && \
/opt/python/cp312-cp312/bin/python -m pip install --upgrade pip maturin && \
for plugin_dir in /build/plugins_rust/*/; do \
if [ -f "$plugin_dir/Cargo.toml" ]; then \
plugin_name=$(basename "$plugin_dir"); \
echo "🦀 Building Rust plugin: $plugin_name"; \
(cd "$plugin_dir" && /opt/python/cp312-cp312/bin/maturin build --release --compatibility manylinux2014 --out /build/rust-wheels) || exit 1; \
fi; \
done && \
echo "✅ Rust plugins built successfully"; \
else \
echo "⏭️ Skipping Rust plugin build"; \
fi
WORKDIR /build/tools_rust/mcp_runtime
# Build the experimental Rust MCP runtime binary (only if ENABLE_RUST=true)
RUN if [ "$ENABLE_RUST" = "true" ]; then \
if [ "$ENABLE_RUST_MCP_RMCP" = "true" ]; then \
cargo build --release --features rmcp-upstream-client; \
else \
cargo build --release; \
fi && \
cp target/release/contextforge_mcp_runtime target/release/contextforge-mcp-runtime && \
echo "✅ Rust MCP runtime built successfully"; \
else \
echo "⏭️ Skipping Rust MCP runtime build"; \
fi
FROM rust-builder-base AS rust-builder
###########################
# Builder stage
###########################
FROM registry.access.redhat.com/ubi10/ubi:10.1-1772441712 AS builder
SHELL ["/bin/bash", "-euo", "pipefail", "-c"]
ARG PYTHON_VERSION
ARG GRPC_PYTHON_BUILD_SYSTEM_OPENSSL='False'
# ----------------------------------------------------------------------------
# 1) Patch the OS
# 2) Install Python + headers for building wheels
# 3) Install binutils for strip command and curl for downloading CDN assets
# 4) Register python3 alternative
# 5) Clean caches to reduce layer size
# ----------------------------------------------------------------------------
# hadolint ignore=DL3041
RUN set -euo pipefail \
&& dnf upgrade -y \
&& dnf install -y \
python${PYTHON_VERSION} \
python${PYTHON_VERSION}-devel \
binutils openssl-devel gcc postgresql-devel gcc-c++ curl \
&& update-alternatives --install /usr/bin/python3 python3 /usr/bin/python${PYTHON_VERSION} 1 \
&& dnf clean all
WORKDIR /app
# ----------------------------------------------------------------------------
# s390x architecture does not support BoringSSL when building wheel grpcio.
# Force Python whl to use OpenSSL.
# NOTE: ppc64le has the same OpenSSL requirement
# ----------------------------------------------------------------------------
RUN if [ "$(uname -m)" = "s390x" ] || [ "$(uname -m)" = "ppc64le" ]; then \
echo "Building for $(uname -m)."; \
echo "export GRPC_PYTHON_BUILD_SYSTEM_OPENSSL='True'" > /etc/profile.d/use-openssl.sh; \
else \
echo "export GRPC_PYTHON_BUILD_SYSTEM_OPENSSL='False'" > /etc/profile.d/use-openssl.sh; \
fi
RUN chmod 644 /etc/profile.d/use-openssl.sh
# ----------------------------------------------------------------------------
# Copy only the files needed for dependency installation first
# This maximizes Docker layer caching - dependencies change less often
# ----------------------------------------------------------------------------
COPY pyproject.toml /app/
# ----------------------------------------------------------------------------
# Copy Rust plugin wheels from rust-builder stage (if any exist)
# ----------------------------------------------------------------------------
COPY --from=rust-builder /build/rust-wheels/ /tmp/rust-wheels/
COPY --from=rust-builder /build/tools_rust/mcp_runtime/target/release/contextforge-mcp-runtime /app/bin/contextforge-mcp-runtime
# ----------------------------------------------------------------------------
# Create and populate virtual environment
# - Upgrade pip, setuptools, wheel, pdm, uv
# - Install project dependencies and package
# - Include observability packages for OpenTelemetry support
# - Install Rust plugins from pre-built wheels (if built)
# - Optionally install profiling tools (memray, py-spy) if ENABLE_PROFILING=true
# - Remove build tools but keep runtime dist-info
# - Remove build caches and build artifacts
# ----------------------------------------------------------------------------
ARG ENABLE_RUST=false
ARG ENABLE_RUST_MCP_RMCP=false
ARG ENABLE_PROFILING=false
RUN set -euo pipefail \
&& . /etc/profile.d/use-openssl.sh \
&& python3 -m venv /app/.venv \
&& /app/.venv/bin/pip install --no-cache-dir --upgrade pip setuptools wheel pdm uv \
&& /app/.venv/bin/uv pip install ".[redis,postgres,mysql,observability,granian]" \
&& if [ "$ENABLE_RUST" = "true" ] && ls /tmp/rust-wheels/*.whl 1> /dev/null 2>&1; then \
echo "🦀 Installing Rust plugins..."; \
/app/.venv/bin/python3 -m pip install /tmp/rust-wheels/*.whl && \
/app/.venv/bin/python3 -c "from pii_filter_rust.pii_filter_rust import PIIDetectorRust; print('✓ Rust PII filter installed successfully')"; \
else \
echo "⏭️ Rust plugins not available - using Python implementations"; \
fi \
&& rm -rf /tmp/rust-wheels \
&& if [ "$ENABLE_PROFILING" = "true" ]; then \
echo "📊 Installing profiling tools (memray, py-spy)..."; \
/app/.venv/bin/pip install --no-cache-dir "memray>=1.17.0" && \
/app/.venv/bin/python3 -c "import memray; print('✓ memray installed successfully')"; \
else \
echo "⏭️ Profiling tools disabled (set --build-arg ENABLE_PROFILING=true to enable)"; \
fi \
&& /app/.venv/bin/pip uninstall --yes uv pip setuptools wheel pdm \
&& rm -rf /root/.cache /var/cache/dnf \
&& find /app/.venv -name "*.dist-info" -type d \
\( -name "pip-*" -o -name "setuptools-*" -o -name "wheel-*" -o -name "pdm-*" -o -name "uv-*" \) \
-exec rm -rf {} + 2>/dev/null || true \
&& rm -rf /app/.venv/share/python-wheels \
&& rm -rf /app/*.egg-info /app/build /app/dist /app/.eggs
# ----------------------------------------------------------------------------
# Now copy only the application files needed for runtime
# This ensures code changes don't invalidate the dependency layer
# ----------------------------------------------------------------------------
COPY run-gunicorn.sh run-granian.sh docker-entrypoint.sh /app/
COPY mcpgateway/ /app/mcpgateway/
COPY gunicorn.config.py /app/
COPY plugins/ /app/plugins/
COPY mcp-catalog.yml /app/
# Optional: Copy run.sh if it's needed in production
COPY run.sh /app/
# ----------------------------------------------------------------------------
# Download CDN assets for airgapped deployment
# This downloads external CSS/JS dependencies during build
# ----------------------------------------------------------------------------
COPY scripts/download-cdn-assets.sh /tmp/download-cdn-assets.sh
RUN chmod +x /tmp/download-cdn-assets.sh \
&& /tmp/download-cdn-assets.sh \
&& rm /tmp/download-cdn-assets.sh
# ----------------------------------------------------------------------------
# Ensure executable permissions for scripts
# ----------------------------------------------------------------------------
RUN chmod +x /app/run-gunicorn.sh /app/run-granian.sh /app/run.sh
# ----------------------------------------------------------------------------
# Pre-compile Python bytecode with -OO optimization
# - Strips docstrings and assertions
# - Improves startup performance
# - Remove __pycache__ directories after compilation
# ----------------------------------------------------------------------------
RUN python3 -OO -m compileall -q /app/.venv /app/mcpgateway /app/plugins \
&& find /app -type d -name "__pycache__" -exec rm -rf {} + 2>/dev/null || true
# ----------------------------------------------------------------------------
# Set ownership for non-root user (1001)
# - OpenShift compatible (accepts any UID in group 0)
# ----------------------------------------------------------------------------
RUN chown -R 1001:0 /app \
&& chmod -R g=u /app
###########################
# Final runtime stage
###########################
# Using ubi10-minimal for cross-platform compatibility (works with QEMU emulation)
# hadolint ignore=DL3006
FROM registry.access.redhat.com/ubi10/ubi-minimal:10.1-1772441549 AS runtime
ARG PYTHON_VERSION=3.12
ARG ENABLE_RUST=false
ARG ENABLE_RUST_MCP_RMCP=false
ARG ENABLE_PROFILING=false
# ----------------------------------------------------------------------------
# OCI image metadata
# ----------------------------------------------------------------------------
LABEL maintainer="Mihai Criveti" \
org.opencontainers.image.title="mcp/mcpgateway" \
org.opencontainers.image.description="ContextForge: An enterprise-ready Model Context Protocol Gateway" \
org.opencontainers.image.licenses="Apache-2.0" \
org.opencontainers.image.version="1.0.0-RC-2"
# ----------------------------------------------------------------------------
# Install minimal runtime dependencies
# - Python runtime (no devel packages)
# - ca-certificates for HTTPS
# - procps-ng for process management (ps command)
# - shadow-utils for useradd command
# - gdb for memray attach (only when ENABLE_PROFILING=true)
# ----------------------------------------------------------------------------
# hadolint ignore=DL3041
RUN microdnf upgrade -y --nodocs --setopt=install_weak_deps=0 \
&& microdnf install -y --nodocs --setopt=install_weak_deps=0 \
python${PYTHON_VERSION} \
ca-certificates \
procps-ng \
shadow-utils \
&& microdnf clean all \
&& rm -rf /var/cache/yum
# ----------------------------------------------------------------------------
# Install profiling dependencies (only when ENABLE_PROFILING=true)
# - gdb: Required for memray attach to running processes
# ----------------------------------------------------------------------------
# hadolint ignore=DL3041
RUN if [ "$ENABLE_PROFILING" = "true" ]; then \
echo "📊 Installing profiling dependencies (gdb)..."; \
microdnf install -y --nodocs --setopt=install_weak_deps=0 gdb && \
microdnf clean all && \
rm -rf /var/cache/yum && \
echo "✓ gdb installed for memray attach support"; \
fi
# ----------------------------------------------------------------------------
# Create python3 symlink
# ----------------------------------------------------------------------------
RUN ln -sf /usr/bin/python${PYTHON_VERSION} /usr/bin/python3
# ----------------------------------------------------------------------------
# Create non-root user
# ----------------------------------------------------------------------------
RUN useradd --uid 1001 --gid 0 --home-dir /app --shell /sbin/nologin --no-create-home --comment app app
# ----------------------------------------------------------------------------
# Copy the application from the builder stage
# ----------------------------------------------------------------------------
COPY --from=builder --chown=1001:0 /app /app
# ----------------------------------------------------------------------------
# Ensure our virtual environment binaries have priority in PATH
# - Don't write bytecode files (we pre-compiled with -OO)
# - Unbuffered output for better logging
# - Random hash seed for security
# - Disable pip cache to save space
# - Disable pip version check to reduce startup time
# ----------------------------------------------------------------------------
ENV PATH="/app/.venv/bin:${PATH}" \
CONTEXTFORGE_ENABLE_RUST_BUILD=${ENABLE_RUST} \
CONTEXTFORGE_ENABLE_RUST_MCP_RMCP_BUILD=${ENABLE_RUST_MCP_RMCP} \
PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1 \
PYTHONHASHSEED=random \
PIP_NO_CACHE_DIR=1 \
PIP_DISABLE_PIP_VERSION_CHECK=1
# ----------------------------------------------------------------------------
# Application working directory
# ----------------------------------------------------------------------------
WORKDIR /app
# ----------------------------------------------------------------------------
# Expose application port
# ----------------------------------------------------------------------------
EXPOSE 4444
# ----------------------------------------------------------------------------
# Run as non-root user (1001)
# ----------------------------------------------------------------------------
USER 1001
# ----------------------------------------------------------------------------
# Health check
# ----------------------------------------------------------------------------
HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \
CMD ["python3", "-c", "import httpx,sys;sys.exit(0 if httpx.get('http://localhost:4444/health',timeout=5).status_code==200 else 1)"]
# ----------------------------------------------------------------------------
# Entrypoint
# ----------------------------------------------------------------------------
# HTTP server selection via HTTP_SERVER environment variable:
# - gunicorn : Python-based with Uvicorn workers (default)
# - granian : Rust-based HTTP server (alternative)
#
# Examples:
# docker run -e HTTP_SERVER=gunicorn mcpgateway # Default
# docker run -e HTTP_SERVER=granian mcpgateway # Alternative
ENV HTTP_SERVER=gunicorn
CMD ["./docker-entrypoint.sh"]