# The release images use immutable base-image digests. Renovation is explicit: # update the patch version and digest together, rebuild both architectures, and # run the complete test/smoke suite before publishing a new semver tag. FROM node:22.23.2-bookworm-slim@sha256:d649c27dae7ba0137b3cef5dd75baa422c08dc3d9e3fc0c23dfb172dc3cc6436 AS ui-build WORKDIR /build/ui COPY services/memory-gateway/ui/package.json services/memory-gateway/ui/package-lock.json ./ RUN npm ci --ignore-scripts --no-audit --no-fund COPY services/memory-gateway/ui/ ./ RUN npm run build FROM python:3.12.13-slim-bookworm@sha256:4766d8b510c428e595d74b9cc5bbb2fae8e26316fffb4adc89908d79aacd58a2 AS python-wheelhouse ENV PIP_DISABLE_PIP_VERSION_CHECK=1 \ PIP_NO_CACHE_DIR=1 \ PYTHONDONTWRITEBYTECODE=1 WORKDIR /build # Download the union dependency graph once, with hashes enforced, into an # offline wheelhouse. The three runtime environments below resolve only their # own project wheels from this trusted artifact set; they do not clone a shared # union venv into every image. COPY requirements-runtime.lock ./requirements-runtime.lock RUN mkdir -p /wheelhouse \ && python -m pip download \ --require-hashes --only-binary=:all: \ --dest /wheelhouse \ -r requirements-runtime.lock COPY packages/model-gateway-contracts/pyproject.toml ./model-gateway-contracts/pyproject.toml COPY packages/model-gateway-contracts/model_gateway_contracts ./model-gateway-contracts/model_gateway_contracts COPY services/memory-gateway/pyproject.toml services/memory-gateway/README.md ./memory-gateway/ COPY services/memory-gateway/app ./memory-gateway/app COPY services/model-gateway/pyproject.toml services/model-gateway/README.md ./model-gateway/ COPY services/model-gateway/model_gateway ./model-gateway/model_gateway RUN python -m venv /opt/build-venv \ && /opt/build-venv/bin/pip install \ --no-index --find-links=/wheelhouse \ setuptools==83.0.0 wheel==0.46.3 \ && mkdir -p /wheels \ && /opt/build-venv/bin/pip wheel \ --no-deps --no-build-isolation --wheel-dir /wheels \ ./model-gateway-contracts ./memory-gateway ./model-gateway \ && cp /wheels/*.whl /wheelhouse/ # Each target starts from a fresh venv. pip resolves against only the # hash-verified offline wheelhouse, so Model cannot accidentally inherit MCP, # pypdf, or other Memory-only distributions. FROM python-wheelhouse AS memory-python-build RUN python -m venv /opt/venv \ && /opt/venv/bin/pip install \ --no-index --find-links=/wheelhouse \ /wheelhouse/memory_gateway-*.whl \ /wheelhouse/model_gateway_contracts-*.whl \ && /opt/venv/bin/pip check \ && touch /opt/venv/.pip-check-ok \ && /opt/venv/bin/pip uninstall -y pip setuptools wheel FROM python-wheelhouse AS model-python-build RUN python -m venv /opt/venv \ && /opt/venv/bin/pip install \ --no-index --find-links=/wheelhouse \ /wheelhouse/local_model_gateway-*.whl \ /wheelhouse/model_gateway_contracts-*.whl \ && /opt/venv/bin/pip check \ && touch /opt/venv/.pip-check-ok \ && /opt/venv/bin/pip uninstall -y pip setuptools wheel FROM python-wheelhouse AS init-python-build RUN python -m venv /opt/venv \ && /opt/venv/bin/pip install \ --no-index --find-links=/wheelhouse \ /wheelhouse/memory_gateway-*.whl \ /wheelhouse/local_model_gateway-*.whl \ /wheelhouse/model_gateway_contracts-*.whl \ && /opt/venv/bin/pip check \ && touch /opt/venv/.pip-check-ok \ && /opt/venv/bin/pip uninstall -y pip setuptools wheel FROM python:3.12.13-slim-bookworm@sha256:4766d8b510c428e595d74b9cc5bbb2fae8e26316fffb4adc89908d79aacd58a2 AS runtime-base ENV PATH=/opt/venv/bin:$PATH \ PYTHONUNBUFFERED=1 \ PYTHONDONTWRITEBYTECODE=1 WORKDIR /app FROM runtime-base AS memory-runtime ENV MEMGW_HOME=/data/config \ MEMGW_SETTINGS_PATH=/secrets/settings.env \ MEMGW_PROJECT_ROOT=/app/services/memory-gateway \ UI_DIST_DIR=/app/ui/dist COPY --from=memory-python-build /opt/venv /opt/venv # Keep a validated CLI project root without making it an editable install. # The installed wheel remains the runtime import; Model source is absent. COPY services/memory-gateway/pyproject.toml /app/services/memory-gateway/pyproject.toml COPY services/memory-gateway/app /app/services/memory-gateway/app COPY --from=ui-build /build/ui/dist /app/ui/dist COPY deploy/memory-entrypoint.sh /usr/local/bin/memory-gateway-entrypoint RUN groupadd --gid 10001 memory-gateway \ && useradd --uid 10001 --gid 10001 --no-create-home \ --home-dir /nonexistent --shell /usr/sbin/nologin memory-gateway \ && chmod 0555 /usr/local/bin/memory-gateway-entrypoint \ && mkdir -p /data /secrets \ && chown 10001:10001 /data /secrets USER 10001:10001 VOLUME ["/data", "/secrets"] EXPOSE 2026 ENTRYPOINT ["memory-gateway-entrypoint"] FROM runtime-base AS model-runtime ENV MODEL_GATEWAY_HOME=/data \ MODEL_GATEWAY_SECRETS_PATH=/secrets/secrets.env COPY --from=model-python-build /opt/venv /opt/venv COPY deploy/model-entrypoint.sh /usr/local/bin/model-gateway-entrypoint RUN groupadd --gid 10002 model-gateway \ && useradd --uid 10002 --gid 10002 --no-create-home \ --home-dir /nonexistent --shell /usr/sbin/nologin model-gateway \ && chmod 0555 /usr/local/bin/model-gateway-entrypoint \ && mkdir -p /data /secrets \ && chown 10002:10002 /data /secrets USER 10002:10002 VOLUME ["/data", "/secrets"] EXPOSE 2030 ENTRYPOINT ["model-gateway-entrypoint"] # One-shot root image. It is run with networking disabled, initializes or # migrates only explicitly mounted volumes, drops credentials into a host bind # as 0600 files, and exits before either long-lived service starts. FROM runtime-base AS stack-init ENV MEMGW_HOME=/memory-data/config \ MEMGW_SETTINGS_PATH=/memory-secrets/settings.env \ MEMGW_PROJECT_ROOT=/app/services/memory-gateway \ MODEL_GATEWAY_HOME=/model-data \ MODEL_GATEWAY_SECRETS_PATH=/model-secrets/secrets.env COPY --from=init-python-build /opt/venv /opt/venv COPY services/memory-gateway/pyproject.toml /app/services/memory-gateway/pyproject.toml COPY services/memory-gateway/app /app/services/memory-gateway/app COPY deploy/init_stack.py /usr/local/libexec/memory-platform/init_stack.py COPY deploy/migrate_legacy.py /usr/local/libexec/memory-platform/migrate_legacy.py COPY deploy/backup_legacy.py /usr/local/libexec/memory-platform/backup_legacy.py COPY deploy/restore_split.py /usr/local/libexec/memory-platform/restore_split.py COPY deploy/validate_compose.py /usr/local/libexec/memory-platform/validate_compose.py COPY deploy/plan_install.py /usr/local/libexec/memory-platform/plan_install.py COPY deploy/verify_backup.py /usr/local/libexec/memory-platform/verify_backup.py ENTRYPOINT ["python", "/usr/local/libexec/memory-platform/init_stack.py"]