@@ -23,76 +23,183 @@ ARG ROOTFS_PATH=/tmp/rootfs
23
23
# Python major.minor series to track
24
24
ARG PYTHON_VERSION=3.11
25
25
26
- ###########################
27
- # Base image for copying into scratch
28
- ###########################
29
- FROM registry.access.redhat.com/ubi9/ubi-micro:9.6-1752751762 AS base
30
-
31
26
###########################
32
27
# Builder stage
33
28
###########################
34
- FROM registry.access.redhat.com/ubi9/ubi:9.6-1752625787 AS builder
35
- SHELL ["/bin/bash", "-c"]
29
+ FROM registry.access.redhat.com/ubi9/ubi:9.6-1753978585 AS builder
30
+ SHELL ["/bin/bash", "-euo", "pipefail", "- c"]
36
31
37
32
ARG PYTHON_VERSION
38
33
ARG ROOTFS_PATH
39
34
40
35
# ----------------------------------------------------------------------------
41
36
# 1) Patch the OS
42
37
# 2) Install Python + headers for building wheels
43
- # 3) Register python3 alternative
44
- # 4) Clean caches to reduce layer size
38
+ # 3) Install binutils for strip command
39
+ # 4) Register python3 alternative
40
+ # 5) Clean caches to reduce layer size
45
41
# ----------------------------------------------------------------------------
46
42
# hadolint ignore=DL3041
47
43
RUN set -euo pipefail \
48
44
&& dnf upgrade -y \
49
45
&& dnf install -y \
50
46
python${PYTHON_VERSION} \
51
47
python${PYTHON_VERSION}-devel \
48
+ binutils \
52
49
&& update-alternatives --install /usr/bin/python3 python3 /usr/bin/python${PYTHON_VERSION} 1 \
53
50
&& dnf clean all
54
51
55
52
WORKDIR /app
56
53
57
- # Copy project source last so small code changes don't bust earlier caches
58
- COPY . /app
54
+ # ----------------------------------------------------------------------------
55
+ # Copy only the files needed for dependency installation first
56
+ # This maximizes Docker layer caching - dependencies change less often
57
+ # ----------------------------------------------------------------------------
58
+ COPY pyproject.toml /app/
59
59
60
60
# ----------------------------------------------------------------------------
61
61
# Create and populate virtual environment
62
62
# - Upgrade pip, setuptools, wheel, pdm, uv
63
63
# - Install project dependencies and package
64
- # - Remove build caches
64
+ # - Remove build tools but keep runtime dist-info
65
+ # - Remove build caches and build artifacts
65
66
# ----------------------------------------------------------------------------
66
67
RUN set -euo pipefail \
67
68
&& python3 -m venv /app/.venv \
68
69
&& /app/.venv/bin/pip install --no-cache-dir --upgrade pip setuptools wheel pdm uv \
69
70
&& /app/.venv/bin/uv pip install ".[redis,postgres]" \
70
- && /app/.venv/bin/pip uninstall --yes uv pip setuptools \
71
- && rm -rf /root/.cache /var/cache/dnf
71
+ && /app/.venv/bin/pip uninstall --yes uv pip setuptools wheel pdm \
72
+ && rm -rf /root/.cache /var/cache/dnf \
73
+ && find /app/.venv -name "*.dist-info" -type d \
74
+ \( -name "pip-*" -o -name "setuptools-*" -o -name "wheel-*" -o -name "pdm-*" -o -name "uv-*" \) \
75
+ -exec rm -rf {} + 2>/dev/null || true \
76
+ && rm -rf /app/.venv/share/python-wheels \
77
+ && rm -rf /app/*.egg-info /app/build /app/dist /app/.eggs
78
+
79
+ # ----------------------------------------------------------------------------
80
+ # Now copy only the application files needed for runtime
81
+ # This ensures code changes don't invalidate the dependency layer
82
+ # ----------------------------------------------------------------------------
83
+ COPY run-gunicorn.sh /app/
84
+ COPY mcpgateway/ /app/mcpgateway/
85
+ COPY gunicorn.config.py /app/
86
+ COPY plugins/ /app/plugins/
87
+
88
+ # Optional: Copy run.sh if it's needed in production
89
+ COPY run.sh /app/
90
+
91
+ # ----------------------------------------------------------------------------
92
+ # Ensure executable permissions for scripts
93
+ # ----------------------------------------------------------------------------
94
+ RUN chmod +x /app/run-gunicorn.sh /app/run.sh
95
+
96
+ # ----------------------------------------------------------------------------
97
+ # Pre-compile Python bytecode with -OO optimization
98
+ # - Strips docstrings and assertions
99
+ # - Improves startup performance
100
+ # - Must be done before copying to rootfs
101
+ # - Remove __pycache__ directories after compilation
102
+ # ----------------------------------------------------------------------------
103
+ RUN python3 -OO -m compileall -q /app/.venv /app/mcpgateway /app/plugins \
104
+ && find /app -type d -name "__pycache__" -exec rm -rf {} + 2>/dev/null || true
72
105
73
106
# ----------------------------------------------------------------------------
74
107
# Build a minimal, fully-patched rootfs containing only the runtime Python
108
+ # Include ca-certificates for HTTPS connections
75
109
# ----------------------------------------------------------------------------
76
110
# hadolint ignore=DL3041
77
111
RUN set -euo pipefail \
78
- && mkdir -p "${ROOTFS_PATH}" \
79
- && dnf --installroot="${ROOTFS_PATH}" --releasever=9 upgrade -y \
80
- && dnf --installroot="${ROOTFS_PATH}" --releasever=9 install -y \
112
+ && mkdir -p "${ROOTFS_PATH:? }" \
113
+ && dnf --installroot="${ROOTFS_PATH:? }" --releasever=9 upgrade -y \
114
+ && dnf --installroot="${ROOTFS_PATH:? }" --releasever=9 install -y \
81
115
--setopt=install_weak_deps=0 \
116
+ --setopt=tsflags=nodocs \
82
117
python${PYTHON_VERSION} \
83
- && dnf clean all --installroot="${ROOTFS_PATH}"
118
+ ca-certificates \
119
+ && dnf clean all --installroot="${ROOTFS_PATH:?}"
84
120
85
121
# ----------------------------------------------------------------------------
86
122
# Create `python3` symlink in the rootfs for compatibility
87
123
# ----------------------------------------------------------------------------
88
- RUN ln -s /usr/bin/python${PYTHON_VERSION} ${ROOTFS_PATH}/usr/bin/python3
124
+ RUN ln -s /usr/bin/python${PYTHON_VERSION} ${ROOTFS_PATH:?}/usr/bin/python3
125
+
126
+ # ----------------------------------------------------------------------------
127
+ # Clean up unnecessary files from rootfs (if they exist)
128
+ # - Remove development headers, documentation
129
+ # - Use ${var:?} to prevent accidental deletion of host directories
130
+ # ----------------------------------------------------------------------------
131
+ RUN set -euo pipefail \
132
+ && rm -rf ${ROOTFS_PATH:?}/usr/include/* \
133
+ ${ROOTFS_PATH:?}/usr/share/man/* \
134
+ ${ROOTFS_PATH:?}/usr/share/doc/* \
135
+ ${ROOTFS_PATH:?}/usr/share/info/* \
136
+ ${ROOTFS_PATH:?}/usr/share/locale/* \
137
+ ${ROOTFS_PATH:?}/var/log/* \
138
+ ${ROOTFS_PATH:?}/boot \
139
+ ${ROOTFS_PATH:?}/media \
140
+ ${ROOTFS_PATH:?}/srv \
141
+ ${ROOTFS_PATH:?}/usr/games \
142
+ && find ${ROOTFS_PATH:?}/usr/lib*/python*/ -type d -name "test" -exec rm -rf {} + 2>/dev/null || true \
143
+ && find ${ROOTFS_PATH:?}/usr/lib*/python*/ -type d -name "tests" -exec rm -rf {} + 2>/dev/null || true \
144
+ && find ${ROOTFS_PATH:?}/usr/lib*/python*/ -type d -name "idle_test" -exec rm -rf {} + 2>/dev/null || true \
145
+ && find ${ROOTFS_PATH:?}/usr/lib*/python*/ -name "*.mo" -delete 2>/dev/null || true \
146
+ && rm -rf ${ROOTFS_PATH:?}/usr/lib*/python*/ensurepip \
147
+ ${ROOTFS_PATH:?}/usr/lib*/python*/idlelib \
148
+ ${ROOTFS_PATH:?}/usr/lib*/python*/tkinter \
149
+ ${ROOTFS_PATH:?}/usr/lib*/python*/turtle* \
150
+ ${ROOTFS_PATH:?}/usr/lib*/python*/distutils/command/*.exe
151
+
152
+ # ----------------------------------------------------------------------------
153
+ # Remove package managers and unnecessary system tools from rootfs
154
+ # - Keep RPM database for security scanning with Trivy/Dockle
155
+ # - This keeps the final image size minimal while allowing vulnerability scanning
156
+ # ----------------------------------------------------------------------------
157
+ RUN rm -rf ${ROOTFS_PATH:?}/usr/bin/dnf* \
158
+ ${ROOTFS_PATH:?}/usr/bin/yum* \
159
+ ${ROOTFS_PATH:?}/usr/bin/rpm* \
160
+ ${ROOTFS_PATH:?}/usr/bin/microdnf \
161
+ ${ROOTFS_PATH:?}/usr/lib/rpm \
162
+ ${ROOTFS_PATH:?}/usr/lib/dnf \
163
+ ${ROOTFS_PATH:?}/usr/lib/yum* \
164
+ ${ROOTFS_PATH:?}/etc/dnf \
165
+ ${ROOTFS_PATH:?}/etc/yum*
166
+
167
+ # ----------------------------------------------------------------------------
168
+ # Strip unneeded symbols from shared libraries and remove binutils
169
+ # - This reduces the final image size and removes the build tool in one step
170
+ # ----------------------------------------------------------------------------
171
+ RUN find "${ROOTFS_PATH:?}/usr/lib64" -name '*.so*' -exec strip --strip-unneeded {} + 2>/dev/null || true \
172
+ && dnf remove -y binutils \
173
+ && dnf clean all
174
+
175
+ # ----------------------------------------------------------------------------
176
+ # Remove setuid/setgid binaries for security
177
+ # ----------------------------------------------------------------------------
178
+ RUN find ${ROOTFS_PATH:?} -perm /4000 -o -perm /2000 -type f -delete 2>/dev/null || true
179
+
180
+ # ----------------------------------------------------------------------------
181
+ # Create minimal passwd/group files for user 1001
182
+ # - Using GID 1001 to match UID for consistency
183
+ # - OpenShift compatible (accepts any UID in group 1001)
184
+ # ----------------------------------------------------------------------------
185
+ RUN printf 'app:x:1001:1001:app:/app:/sbin/nologin\n' > "${ROOTFS_PATH:?}/etc/passwd" && \
186
+ printf 'app:x:1001:\n' > "${ROOTFS_PATH:?}/etc/group"
187
+
188
+ # ----------------------------------------------------------------------------
189
+ # Create necessary directories in the rootfs
190
+ # - /tmp and /var/tmp with sticky bit for security
191
+ # ----------------------------------------------------------------------------
192
+ RUN chmod 1777 ${ROOTFS_PATH:?}/tmp ${ROOTFS_PATH:?}/var/tmp 2>/dev/null || true \
193
+ && chown 1001:1001 ${ROOTFS_PATH:?}/tmp ${ROOTFS_PATH:?}/var/tmp
89
194
90
195
# ----------------------------------------------------------------------------
91
196
# Copy application directory into the rootfs and fix permissions for non-root
197
+ # - Set ownership to 1001:1001 (matching passwd/group)
198
+ # - Allow group write permissions for OpenShift compatibility
92
199
# ----------------------------------------------------------------------------
93
- RUN cp -r /app ${ROOTFS_PATH}/app \
94
- && chown -R 1001:0 ${ROOTFS_PATH}/app \
95
- && chmod -R g=u ${ROOTFS_PATH}/app
200
+ RUN cp -r /app ${ROOTFS_PATH:? }/app \
201
+ && chown -R 1001:1001 ${ROOTFS_PATH:? }/app \
202
+ && chmod -R g=u ${ROOTFS_PATH:? }/app
96
203
97
204
###########################
98
205
# Final runtime (squashed)
@@ -118,8 +225,18 @@ COPY --from=builder ${ROOTFS_PATH}/ /
118
225
119
226
# ----------------------------------------------------------------------------
120
227
# Ensure our virtual environment binaries have priority in PATH
228
+ # - Don't write bytecode files (we pre-compiled with -OO)
229
+ # - Unbuffered output for better logging
230
+ # - Random hash seed for security
231
+ # - Disable pip cache to save space
232
+ # - Disable pip version check to reduce startup time
121
233
# ----------------------------------------------------------------------------
122
- ENV PATH="/app/.venv/bin:${PATH}"
234
+ ENV PATH="/app/.venv/bin:${PATH}" \
235
+ PYTHONDONTWRITEBYTECODE=1 \
236
+ PYTHONUNBUFFERED=1 \
237
+ PYTHONHASHSEED=random \
238
+ PIP_NO_CACHE_DIR=1 \
239
+ PIP_DISABLE_PIP_VERSION_CHECK=1
123
240
124
241
# ----------------------------------------------------------------------------
125
242
# Application working directory
0 commit comments