Skip to content

Commit 33864d3

Browse files
authored
Dependency updates and docker build cleanup and optimization (#655)
* Update container images and all dependencies Signed-off-by: Mihai Criveti <[email protected]> * Update container images and all dependencies Signed-off-by: Mihai Criveti <[email protected]> * Make image smaller Signed-off-by: Mihai Criveti <[email protected]> * Make image smaller Signed-off-by: Mihai Criveti <[email protected]> * Make image smaller Signed-off-by: Mihai Criveti <[email protected]> * Precompile python bytecode Signed-off-by: Mihai Criveti <[email protected]> * Remove unnecessary micro image Signed-off-by: Mihai Criveti <[email protected]> * Update dockerignore Signed-off-by: Mihai Criveti <[email protected]> * Update dockerignore Signed-off-by: Mihai Criveti <[email protected]> * Cleanup pre-commit Signed-off-by: Mihai Criveti <[email protected]> * Cleanup pre-commit Signed-off-by: Mihai Criveti <[email protected]> * Cleanup pre-commit Signed-off-by: Mihai Criveti <[email protected]> --------- Signed-off-by: Mihai Criveti <[email protected]>
1 parent 1c048c7 commit 33864d3

File tree

9 files changed

+429
-143
lines changed

9 files changed

+429
-143
lines changed

.dockerignore

Lines changed: 267 additions & 96 deletions
Large diffs are not rendered by default.

Containerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
FROM registry.access.redhat.com/ubi9-minimal:9.6-1752587672
1+
FROM registry.access.redhat.com/ubi9-minimal:9.6-1754000177
22
LABEL maintainer="Mihai Criveti" \
33
name="mcp/mcpgateway" \
44
version="0.4.0" \

Containerfile.lite

Lines changed: 140 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -23,76 +23,183 @@ ARG ROOTFS_PATH=/tmp/rootfs
2323
# Python major.minor series to track
2424
ARG PYTHON_VERSION=3.11
2525

26-
###########################
27-
# Base image for copying into scratch
28-
###########################
29-
FROM registry.access.redhat.com/ubi9/ubi-micro:9.6-1752751762 AS base
30-
3126
###########################
3227
# Builder stage
3328
###########################
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"]
3631

3732
ARG PYTHON_VERSION
3833
ARG ROOTFS_PATH
3934

4035
# ----------------------------------------------------------------------------
4136
# 1) Patch the OS
4237
# 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
4541
# ----------------------------------------------------------------------------
4642
# hadolint ignore=DL3041
4743
RUN set -euo pipefail \
4844
&& dnf upgrade -y \
4945
&& dnf install -y \
5046
python${PYTHON_VERSION} \
5147
python${PYTHON_VERSION}-devel \
48+
binutils \
5249
&& update-alternatives --install /usr/bin/python3 python3 /usr/bin/python${PYTHON_VERSION} 1 \
5350
&& dnf clean all
5451

5552
WORKDIR /app
5653

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/
5959

6060
# ----------------------------------------------------------------------------
6161
# Create and populate virtual environment
6262
# - Upgrade pip, setuptools, wheel, pdm, uv
6363
# - 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
6566
# ----------------------------------------------------------------------------
6667
RUN set -euo pipefail \
6768
&& python3 -m venv /app/.venv \
6869
&& /app/.venv/bin/pip install --no-cache-dir --upgrade pip setuptools wheel pdm uv \
6970
&& /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
72105

73106
# ----------------------------------------------------------------------------
74107
# Build a minimal, fully-patched rootfs containing only the runtime Python
108+
# Include ca-certificates for HTTPS connections
75109
# ----------------------------------------------------------------------------
76110
# hadolint ignore=DL3041
77111
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 \
81115
--setopt=install_weak_deps=0 \
116+
--setopt=tsflags=nodocs \
82117
python${PYTHON_VERSION} \
83-
&& dnf clean all --installroot="${ROOTFS_PATH}"
118+
ca-certificates \
119+
&& dnf clean all --installroot="${ROOTFS_PATH:?}"
84120

85121
# ----------------------------------------------------------------------------
86122
# Create `python3` symlink in the rootfs for compatibility
87123
# ----------------------------------------------------------------------------
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
89194

90195
# ----------------------------------------------------------------------------
91196
# 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
92199
# ----------------------------------------------------------------------------
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
96203

97204
###########################
98205
# Final runtime (squashed)
@@ -118,8 +225,18 @@ COPY --from=builder ${ROOTFS_PATH}/ /
118225

119226
# ----------------------------------------------------------------------------
120227
# 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
121233
# ----------------------------------------------------------------------------
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
123240

124241
# ----------------------------------------------------------------------------
125242
# Application working directory

MANIFEST.in

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
# 1️⃣ Core project files that SDists/Wheels should always carry
66
include LICENSE
77
include README.md
8+
include plugins/README.md
89
include pyproject.toml
910
include gunicorn.config.py
1011
include Containerfile

Makefile

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1225,6 +1225,7 @@ container-build:
12251225
--tag $(IMAGE_BASE):$(IMAGE_TAG) \
12261226
.
12271227
@echo "✅ Built image: $(call get_image_name)"
1228+
$(CONTAINER_RUNTIME) images $(IMAGE_BASE):$(IMAGE_TAG)
12281229

12291230
container-run: container-check-image
12301231
@echo "🚀 Running with $(CONTAINER_RUNTIME)..."
@@ -1269,7 +1270,6 @@ container-run-ssl: certs container-check-image
12691270
-$(CONTAINER_RUNTIME) stop $(PROJECT_NAME) 2>/dev/null || true
12701271
-$(CONTAINER_RUNTIME) rm $(PROJECT_NAME) 2>/dev/null || true
12711272
$(CONTAINER_RUNTIME) run --name $(PROJECT_NAME) \
1272-
-u $(id -u):$(id -g) \
12731273
--env-file=.env \
12741274
-e SSL=true \
12751275
-e CERT_FILE=certs/cert.pem \
@@ -1290,7 +1290,6 @@ container-run-ssl-host: certs container-check-image
12901290
-$(CONTAINER_RUNTIME) stop $(PROJECT_NAME) 2>/dev/null || true
12911291
-$(CONTAINER_RUNTIME) rm $(PROJECT_NAME) 2>/dev/null || true
12921292
$(CONTAINER_RUNTIME) run --name $(PROJECT_NAME) \
1293-
-u $(id -u):$(id -g) \
12941293
--network=host \
12951294
--env-file=.env \
12961295
-e SSL=true \
@@ -1306,9 +1305,6 @@ container-run-ssl-host: certs container-check-image
13061305
@sleep 2
13071306
@echo "✅ Container started with TLS (host networking)"
13081307

1309-
1310-
1311-
13121308
container-push: container-check-image
13131309
@echo "📤 Preparing to push image..."
13141310
@# For Podman, we need to remove localhost/ prefix for push

docs/requirements.txt

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ CairoSVG>=2.8.2
99
certifi>=2025.7.14
1010
cffi>=1.17.1
1111
charset-normalizer>=3.4.2
12-
click>=8.2.1
12+
click>=8.2.2
1313
colorama>=0.4.6
1414
csscompressor>=0.9.5
1515
cssselect2>=0.8.0
@@ -42,7 +42,7 @@ mkdocs-git-authors-plugin>=0.10.0
4242
mkdocs-git-revision-date-localized-plugin>=1.4.7
4343
mkdocs-glightbox>=0.4.0
4444
mkdocs-include-markdown-plugin>=7.1.6
45-
mkdocs-material>=9.6.15
45+
mkdocs-material>=9.6.16
4646
mkdocs-material-extensions>=1.3.1
4747
mkdocs-mermaid-plugin>=0.1.1
4848
mkdocs-mermaid2-plugin>=1.2.1
@@ -53,7 +53,7 @@ mkdocs-rss-plugin>=1.17.3
5353
mkdocs-table-reader-plugin>=3.1.0
5454
mkdocs-with-pdf>=0.9.3
5555
natsort>=8.4.0
56-
numpy>=2.3.1
56+
numpy>=2.3.2
5757
nwdiag>=3.0.0
5858
packaging>=25.0
5959
paginate>=0.5.7
@@ -64,14 +64,14 @@ platformdirs>=4.3.8
6464
pycparser>=2.22
6565
pydyf>=0.11.0
6666
Pygments>=2.19.2
67-
pymdown-extensions>=10.16
67+
pymdown-extensions>=10.16.1
6868
pyparsing>=3.2.3
6969
pyphen>=0.17.2
7070
python-dateutil>=2.9.0.post0
7171
pytz>=2025.2
7272
PyYAML>=6.0.2
7373
pyyaml_env_tag>=1.1
74-
regex>=2024.11.6
74+
regex>=2025.7.34
7575
requests>=2.32.4
7676
seqdiag>=3.0.0
7777
six>=1.17.0
@@ -83,7 +83,7 @@ tzdata>=2025.2
8383
urllib3>=2.5.0
8484
watchdog>=6.0.0
8585
wcmatch>=10.1
86-
weasyprint>=65.1
86+
weasyprint>=66.0
8787
webcolors>=24.11.1
8888
webencodings>=0.5.1
8989
zipp>=3.23.0

mcpgateway/schemas.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2078,7 +2078,7 @@ def masked(self) -> "GatewayRead":
20782078
- The `auth_value` field is only masked if it exists and its value is different from the masking
20792079
placeholder.
20802080
- Other sensitive fields (`auth_password`, `auth_token`, `auth_header_value`) are masked if present.
2081-
- Fields not related to authentication remain unchanged.
2081+
- Fields not related to authentication remain unmodified.
20822082
"""
20832083
masked_data = self.model_dump()
20842084

plugins/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Plugins will go here..

0 commit comments

Comments
 (0)