chore(docker): default web + model-server bases to non-DHI images (#13915)

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Jamison LahmanandClaude Opus 5 authored and GitHub committed 2026-08-12 19:58:06 +00:00
1 parent f3bb724922
commit 36640827b7
7 files changed
+170 -98

No files matched your search

@@ -28,11 +28,15 @@ inputs:
description: "ECR registry host for the Docker Hub pull-through cache (the ECR_REGISTRY repo variable)"
required: true
docker-username:
description: "Docker Hub username (must have access to the DHI catalog on dhi.io)"
required: true
description: >-
Docker Hub username with DHI catalog access. Optional -- without it the image
builds on its public base defaults instead of the hardened ones.
required: false
default: ""
docker-token:
description: "Docker Hub token"
required: true
required: false
default: ""
runs:
using: "composite"
steps:
@@ -59,14 +63,13 @@ runs:
with:
ecr-registry: ${{ inputs.ecr-registry }}
# Dockerfile.model_server pulls its hardened Python base from dhi.io, authenticated
# with the same Docker account credentials (the account must have DHI catalog access).
- name: Login to Docker Hardened Images (dhi.io)
uses: docker/login-action@c99871dec2022cc055c062a10cc1a1310835ceb4
# Dockerfile.model_server defaults to the public Python bases. CI builds ship on the
# hardened DHI equivalents, passed as build args below.
- name: Resolve Docker Hardened Image bases
uses: ./.github/actions/dhi-base-images
with:
registry: dhi.io
username: ${{ inputs.docker-username }}
password: ${{ inputs.docker-token }}
docker-username: ${{ inputs.docker-username }}
docker-token: ${{ inputs.docker-token }}
- name: Build and push Model Server Docker image
uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf # ratchet:docker/build-push-action@v6
@@ -77,6 +80,7 @@ runs:
platforms: ${{ inputs.platforms }}
build-args: |
BASE_IMAGE_REGISTRY=${{ env.BASE_IMAGE_REGISTRY }}
${{ env.DHI_PYTHON_BUILD_ARGS }}
tags: ${{ inputs.runs-on-ecr-cache }}:${{ inputs.tag-prefix }}-${{ inputs.run-id }}
cache-from: |
type=registry,ref=${{ inputs.runs-on-ecr-cache }}:model-server-cache-${{ inputs.github-sha }}
@@ -0,0 +1,56 @@
name: "Docker Hardened Image bases"
description: >-
Logs in to dhi.io and exports the build args that point web/Dockerfile and
backend/Dockerfile.model_server at the pinned Docker Hardened Images (DHI).
Both Dockerfiles default to public Docker Hub images, so without DHI credentials
(fork pull requests) this action exports nothing and the defaults apply.
inputs:
docker-username:
description: "Docker Hub username (the account must have DHI catalog access)"
required: false
default: ""
docker-token:
description: "Docker Hub token"
required: false
default: ""
runs:
using: "composite"
steps:
- name: Check for DHI credentials
id: creds
shell: bash
env:
DOCKER_USERNAME: ${{ inputs.docker-username }}
DOCKER_TOKEN: ${{ inputs.docker-token }}
run: |
if [ -n "${DOCKER_USERNAME}" ] && [ -n "${DOCKER_TOKEN}" ]; then
echo "available=true" >> "$GITHUB_OUTPUT"
else
echo "available=false" >> "$GITHUB_OUTPUT"
echo "No DHI credentials; the images build on their public base defaults."
fi
- name: Login to Docker Hardened Images (dhi.io)
if: steps.creds.outputs.available == 'true'
uses: docker/login-action@c99871dec2022cc055c062a10cc1a1310835ceb4
with:
registry: dhi.io
username: ${{ inputs.docker-username }}
password: ${{ inputs.docker-token }}
# Single source of truth for the DHI digests. Refresh one with:
# docker buildx imagetools inspect <image reference>
- name: Export DHI build args
if: steps.creds.outputs.available == 'true'
shell: bash
run: |
{
echo "DHI_NODE_BUILD_ARGS<<EOF"
echo "NODE_BUILDER_IMAGE=dhi.io/node:24-debian13-dev@sha256:25a83f18150669e9ce3c9437327add4d75ae4ea26cbdb5e8747b66d3b74a567a"
echo "NODE_RUNTIME_IMAGE=dhi.io/node:24-debian13@sha256:805278f24c1146c6d3c96577b6256f8f97c43196fff88315fe3291a1ce118ddd"
echo "EOF"
echo "DHI_PYTHON_BUILD_ARGS<<EOF"
echo "PYTHON_BUILDER_IMAGE=dhi.io/python:3.13-debian13-dev@sha256:7933d16e50454c39bca6e935027166d5e5b05c6c03383dc2f58e8fe6e2440b7d"
echo "PYTHON_RUNTIME_IMAGE=dhi.io/python:3.13-debian13@sha256:05827957dafc7b83633d56f24d9281525d3546a1d600eef90385c7212d3def5e"
echo "EOF"
} >> "$GITHUB_ENV"
+42 -42
View File
@@ -535,14 +535,13 @@ jobs:
username: ${{ env.DOCKER_USERNAME }}
password: ${{ env.DOCKER_TOKEN }}
# web/Dockerfile pulls its hardened Node base from dhi.io, authenticated with the same
# Docker account credentials (the account must have access to the DHI catalog).
- name: Login to Docker Hardened Images (dhi.io)
uses: docker/login-action@c99871dec2022cc055c062a10cc1a1310835ceb4
# web/Dockerfile defaults to the public Node bases. Release builds ship on the
# hardened DHI equivalents, passed as build args below.
- name: Resolve Docker Hardened Image bases
uses: ./.github/actions/dhi-base-images
with:
registry: dhi.io
username: ${{ env.DOCKER_USERNAME }}
password: ${{ env.DOCKER_TOKEN }}
docker-username: ${{ env.DOCKER_USERNAME }}
docker-token: ${{ env.DOCKER_TOKEN }}
- name: Build and push AMD64
id: build
@@ -554,6 +553,7 @@ jobs:
labels: ${{ steps.meta.outputs.labels }}
build-args: |
ONYX_VERSION=${{ github.ref_name }}
${{ env.DHI_NODE_BUILD_ARGS }}
NODE_OPTIONS=--max-old-space-size=8192
cache-from: |
type=registry,ref=${{ env.REGISTRY_IMAGE }}:edge
@@ -617,14 +617,13 @@ jobs:
username: ${{ env.DOCKER_USERNAME }}
password: ${{ env.DOCKER_TOKEN }}
# web/Dockerfile pulls its hardened Node base from dhi.io, authenticated with the same
# Docker account credentials (the account must have access to the DHI catalog).
- name: Login to Docker Hardened Images (dhi.io)
uses: docker/login-action@c99871dec2022cc055c062a10cc1a1310835ceb4
# web/Dockerfile defaults to the public Node bases. Release builds ship on the
# hardened DHI equivalents, passed as build args below.
- name: Resolve Docker Hardened Image bases
uses: ./.github/actions/dhi-base-images
with:
registry: dhi.io
username: ${{ env.DOCKER_USERNAME }}
password: ${{ env.DOCKER_TOKEN }}
docker-username: ${{ env.DOCKER_USERNAME }}
docker-token: ${{ env.DOCKER_TOKEN }}
- name: Build and push ARM64
id: build
@@ -636,6 +635,7 @@ jobs:
labels: ${{ steps.meta.outputs.labels }}
build-args: |
ONYX_VERSION=${{ github.ref_name }}
${{ env.DHI_NODE_BUILD_ARGS }}
NODE_OPTIONS=--max-old-space-size=8192
cache-from: |
type=registry,ref=${{ env.REGISTRY_IMAGE }}:edge
@@ -791,14 +791,13 @@ jobs:
username: ${{ env.DOCKER_USERNAME }}
password: ${{ env.DOCKER_TOKEN }}
# web/Dockerfile pulls its hardened Node base from dhi.io, authenticated with the same
# Docker account credentials (the account must have access to the DHI catalog).
- name: Login to Docker Hardened Images (dhi.io)
uses: docker/login-action@c99871dec2022cc055c062a10cc1a1310835ceb4
# web/Dockerfile defaults to the public Node bases. Release builds ship on the
# hardened DHI equivalents, passed as build args below.
- name: Resolve Docker Hardened Image bases
uses: ./.github/actions/dhi-base-images
with:
registry: dhi.io
username: ${{ env.DOCKER_USERNAME }}
password: ${{ env.DOCKER_TOKEN }}
docker-username: ${{ env.DOCKER_USERNAME }}
docker-token: ${{ env.DOCKER_TOKEN }}
- name: Build and push AMD64
id: build
@@ -810,6 +809,7 @@ jobs:
labels: ${{ steps.meta.outputs.labels }}
build-args: |
ONYX_VERSION=${{ github.ref_name }}
${{ env.DHI_NODE_BUILD_ARGS }}
NEXT_PUBLIC_CLOUD_ENABLED=true
WEB_FRAME_PROTECTION_ENABLED=false
NEXT_PUBLIC_POSTHOG_KEY=${{ secrets.POSTHOG_KEY }}
@@ -884,14 +884,13 @@ jobs:
username: ${{ env.DOCKER_USERNAME }}
password: ${{ env.DOCKER_TOKEN }}
# web/Dockerfile pulls its hardened Node base from dhi.io, authenticated with the same
# Docker account credentials (the account must have access to the DHI catalog).
- name: Login to Docker Hardened Images (dhi.io)
uses: docker/login-action@c99871dec2022cc055c062a10cc1a1310835ceb4
# web/Dockerfile defaults to the public Node bases. Release builds ship on the
# hardened DHI equivalents, passed as build args below.
- name: Resolve Docker Hardened Image bases
uses: ./.github/actions/dhi-base-images
with:
registry: dhi.io
username: ${{ env.DOCKER_USERNAME }}
password: ${{ env.DOCKER_TOKEN }}
docker-username: ${{ env.DOCKER_USERNAME }}
docker-token: ${{ env.DOCKER_TOKEN }}
- name: Build and push ARM64
id: build
@@ -903,6 +902,7 @@ jobs:
labels: ${{ steps.meta.outputs.labels }}
build-args: |
ONYX_VERSION=${{ github.ref_name }}
${{ env.DHI_NODE_BUILD_ARGS }}
NEXT_PUBLIC_CLOUD_ENABLED=true
WEB_FRAME_PROTECTION_ENABLED=false
NEXT_PUBLIC_POSTHOG_KEY=${{ secrets.POSTHOG_KEY }}
@@ -1351,14 +1351,13 @@ jobs:
username: ${{ env.DOCKER_USERNAME }}
password: ${{ env.DOCKER_TOKEN }}
# Dockerfile.model_server pulls its hardened Python base from dhi.io, authenticated
# with the same Docker account credentials (the account must have DHI catalog access).
- name: Login to Docker Hardened Images (dhi.io)
uses: docker/login-action@c99871dec2022cc055c062a10cc1a1310835ceb4
# Dockerfile.model_server defaults to the public Python bases. Release builds ship
# on the hardened DHI equivalents, passed as build args below.
- name: Resolve Docker Hardened Image bases
uses: ./.github/actions/dhi-base-images
with:
registry: dhi.io
username: ${{ env.DOCKER_USERNAME }}
password: ${{ env.DOCKER_TOKEN }}
docker-username: ${{ env.DOCKER_USERNAME }}
docker-token: ${{ env.DOCKER_TOKEN }}
- name: Build and push AMD64
id: build
@@ -1372,6 +1371,7 @@ jobs:
labels: ${{ steps.meta.outputs.labels }}
build-args: |
ONYX_VERSION=${{ github.ref_name }}
${{ env.DHI_PYTHON_BUILD_ARGS }}
cache-from: |
type=registry,ref=${{ env.REGISTRY_IMAGE }}:edge
type=registry,ref=${{ env.REGISTRY_IMAGE }}:latest
@@ -1437,14 +1437,13 @@ jobs:
username: ${{ env.DOCKER_USERNAME }}
password: ${{ env.DOCKER_TOKEN }}
# Dockerfile.model_server pulls its hardened Python base from dhi.io, authenticated
# with the same Docker account credentials (the account must have DHI catalog access).
- name: Login to Docker Hardened Images (dhi.io)
uses: docker/login-action@c99871dec2022cc055c062a10cc1a1310835ceb4
# Dockerfile.model_server defaults to the public Python bases. Release builds ship
# on the hardened DHI equivalents, passed as build args below.
- name: Resolve Docker Hardened Image bases
uses: ./.github/actions/dhi-base-images
with:
registry: dhi.io
username: ${{ env.DOCKER_USERNAME }}
password: ${{ env.DOCKER_TOKEN }}
docker-username: ${{ env.DOCKER_USERNAME }}
docker-token: ${{ env.DOCKER_TOKEN }}
- name: Build and push ARM64
id: build
@@ -1458,6 +1457,7 @@ jobs:
labels: ${{ steps.meta.outputs.labels }}
build-args: |
ONYX_VERSION=${{ github.ref_name }}
${{ env.DHI_PYTHON_BUILD_ARGS }}
cache-from: |
type=registry,ref=${{ env.REGISTRY_IMAGE }}:edge
type=registry,ref=${{ env.REGISTRY_IMAGE }}:latest
+9 -7
View File
@@ -120,6 +120,7 @@ jobs:
- '.github/workflows/pr-playwright-tests.yml'
- '.github/actions/setup-test-license/**'
- '.github/actions/login-ecr-pullthrough-cache/**'
- '.github/actions/dhi-base-images/**'
airgap:
- 'backend/Dockerfile'
- 'backend/Dockerfile.model_server'
@@ -147,6 +148,7 @@ jobs:
- '.github/workflows/pr-playwright-tests.yml'
- '.github/actions/build-model-server-image/**'
- '.github/actions/login-ecr-pullthrough-cache/**'
- '.github/actions/dhi-base-images/**'
mcp_oauth:
- 'backend/onyx/server/features/mcp/**'
- 'backend/tests/integration/mock_services/mcp_test_server/**'
@@ -196,14 +198,13 @@ jobs:
with:
ecr-registry: ${{ vars.ECR_REGISTRY }}
# web/Dockerfile pulls its hardened Node base from dhi.io, authenticated with the same
# Docker account credentials (the account must have access to the DHI catalog).
- name: Login to Docker Hardened Images (dhi.io)
uses: docker/login-action@c99871dec2022cc055c062a10cc1a1310835ceb4
# web/Dockerfile defaults to the public Node bases. CI builds ship on the hardened
# DHI equivalents, passed as build args below.
- name: Resolve Docker Hardened Image bases
uses: ./.github/actions/dhi-base-images
with:
registry: dhi.io
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_TOKEN }}
docker-username: ${{ secrets.DOCKER_USERNAME }}
docker-token: ${{ secrets.DOCKER_TOKEN }}
# SKIP_TYPE_CHECK cuts the build time of this image. Types are still checked
# by the `typescript-check` prek hook in the Quality Checks PR workflow.
@@ -217,6 +218,7 @@ jobs:
push: true
build-args: |
BASE_IMAGE_REGISTRY=${{ env.BASE_IMAGE_REGISTRY }}
${{ env.DHI_NODE_BUILD_ARGS }}
SKIP_TYPE_CHECK=1
cache-from: |
type=registry,ref=${{ env.RUNS_ON_ECR_CACHE }}:web-cache-${{ github.event.pull_request.head.sha || github.sha }}
+4 -10
View File
@@ -306,16 +306,10 @@ If you want to make changes to Onyx and run those changes in Docker, you can als
docker compose up -d --build
```
> **Note:** Building the web image (`web/Dockerfile`) and the model-server image
> (`backend/Dockerfile.model_server`) pulls their bases from Docker Hardened Images (`dhi.io`),
> so you must authenticate first with a Docker account that has access to the DHI catalog:
>
> ```bash
> docker login dhi.io
> ```
>
> Pulling the pre-built `onyxdotapp/onyx-web-server` / `onyxdotapp/onyx-model-server` images
> (the default `docker compose up -d` without `--build`) does not require this.
> **Note:** Local builds use the public Docker Hub base images, so they need no extra
> registry access. Our release builds override the base images with the Docker Hardened
> Image (`dhi.io`) equivalents, so the published `onyxdotapp/onyx-web-server` and
> `onyxdotapp/onyx-model-server` images differ from a local `--build` in their base layers.
> **Note:** `docker-compose.yml`, `docker-compose.prod.yml` and
> `docker-compose.prod-no-letsencrypt.yml` are generated from `docker-compose.template.yml`
+23 -18
View File
@@ -1,13 +1,18 @@
# Registry prefix retained for parity with the other Dockerfiles and so the
# BASE_IMAGE_REGISTRY build-arg (passed by docker-bake.hcl / CI) stays valid.
# The hardened Python bases below come from dhi.io -- a separate registry not
# served by the ECR pull-through cache -- so they intentionally bypass this ARG.
# Registry prefix for the base images below. Defaults to Docker Hub; CI overrides it to
# the ECR pull-through cache to dodge rate limits. It only applies to the default images
# -- the DHI overrides below carry their own registry (dhi.io), which the cache does not serve.
ARG BASE_IMAGE_REGISTRY=docker.io
# Build stage. The DHI "-dev" variant runs as root and ships a shell, apt, and
# build tooling -- everything needed to install the wheels.
# Refresh the digest with: docker buildx imagetools inspect dhi.io/python:3.13-debian13-dev
FROM dhi.io/python:3.13-debian13-dev@sha256:7933d16e50454c39bca6e935027166d5e5b05c6c03383dc2f58e8fe6e2440b7d AS builder
# Python bases. The defaults are the public slim images, so a plain `docker build` needs no
# extra registry access. CI overrides both with the matching Docker Hardened Images from
# dhi.io, which need a Docker account with DHI catalog access.
# Refresh a digest with: docker buildx imagetools inspect <image reference>
ARG PYTHON_BUILDER_IMAGE=${BASE_IMAGE_REGISTRY}/library/python:3.13-slim@sha256:b04b5d7233d2ad9c379e22ea8927cd1378cd15c60d4ef876c065b25ea8fb3bf3
ARG PYTHON_RUNTIME_IMAGE=${BASE_IMAGE_REGISTRY}/library/python:3.13-slim@sha256:b04b5d7233d2ad9c379e22ea8927cd1378cd15c60d4ef876c065b25ea8fb3bf3
# Build stage. Needs a root user plus a shell and build tooling to install the wheels,
# which both the default slim image and the DHI "-dev" variant provide.
FROM ${PYTHON_BUILDER_IMAGE} AS builder
ENV ONYX_RUNNING_IN_DOCKER="true" \
HF_HOME=/app/.cache/huggingface \
@@ -19,11 +24,11 @@ ENV ONYX_RUNNING_IN_DOCKER="true" \
COPY --from=ghcr.io/astral-sh/uv:0.11.25@sha256:1e3808aa9023d0980e7c15b1fa7c1ac16ff35925780cf5c459858b2d693f01a9 /uv /uvx /bin/
# Install into a self-contained venv rather than the system site-packages. The
# venv lives at a fixed path we control (/app/.venv), so the distroless runtime
# stage can copy it wholesale without depending on the base image's Python layout.
# venv lives at a fixed path we control (/app/.venv), so the runtime stage can
# copy it wholesale without depending on the base image's Python layout.
RUN uv venv /app/.venv --python 3.13
# Pre-create the runtime writable dirs here (the distroless runtime has no shell
# Pre-create the runtime writable dirs here (the DHI runtime has no shell
# to mkdir). Left empty in this stage; the model download happens downstream.
RUN mkdir -p /app/.cache/huggingface /var/log/onyx
@@ -31,7 +36,7 @@ RUN mkdir -p /app/.cache/huggingface /var/log/onyx
# pin `-u onyx` / `user: onyx` keep resolving and their 1001-owned volumes stay
# writable. The DHI "-dev" image ships a shell but not the `shadow` tools
# (groupadd/useradd), so write the passwd/group entries directly; they're copied
# into the distroless runtime below.
# into the runtime stage below.
RUN printf 'onyx:x:1001:\n' >> /etc/group && \
printf 'onyx:x:1001:1001::/home/onyx:/usr/sbin/nologin\n' >> /etc/passwd
@@ -55,11 +60,11 @@ FROM builder AS embedding-models
RUN python -c "from sentence_transformers import SentenceTransformer; \
SentenceTransformer(model_name_or_path='nomic-ai/nomic-embed-text-v1', trust_remote_code=False);"
# Runtime stage. The DHI runtime variant is near-distroless: no shell or package
# manager, runs non-root. We run as the `onyx` user (UID 1001, carried over from
# the previous image; see USER below) and chown everything the runtime writes to it.
# Refresh the digest with: docker buildx imagetools inspect dhi.io/python:3.13-debian13
FROM dhi.io/python:3.13-debian13@sha256:05827957dafc7b83633d56f24d9281525d3546a1d600eef90385c7212d3def5e AS final
# Runtime stage. We run as the `onyx` user (UID 1001, carried over from the previous
# image; see USER below) and chown everything the runtime writes to it. The DHI runtime
# variant is near-distroless: no shell or package manager, so keep this stage free of
# RUN instructions.
FROM ${PYTHON_RUNTIME_IMAGE} AS final
LABEL com.danswer.maintainer="founders@onyx.app"
LABEL com.danswer.description="This image is for the Onyx model server which runs all of the \
@@ -76,7 +81,7 @@ ENV ONYX_RUNNING_IN_DOCKER="true" \
VIRTUAL_ENV=/app/.venv \
PATH="/app/.venv/bin:$PATH"
# Bring the `onyx` user (1001) into the distroless runtime. The -dev builder's
# Bring the `onyx` user (1001) into the runtime stage. The -dev builder's
# passwd/group are a superset of the runtime's, so copying them preserves the
# base's own entries while adding `onyx`, letting `USER onyx` and any runtime
# `-u onyx` override resolve the name.
+22 -11
View File
@@ -1,15 +1,22 @@
# Registry prefix for the bun binary source below. Defaults to Docker Hub; CI overrides it to
# the ECR pull-through cache to dodge rate limits. Applies only to `bun_source` -- the DHI Node
# bases come from dhi.io, a separate registry not served by the cache (see the FROM lines below).
# Registry prefix for the base images below. Defaults to Docker Hub; CI overrides it to the
# ECR pull-through cache to dodge rate limits. It only applies to the default images -- the
# DHI overrides below carry their own registry (dhi.io), which the cache does not serve.
ARG BASE_IMAGE_REGISTRY=docker.io
# Node bases. The defaults are the public Debian slim images, so a plain `docker build` needs
# no extra registry access. CI overrides both with the matching Docker Hardened Images from
# dhi.io, which need a Docker account with DHI catalog access.
# Refresh a digest with: docker buildx imagetools inspect <image reference>
ARG NODE_BUILDER_IMAGE=${BASE_IMAGE_REGISTRY}/library/node:24-trixie-slim@sha256:0711b541c1c33a8a530ac4f0d391baa9a15b3d804695b1b24a47daa5fb60e74d
ARG NODE_RUNTIME_IMAGE=${BASE_IMAGE_REGISTRY}/library/node:24-trixie-slim@sha256:0711b541c1c33a8a530ac4f0d391baa9a15b3d804695b1b24a47daa5fb60e74d
# bun binary source, in its own stage because buildx only expands the registry ARG in a FROM,
# not in `COPY --from=`. Use the glibc (Debian) build so it runs on the glibc DHI node images.
# not in `COPY --from=`. Use the glibc (Debian) build so it runs on the glibc node images.
FROM ${BASE_IMAGE_REGISTRY}/oven/bun:1@sha256:e10577f0db68676a7024391c6e5cb4b879ebd17188ab750cf10024a6d700e5c4 AS bun_source
# Build stage. The DHI "-dev" variant runs as root and ships a shell, apt, and npm/npx.
# Refresh the digest with: docker buildx imagetools inspect dhi.io/node:24-debian13-dev
FROM dhi.io/node:24-debian13-dev@sha256:25a83f18150669e9ce3c9437327add4d75ae4ea26cbdb5e8747b66d3b74a567a AS builder
# Build stage. Needs a root user plus a shell and npm/npx, which both the default slim image
# and the DHI "-dev" variant provide.
FROM ${NODE_BUILDER_IMAGE} AS builder
COPY --from=bun_source /usr/local/bin/bun /usr/local/bin/bun
COPY --from=bun_source /usr/local/bin/bunx /usr/local/bin/bunx
WORKDIR /app
@@ -100,10 +107,10 @@ RUN --mount=type=secret,id=sentry_auth_token \
fi && \
NODE_OPTIONS="${NODE_OPTIONS}" npx next build
# Runtime stage. The DHI runtime variant is near-distroless: no shell or package manager, runs
# as the non-root `node` user (uid 1000) with WORKDIR /app pre-set.
# Refresh the digest with: docker buildx imagetools inspect dhi.io/node:24-debian13
FROM dhi.io/node:24-debian13@sha256:805278f24c1146c6d3c96577b6256f8f97c43196fff88315fe3291a1ce118ddd AS runner
# Runtime stage. Runs as the non-root `node` user (uid 1000), which both the default slim
# image and the DHI runtime variant ship. The DHI variant is near-distroless: no shell or
# package manager, so keep this stage free of RUN instructions.
FROM ${NODE_RUNTIME_IMAGE} AS runner
LABEL com.onyx.maintainer="founders@onyx.app"
LABEL com.onyx.description="This image is the web/frontend container of Onyx which \
@@ -193,5 +200,9 @@ ENV ONYX_VERSION=${ONYX_VERSION}
# name only — breaking loopback healthchecks. Force 0.0.0.0 instead.
ENV HOSTNAME="0.0.0.0"
# Don't run production as root. The DHI runtime already defaults to this user; the default
# slim image does not.
USER node
# The DHI runtime image sets no default ENTRYPOINT, so invoke node explicitly.
CMD ["node", "server.js"]