Repository navigation
Expand file tree
/
Copy pathContainerfile
More file actions
468 lines (424 loc) · 22.5 KB
/
Copy pathContainerfile
File metadata and controls
468 lines (424 loc) · 22.5 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
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
# syntax=docker/dockerfile:1.12
###############################################################################
# ContextForge - OCI-compliant multi-stage container build
#
# Purpose: Minimal runtime image using ubi10-minimal, supporting multiplatform
# builds (amd64, arm64, s390x, ppc64le).
#
# This is the only production Containerfile (previously separate lite/scratch
# variants were consolidated here).
#
# 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
# Enable HTMX Admin UI and Node.js frontend build steps.
# Set false for s390x/ppc64le where @tailwindcss/oxide and lightningcss
# have no prebuilt native binaries; those arches run with MCPGATEWAY_UI_ENABLED=false.
ARG MCPGATEWAY_UI_ENABLED=true
# Enable profiling tools (memray, py-spy) - off by default for smaller images
# To enable: docker build --build-arg ENABLE_PROFILING=true -f Containerfile .
# 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
# Base image overrides — defaults to UBI 10; pass UBI 9 values for FedRAMP builds
#
# FedRAMP/Dreadnought deployments MUST override these with images pulled from
# an approved internal registry.
# Public registry defaults (registry.access.redhat.com) are for standard builds only.
#
# Example (Dreadnought):
# docker build -f Containerfile \
# --build-arg ENABLE_FIPS=true \
# --build-arg UBI_BASE=<internal-registry>/ubi9/ubi:latest \
# --build-arg NODEJS_IMAGE=<internal-registry>/ubi9/nodejs-20:latest \
# --build-arg UBI_MINIMAL=<internal-registry>/ubi9/ubi-minimal:latest \
# .
ARG UBI_BASE=registry.access.redhat.com/ubi10:10.2-1792744468
ARG NODEJS_IMAGE=registry.access.redhat.com/ubi10/nodejs-24:10.2-1792840089
ARG UBI_MINIMAL=registry.access.redhat.com/ubi10/ubi-minimal:10.2-1792742351
# Wheel closure stage — used only for s390x and ppc64le where PyPI manylinux
# binary wheels are unavailable (tiktoken/psycopg/cryptography require native
# compilation, and psycopg-binary has no s390x wheel at all).
# For amd64/arm64, WHEELS_REF defaults to UBI_MINIMAL so /wheels is always
# empty and the install step below always resolves via PyPI.
# For s390x/ppc64le, the CI pipeline (docker-multiplatform.yml) passes an
# explicit WHEELS_REF pointing to the lock-hash-tagged wheel image built by
# the `wheels` job in infra/wheels/Containerfile.
ARG WHEELS_REF=${UBI_MINIMAL}
FROM ${WHEELS_REF} AS wheels
RUN mkdir -p /wheels
###############################################################################
# Node.js builder stage - builds Tailwind CSS
###############################################################################
# Use official Red Hat UBI10 Node.js 24 image - published for amd64, arm64,
# s390x and ppc64le, so this stage builds natively on every target platform.
FROM ${NODEJS_IMAGE} AS node-builder
ARG MCPGATEWAY_UI_ENABLED=true
USER root
RUN mkdir -p /build && chown 1001:0 /build && chmod g=u /build
USER 1001
WORKDIR /build
# Copy only files needed for CSS build (with proper ownership for non-root user)
COPY --chown=1001:1001 package.json package-lock.json* ./
COPY --chown=1001:1001 postcss.config.js ./
COPY --chown=1001:1001 mcpgateway/templates/ ./mcpgateway/templates/
COPY --chown=1001:1001 mcpgateway/static/ ./mcpgateway/static/
# Install dependencies and build CSS.
# When MCPGATEWAY_UI_ENABLED=false (s390x/ppc64le), skip npm ci and produce a zero-byte
# stub so the COPY --from=node-builder in the builder stage always succeeds.
RUN if [ "$MCPGATEWAY_UI_ENABLED" = "true" ]; then \
npm ci && \
npm run build:css && \
echo "✅ Tailwind CSS built successfully"; \
else \
mkdir -p /build/mcpgateway/static/css && \
touch /build/mcpgateway/static/css/tailwind.min.css && \
echo "⏭️ UI build skipped (MCPGATEWAY_UI_ENABLED=false)"; \
fi
###########################
# Frontend builder stage
###########################
FROM ${NODEJS_IMAGE} AS frontend-builder
ARG MCPGATEWAY_UI_ENABLED=true
WORKDIR /opt/app-root/src
# Copy package.json and package-lock.json
COPY --chown=10001:10001 package.json package-lock.json ./
# Install frontend dependencies and run Vite build.
# When MCPGATEWAY_UI_ENABLED=false (s390x/ppc64le), skip npm ci and produce an empty
# static directory so the COPY --from=frontend-builder in the builder stage
# always succeeds.
RUN if [ "$MCPGATEWAY_UI_ENABLED" = "true" ]; then \
npm ci && \
echo "✅ Frontend dependencies installed"; \
else \
mkdir -p mcpgateway/static && \
echo "⏭️ Frontend install skipped (MCPGATEWAY_UI_ENABLED=false)"; \
fi
# Create directory structure with correct ownership before Vite build
USER root
RUN mkdir -p mcpgateway/static && chown -R 10001:10001 mcpgateway
USER 10001
# Copy frontend source files
COPY --chown=10001:10001 mcpgateway/admin_ui/ mcpgateway/admin_ui/
COPY --chown=10001:10001 vite.config.js ./
# Run Vite build
RUN if [ "$MCPGATEWAY_UI_ENABLED" = "true" ]; then \
npm run vite:build && \
echo "✅ Vite build complete"; \
else \
echo "⏭️ Vite build skipped (MCPGATEWAY_UI_ENABLED=false)"; \
fi
###########################
# Builder stage
###########################
FROM ${UBI_BASE} 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 --allowerasing \
python${PYTHON_VERSION} \
python${PYTHON_VERSION}-devel \
binutils openssl-devel gcc postgresql-devel gcc-c++ curl libpq-devel git \
&& 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 \
&& chmod 644 /etc/profile.d/use-openssl.sh
# ----------------------------------------------------------------------------
# Copy only the files needed for dependency installation first.
# Order is top-to-bottom from least-frequently-changing to most:
# 1. pyproject.toml (dependency manifest — plugins come from the [plugins]
# extra, resolved from PyPI)
# 2. The setuptools build_py hook module (pyproject.toml's
# [tool.setuptools.cmdclass] points at mcpgateway.tools.builder.build_hooks,
# so setuptools must be able to import it while resolving "."'s build
# metadata below — copy just this module's import chain, not the rest of
# mcpgateway/, to keep the dep layer cache intact for normal source edits)
# Everything else (frontend static, app code) is copied AFTER the heavy venv
# install so those changes don't invalidate the dep layer.
# ----------------------------------------------------------------------------
COPY pyproject.toml /app/
COPY mcpgateway/__init__.py /app/mcpgateway/__init__.py
COPY mcpgateway/tools/builder/__init__.py /app/mcpgateway/tools/builder/__init__.py
COPY mcpgateway/tools/builder/build_hooks.py /app/mcpgateway/tools/builder/build_hooks.py
COPY --from=wheels /wheels /tmp/wheels
# ----------------------------------------------------------------------------
# Create and populate virtual environment — split into discrete RUN steps so a
# failure names the exact stage in the build log:
# 1. Create venv; upgrade pip, setuptools, wheel, uv
# 2. Install project dependencies and package (incl. observability and
# plugins from PyPI via the [plugins] extra)
# 3. Drop consumed wheel staging area
# 4. Optionally install profiling tools (memray, py-spy) if ENABLE_PROFILING=true
# 5. Remove build tools (keep runtime dist-info), caches, and artifacts
# ----------------------------------------------------------------------------
ARG ENABLE_PROFILING=false
ARG MCPGATEWAY_UI_ENABLED=true
# Step 1: Create the virtualenv and bootstrap pip/setuptools/wheel/uv.
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 uv
# Step 2: Install project dependencies and the package itself.
# Hermetic path (CI) installs from the prebuilt wheel closure in /tmp/wheels;
# otherwise resolve from PyPI. GRPC_PYTHON_BUILD_SYSTEM_OPENSSL (sourced via
# use-openssl.sh) must be active here in case grpcio falls back to a source build.
RUN set -euo pipefail \
&& . /etc/profile.d/use-openssl.sh \
&& if [ -n "$(ls -A /tmp/wheels/*.whl 2>/dev/null)" ]; then \
echo "📦 Hermetic install from prebuilt wheel closure"; \
/app/.venv/bin/uv pip install --no-index --find-links=/tmp/wheels ".[redis,observability,plugins,llmchat]" "psycopg[c]>=3.3.3"; \
else \
/app/.venv/bin/uv pip install ".[redis,postgres,observability,plugins,llmchat]"; \
fi \
&& echo "✅ Plugins installed from PyPI via [plugins] extra"
# Step 3: Wheel staging area is consumed — drop it before profiling so a
# profiling failure can never shadow a wheel-install failure (and vice versa).
RUN rm -rf /tmp/wheels
# Step 4: Optionally install profiling tools (memray, py-spy).
RUN set -euo pipefail \
&& if [ "$ENABLE_PROFILING" = "true" ]; then \
echo "📊 Installing profiling tools (memray, py-spy)..."; \
/app/.venv/bin/uv 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
# Step 5: Remove build tools (keep runtime dist-info for other packages),
# build caches, and build artifacts.
RUN set -euo pipefail \
&& /app/.venv/bin/pip uninstall --yes uv pip setuptools wheel \
&& 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 "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
# ----------------------------------------------------------------------------
# Copy frontend static output from its builder stages AFTER the venv install
# so those changes don't invalidate the heavy dependency layer above.
# ----------------------------------------------------------------------------
# Copy frontend static assets from node-builder (Tailwind CSS) and
# frontend-builder (Vite output). Both builder stages always produce their
# target paths — either real build output or empty stubs — so these COPYs
# succeed regardless of MCPGATEWAY_UI_ENABLED. The RUN guard below removes the stubs
# when MCPGATEWAY_UI_ENABLED=false to keep the final image clean.
COPY --from=frontend-builder /opt/app-root/src/mcpgateway/static/ /app/mcpgateway/static/
COPY --from=node-builder /build/mcpgateway/static/css/tailwind.min.css /app/mcpgateway/static/css/
# When UI is disabled, drop the stub CSS file so the runtime image contains
# no misleading zero-byte asset.
RUN if [ "$MCPGATEWAY_UI_ENABLED" != "true" ]; then \
rm -f /app/mcpgateway/static/css/tailwind.min.css && \
echo "⏭️ UI static assets removed (MCPGATEWAY_UI_ENABLED=false)"; \
fi
# Optional: Copy run.sh if it's needed in production
COPY run.sh /app/
# ----------------------------------------------------------------------------
# Application files, ordered from least- to most-frequently-changing so that
# the more volatile COPYs at the bottom don't invalidate the stable ones above.
# - Run/entrypoint scripts (rarely change) [--chmod=0755 keeps +x]
# - gunicorn config + mcp catalog (rare/occasional)
# - mcpgateway/ + plugins/ (change most often)
# ----------------------------------------------------------------------------
COPY --chmod=0755 run-gunicorn.sh docker-entrypoint.sh run.sh /app/
COPY gunicorn.config.py mcp-catalog.yml /app/
COPY mcpgateway/ /app/mcpgateway/
COPY plugins/ /app/plugins/
# ----------------------------------------------------------------------------
# Final pass: compile bytecode with -OO, strip caches, fix OpenShift-compatible
# ownership. Merged into one layer — all of these are cheap and all get
# invalidated whenever any app code changes above.
# ----------------------------------------------------------------------------
RUN python3 -OO -m compileall -x 'cpex/templates' -q /app/.venv /app/mcpgateway /app/plugins \
&& find /app -type d -name "__pycache__" -exec rm -rf {} + 2>/dev/null || true \
&& chown -R 10001:10001 /app
###########################
# Final runtime stage
###########################
# Runtime base: UBI 10 by default; override UBI_MINIMAL build-arg to ubi9/ubi-minimal:latest for FedRAMP builds
# hadolint ignore=DL3006
FROM ${UBI_MINIMAL} AS runtime
ARG ENABLE_FIPS=false
ARG PYTHON_VERSION=3.12
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.1.0"
# ----------------------------------------------------------------------------
# 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)
# ----------------------------------------------------------------------------
# Install runtime deps, conditionally add gdb for profiling, create the python3
# symlink and non-root user — all merged into a single layer since these are
# stable setup steps that rarely change.
# 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 \
openssl \
procps-ng \
shadow-utils \
libpq \
&& if [ "$ENABLE_PROFILING" = "true" ]; then \
microdnf install -y --nodocs --setopt=install_weak_deps=0 gdb; \
fi \
&& microdnf clean all \
&& rm -rf /var/cache/yum \
\
# Revoke emSign/eMudhra CA roots. The ca-certificates RPM ships them in a
# monolithic bundle; copying their PEM certs into the blocklist dir and
# re-running update-ca-trust is the reliable removal mechanism on UBI10.
&& BLOCKLIST=/usr/share/pki/ca-trust-source/blocklist \
&& csplit -z -f /tmp/cacert- /etc/pki/tls/certs/ca-bundle.crt \
"/-----BEGIN CERTIFICATE-----/" "{*}" 2>/dev/null \
&& for f in /tmp/cacert-*; do \
subj=$(openssl x509 -in "$f" -noout -subject 2>/dev/null); \
echo "$subj" | grep -qi "emsign\|emudhra" || continue; \
slug=$(echo "$subj" | sed 's/.*CN=//' | tr ' ' '-' | tr '[:upper:]' '[:lower:]' | tr -cd 'a-z0-9-'); \
cp "$f" "${BLOCKLIST}/${slug}.pem"; \
done \
&& rm -f /tmp/cacert-* \
&& update-ca-trust extract \
&& if openssl crl2pkcs7 -nocrl -certfile /etc/pki/tls/certs/ca-bundle.crt \
| openssl pkcs7 -print_certs -noout 2>/dev/null \
| grep -qi "emsign\|emudhra"; then \
echo "ERROR: emSign/eMudhra root still present in ca-bundle.crt after update-ca-trust" >&2; \
exit 1; \
fi \
\
&& ln -sf /usr/bin/python${PYTHON_VERSION} /usr/bin/python3 \
&& groupadd --gid 10001 app \
&& useradd --uid 10001 --gid 10001 --home-dir /app --shell /sbin/nologin --no-create-home --comment app app
# ----------------------------------------------------------------------------
# Copy the application from the builder stage
# ----------------------------------------------------------------------------
COPY --from=builder --chown=10001:10001 /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}" \
PYTHONPATH="/app" \
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
# hadolint ignore=DL3041
# FedRAMP compliance block — only active when ENABLE_FIPS=true
# Resolves: FIPS:STIG crypto policy (RHEL-09-215105/672030), SSH ciphers/MACs,
# gnutls-utils (RHEL-09-215080), nss-tools (RHEL-09-215085),
# subscription-manager (RHEL-09-215010), pam_wheel (RHEL-09-432035),
# init file perms 0740 (RHEL-09-232045), home dir perms 0750 (RHEL-09-232050),
# rootfiles tmpfile.d (RHEL-09-232045), SSH RekeyLimit
#
# NOTE: subscription-manager pulls in UBI9's system python3 (3.9) as a
# dependency, which overwrites /usr/bin/python3 and clobbers the
# python3 -> python${PYTHON_VERSION} symlink set up earlier — surfacing at
# runtime as a misleading "ModuleNotFoundError: No module named 'gunicorn'".
# We re-create the symlink right after the package install below.
RUN if [ "$ENABLE_FIPS" = "true" ]; then \
if ! grep -q "release 9" /etc/redhat-release 2>/dev/null; then \
echo "ERROR: ENABLE_FIPS=true requires UBI 9 base images (UBI_MINIMAL must be ubi9/ubi-minimal)" >&2; \
exit 1; \
fi; \
microdnf install -y crypto-policies crypto-policies-scripts rootfiles \
gnutls-utils nss-tools subscription-manager \
&& microdnf clean all \
&& ln -sf /usr/bin/python${PYTHON_VERSION} /usr/bin/python3 \
&& (test -f /usr/share/crypto-policies/policies/modules/STIG.pmod \
|| printf '# STIG module stub — not shipped in UBI9 minimal\n' \
> /usr/share/crypto-policies/policies/modules/STIG.pmod) \
&& update-crypto-policies --set FIPS:STIG \
&& mkdir -p /etc/ssh/ssh_config.d /etc/tmpfiles.d /usr/lib/tmpfiles.d /usr/share/rootfiles \
&& echo "RekeyLimit 512M 1h" > /etc/ssh/ssh_config.d/02-rekey-limit.conf \
&& if [ -f /etc/pam.d/su ]; then \
grep -Eq '^[[:space:]]*auth[[:space:]]+required[[:space:]]+pam_wheel\.so([[:space:]]|$)' /etc/pam.d/su \
|| echo 'auth required pam_wheel.so use_uid' >> /etc/pam.d/su; \
fi \
&& cp -p /root/.bash_logout /root/.bash_profile /root/.bashrc /root/.cshrc /root/.tcshrc \
/usr/share/rootfiles/ \
&& printf '%s\n' \
'C /root/.bash_logout 600 root root - /usr/share/rootfiles/.bash_logout' \
'C /root/.bash_profile 600 root root - /usr/share/rootfiles/.bash_profile' \
'C /root/.bashrc 600 root root - /usr/share/rootfiles/.bashrc' \
'C /root/.cshrc 600 root root - /usr/share/rootfiles/.cshrc' \
'C /root/.tcshrc 600 root root - /usr/share/rootfiles/.tcshrc' \
| tee /usr/lib/tmpfiles.d/rootfiles.conf > /etc/tmpfiles.d/rootfiles.conf \
&& find /root -maxdepth 1 -name '.*' -type f -exec chmod 0740 {} \; \
&& chmod 0750 /root \
&& (chgrp 0 /app 2>/dev/null || true) \
&& (chmod 0750 /app 2>/dev/null || true) \
&& (find /app -maxdepth 1 -name '.*' -type f -exec chmod 0740 {} \; 2>/dev/null || true) \
&& find /home -maxdepth 1 -mindepth 1 -type d -exec chmod 0750 {} \; \
&& find /home -maxdepth 2 -name '.*' -type f -exec chmod 0740 {} \;; \
else \
echo "ENABLE_FIPS=false — skipping FedRAMP compliance block"; \
fi
# ----------------------------------------------------------------------------
# Run as non-root user (10001:10001)
# ----------------------------------------------------------------------------
USER 10001:10001
# ----------------------------------------------------------------------------
# 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: Gunicorn with Uvicorn workers
CMD ["./docker-entrypoint.sh"]