forked from IBM/mcp-context-forge
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathContainerfile.lite
More file actions
267 lines (236 loc) · 12.2 KB
/
Copy pathContainerfile.lite
File metadata and controls
267 lines (236 loc) · 12.2 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
# syntax=docker/dockerfile:1.7
###############################################################################
# MCP Gateway (lite) - OCI-compliant container build
#
# This multi-stage Dockerfile produces an ultra-slim, scratch-based runtime
# image that automatically tracks the latest Python 3.12.x patch release
# from the RHEL 10 repositories and is fully patched on each rebuild.
#
# Key design points:
# - Builder stage has full DNF + devel headers for wheel compilation
# - Runtime stage is scratch: only the Python runtime and app
# - Both builder and runtime rootfs receive `dnf upgrade -y`
# - Development headers are dropped from the final image
# - Hadolint DL3041 is suppressed to allow "latest patch" RPM usage
###############################################################################
###########################
# Build-time arguments
###########################
# Temporary dir for assembling the scratch rootfs
ARG ROOTFS_PATH=/tmp/rootfs
# Python major.minor series to track
ARG PYTHON_VERSION=3.12
###########################
# Builder stage
###########################
FROM registry.access.redhat.com/ubi10/ubi:10.0-1756805986 AS builder
SHELL ["/bin/bash", "-euo", "pipefail", "-c"]
ARG PYTHON_VERSION
ARG ROOTFS_PATH
# ----------------------------------------------------------------------------
# 1) Patch the OS
# 2) Install Python + headers for building wheels
# 3) Install binutils for strip command
# 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 \
&& update-alternatives --install /usr/bin/python3 python3 /usr/bin/python${PYTHON_VERSION} 1 \
&& dnf clean all
WORKDIR /app
# ----------------------------------------------------------------------------
# Copy only the files needed for dependency installation first
# This maximizes Docker layer caching - dependencies change less often
# ----------------------------------------------------------------------------
COPY pyproject.toml /app/
# ----------------------------------------------------------------------------
# Create and populate virtual environment
# - Upgrade pip, setuptools, wheel, pdm, uv
# - Install project dependencies and package
# - Include observability packages for OpenTelemetry support
# - Remove build tools but keep runtime dist-info
# - Remove build caches and build artifacts
# ----------------------------------------------------------------------------
RUN set -euo pipefail \
&& 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]" \
&& /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 /app/
COPY mcpgateway/ /app/mcpgateway/
COPY gunicorn.config.py /app/
COPY plugins/ /app/plugins/
# Optional: Copy run.sh if it's needed in production
COPY run.sh /app/
# ----------------------------------------------------------------------------
# Ensure executable permissions for scripts
# ----------------------------------------------------------------------------
RUN chmod +x /app/run-gunicorn.sh /app/run.sh
# ----------------------------------------------------------------------------
# Pre-compile Python bytecode with -OO optimization
# - Strips docstrings and assertions
# - Improves startup performance
# - Must be done before copying to rootfs
# - 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
# ----------------------------------------------------------------------------
# Build a minimal, fully-patched rootfs containing only the runtime Python
# Include ca-certificates for HTTPS connections
# ----------------------------------------------------------------------------
# hadolint ignore=DL3041
RUN set -euo pipefail \
&& mkdir -p "${ROOTFS_PATH:?}" \
&& dnf --installroot="${ROOTFS_PATH:?}" --releasever=10 upgrade -y \
&& dnf --installroot="${ROOTFS_PATH:?}" --releasever=10 install -y \
--setopt=install_weak_deps=0 \
--setopt=tsflags=nodocs \
python${PYTHON_VERSION} \
ca-certificates \
procps-ng \
&& dnf clean all --installroot="${ROOTFS_PATH:?}"
# ----------------------------------------------------------------------------
# Create `python3` symlink in the rootfs for compatibility
# ----------------------------------------------------------------------------
RUN ln -sf /usr/bin/python${PYTHON_VERSION} ${ROOTFS_PATH:?}/usr/bin/python3
# ----------------------------------------------------------------------------
# Clean up unnecessary files from rootfs (if they exist)
# - Remove development headers, documentation
# - Use ${var:?} to prevent accidental deletion of host directories
# ----------------------------------------------------------------------------
RUN set -euo pipefail \
&& rm -rf ${ROOTFS_PATH:?}/usr/include/* \
${ROOTFS_PATH:?}/usr/share/man/* \
${ROOTFS_PATH:?}/usr/share/doc/* \
${ROOTFS_PATH:?}/usr/share/info/* \
${ROOTFS_PATH:?}/usr/share/locale/* \
${ROOTFS_PATH:?}/var/log/* \
${ROOTFS_PATH:?}/boot \
${ROOTFS_PATH:?}/media \
${ROOTFS_PATH:?}/srv \
${ROOTFS_PATH:?}/usr/games \
&& find ${ROOTFS_PATH:?}/usr/lib*/python*/ -type d -name "test" -exec rm -rf {} + 2>/dev/null || true \
&& find ${ROOTFS_PATH:?}/usr/lib*/python*/ -type d -name "tests" -exec rm -rf {} + 2>/dev/null || true \
&& find ${ROOTFS_PATH:?}/usr/lib*/python*/ -type d -name "idle_test" -exec rm -rf {} + 2>/dev/null || true \
&& find ${ROOTFS_PATH:?}/usr/lib*/python*/ -name "*.mo" -delete 2>/dev/null || true \
&& rm -rf ${ROOTFS_PATH:?}/usr/lib*/python*/ensurepip \
${ROOTFS_PATH:?}/usr/lib*/python*/idlelib \
${ROOTFS_PATH:?}/usr/lib*/python*/tkinter \
${ROOTFS_PATH:?}/usr/lib*/python*/turtle* \
${ROOTFS_PATH:?}/usr/lib*/python*/distutils/command/*.exe
# ----------------------------------------------------------------------------
# Remove package managers and unnecessary system tools from rootfs
# - Keep RPM database for security scanning with Trivy/Dockle
# - This keeps the final image size minimal while allowing vulnerability scanning
# ----------------------------------------------------------------------------
RUN rm -rf ${ROOTFS_PATH:?}/usr/bin/dnf* \
${ROOTFS_PATH:?}/usr/bin/yum* \
${ROOTFS_PATH:?}/usr/bin/rpm* \
${ROOTFS_PATH:?}/usr/bin/microdnf \
${ROOTFS_PATH:?}/usr/lib/rpm \
${ROOTFS_PATH:?}/usr/lib/dnf \
${ROOTFS_PATH:?}/usr/lib/yum* \
${ROOTFS_PATH:?}/etc/dnf \
${ROOTFS_PATH:?}/etc/yum*
# ----------------------------------------------------------------------------
# Strip unneeded symbols from shared libraries and remove binutils
# - This reduces the final image size and removes the build tool in one step
# ----------------------------------------------------------------------------
RUN find "${ROOTFS_PATH:?}/usr/lib64" -name '*.so*' -exec strip --strip-unneeded {} + 2>/dev/null || true \
&& dnf remove -y binutils \
&& dnf clean all
# ----------------------------------------------------------------------------
# Remove setuid/setgid binaries for security
# ----------------------------------------------------------------------------
RUN find ${ROOTFS_PATH:?} -perm /4000 -o -perm /2000 -type f -delete 2>/dev/null || true
# ----------------------------------------------------------------------------
# Create minimal passwd/group files for user 1001
# - Using GID 1001 to match UID for consistency
# - OpenShift compatible (accepts any UID in group 1001)
# ----------------------------------------------------------------------------
RUN printf 'app:x:1001:1001:app:/app:/sbin/nologin\n' > "${ROOTFS_PATH:?}/etc/passwd" && \
printf 'app:x:1001:\n' > "${ROOTFS_PATH:?}/etc/group"
# ----------------------------------------------------------------------------
# Create necessary directories in the rootfs
# - /tmp and /var/tmp with sticky bit for security
# ----------------------------------------------------------------------------
RUN chmod 1777 ${ROOTFS_PATH:?}/tmp ${ROOTFS_PATH:?}/var/tmp 2>/dev/null || true \
&& chown 1001:1001 ${ROOTFS_PATH:?}/tmp ${ROOTFS_PATH:?}/var/tmp
# ----------------------------------------------------------------------------
# Copy application directory into the rootfs and fix permissions for non-root
# - Set ownership to 1001:1001 (matching passwd/group)
# - Allow group write permissions for OpenShift compatibility
# ----------------------------------------------------------------------------
RUN cp -r /app ${ROOTFS_PATH:?}/app \
&& chown -R 1001:1001 ${ROOTFS_PATH:?}/app \
&& chmod -R g=u ${ROOTFS_PATH:?}/app
###########################
# Final runtime (squashed)
###########################
FROM scratch AS runtime
ARG PYTHON_VERSION
ARG ROOTFS_PATH
# ----------------------------------------------------------------------------
# OCI image metadata
# ----------------------------------------------------------------------------
LABEL maintainer="Mihai Criveti" \
org.opencontainers.image.title="mcp/mcpgateway" \
org.opencontainers.image.description="MCP Gateway: An enterprise-ready Model Context Protocol Gateway" \
org.opencontainers.image.licenses="Apache-2.0" \
org.opencontainers.image.version="0.7.0"
# ----------------------------------------------------------------------------
# Copy the entire prepared root filesystem from the builder stage
# ----------------------------------------------------------------------------
COPY --from=builder ${ROOTFS_PATH}/ /
# ----------------------------------------------------------------------------
# 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}" \
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
# ----------------------------------------------------------------------------
CMD ["./run-gunicorn.sh"]