fix: support OpenShift arbitrary UID/GID-0 in Docker image (#5136)

* fix: support OpenShift arbitrary UID/GID-0 in Docker image

Made-with: Cursor

* move to community

---------

Co-authored-by: Petre Ghita <petre.ghita@asset-metrix.com>
Co-authored-by: Timothy Carambat <rambat1010@gmail.com>
This commit is contained in:
Petre Ghita 2026-04-28 19:10:27 +02:00 committed by GitHub
parent 5eb9842533
commit c8113aa188
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 410 additions and 0 deletions

View File

@ -0,0 +1,225 @@
# OpenShift-compatible Dockerfile for AnythingLLM
#
# This Dockerfile is specifically designed for OpenShift deployments which use
# arbitrary UIDs with GID 0 (root group). Do NOT use this for standard Docker
# or Docker Compose deployments - use the main docker/Dockerfile instead.
#
# Key differences from the standard Dockerfile:
# - User is added to supplementary group 0 (root) for OpenShift compatibility
# - All files are owned by group 0 and are group-writable (chmod g+w)
# - /etc/passwd is made group-writable for dynamic UID injection
# - Uses a modified entrypoint that handles arbitrary UID scenarios
# Setup base image
FROM ubuntu:noble-20251013 AS base
# Build arguments
ARG ARG_UID=1000
ARG ARG_GID=1000
FROM base AS build-arm64
RUN echo "Preparing build of AnythingLLM image for arm64 architecture"
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
# Install system dependencies
# hadolint ignore=DL3008,DL3013
RUN DEBIAN_FRONTEND=noninteractive apt-get update && \
DEBIAN_FRONTEND=noninteractive apt-get install -yq --no-install-recommends \
unzip curl gnupg libgfortran5 libgbm1 tzdata netcat-openbsd \
libasound2t64 libatk1.0-0 libc6 libcairo2 libcups2 libdbus-1-3 libexpat1 libfontconfig1 \
libgcc1 libglib2.0-0 libgtk-3-0 libnspr4 libpango-1.0-0 libx11-6 libx11-xcb1 libxcb1 \
libxcomposite1 libxcursor1 libxdamage1 libxext6 libxfixes3 libxi6 libxrandr2 libxrender1 \
libxss1 libxtst6 ca-certificates fonts-liberation libappindicator3-1 libnss3 lsb-release \
xdg-utils git build-essential ffmpeg && \
mkdir -p /etc/apt/keyrings && \
curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg && \
echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_18.x nodistro main" | tee /etc/apt/sources.list.d/nodesource.list && \
apt-get update && \
# Install node and yarn
apt-get install -yq --no-install-recommends nodejs && \
curl -LO https://github.com/yarnpkg/yarn/releases/download/v1.22.19/yarn_1.22.19_all.deb \
&& dpkg -i yarn_1.22.19_all.deb \
&& rm yarn_1.22.19_all.deb && \
# Install uvx (pinned to 0.6.10) for MCP support
curl -LsSf https://astral.sh/uv/0.6.10/install.sh | sh && \
mv /root/.local/bin/uv /usr/local/bin/uv && \
mv /root/.local/bin/uvx /usr/local/bin/uvx && \
echo "Installed uvx! $(uv --version)" && \
apt-get clean && \
rm -rf /var/lib/apt/lists/*
# Create a group and user with specific UID and GID.
# The user's primary group is ARG_GID (default 1000) for Docker/Compose backward compatibility.
# The user is also added to the root group (GID 0) as a supplementary group so that
# OpenShift's arbitrary-UID-with-GID-0 model works via group-writable permissions.
RUN (getent passwd "$ARG_UID" && userdel -f "$(getent passwd "$ARG_UID" | cut -d: -f1)") || true && \
if [ "$ARG_GID" != "0" ]; then \
(getent group "$ARG_GID" && groupdel "$(getent group "$ARG_GID" | cut -d: -f1)") || true && \
groupadd -g "$ARG_GID" anythingllm && \
useradd -l -u "$ARG_UID" -m -d /app -s /bin/bash -g anythingllm -G 0 anythingllm; \
else \
useradd -l -u "$ARG_UID" -m -d /app -s /bin/bash -g 0 anythingllm; \
fi && \
mkdir -p /app/frontend/ /app/server/ /app/collector/ /app/.cache /app/.yarn && \
chown -R anythingllm:0 /app && \
chmod -R g+w /app && \
chmod g=u /etc/passwd
# Copy docker helper scripts (use OpenShift-specific entrypoint)
COPY ./cloud-deployments/openshift/docker-entrypoint.sh /usr/local/bin/
COPY ./docker/docker-healthcheck.sh /usr/local/bin/
COPY --chown=anythingllm:0 ./docker/.env.example /app/server/.env
# Ensure the scripts are executable
RUN chmod +x /usr/local/bin/docker-entrypoint.sh && \
chmod +x /usr/local/bin/docker-healthcheck.sh
USER anythingllm
WORKDIR /app
# Puppeteer does not ship with an ARM86 compatible build for Chromium
# so web-scraping would be broken in arm docker containers unless we patch it
# by manually installing a compatible chromedriver.
RUN echo "Need to patch Puppeteer x Chromium support for ARM86 - installing dep!" && \
curl -fSL https://webassets.anythingllm.com/chromium-1088-linux-arm64.zip -o chrome-linux.zip && \
unzip chrome-linux.zip && \
rm -rf chrome-linux.zip
ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true
ENV CHROME_PATH=/app/chrome-linux/chrome
ENV PUPPETEER_EXECUTABLE_PATH=/app/chrome-linux/chrome
RUN echo "Done running arm64 specific installation steps"
#############################################
# amd64-specific stage
FROM base AS build-amd64
RUN echo "Preparing build of AnythingLLM image for non-ARM architecture"
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
# Install system dependencies
# hadolint ignore=DL3008,DL3013
RUN DEBIAN_FRONTEND=noninteractive apt-get update && \
DEBIAN_FRONTEND=noninteractive apt-get install -yq --no-install-recommends \
curl gnupg libgfortran5 libgbm1 tzdata netcat-openbsd \
libasound2t64 libatk1.0-0 libc6 libcairo2 libcups2 libdbus-1-3 libexpat1 libfontconfig1 \
libgcc1 libglib2.0-0 libgtk-3-0 libnspr4 libpango-1.0-0 libx11-6 libx11-xcb1 libxcb1 \
libxcomposite1 libxcursor1 libxdamage1 libxext6 libxfixes3 libxi6 libxrandr2 libxrender1 \
libxss1 libxtst6 ca-certificates fonts-liberation libappindicator3-1 libnss3 lsb-release \
xdg-utils git build-essential ffmpeg && \
mkdir -p /etc/apt/keyrings && \
curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg && \
echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_18.x nodistro main" | tee /etc/apt/sources.list.d/nodesource.list && \
apt-get update && \
# Install node and yarn
apt-get install -yq --no-install-recommends nodejs && \
curl -LO https://github.com/yarnpkg/yarn/releases/download/v1.22.19/yarn_1.22.19_all.deb \
&& dpkg -i yarn_1.22.19_all.deb \
&& rm yarn_1.22.19_all.deb && \
# Install uvx (pinned to 0.6.10) for MCP support
curl -LsSf https://astral.sh/uv/0.6.10/install.sh | sh && \
mv /root/.local/bin/uv /usr/local/bin/uv && \
mv /root/.local/bin/uvx /usr/local/bin/uvx && \
echo "Installed uvx! $(uv --version)" && \
apt-get clean && \
rm -rf /var/lib/apt/lists/*
# Create a group and user with specific UID and GID.
# The user's primary group is ARG_GID (default 1000) for Docker/Compose backward compatibility.
# The user is also added to the root group (GID 0) as a supplementary group so that
# OpenShift's arbitrary-UID-with-GID-0 model works via group-writable permissions.
RUN (getent passwd "$ARG_UID" && userdel -f "$(getent passwd "$ARG_UID" | cut -d: -f1)") || true && \
if [ "$ARG_GID" != "0" ]; then \
(getent group "$ARG_GID" && groupdel "$(getent group "$ARG_GID" | cut -d: -f1)") || true && \
groupadd -g "$ARG_GID" anythingllm && \
useradd -l -u "$ARG_UID" -m -d /app -s /bin/bash -g anythingllm -G 0 anythingllm; \
else \
useradd -l -u "$ARG_UID" -m -d /app -s /bin/bash -g 0 anythingllm; \
fi && \
mkdir -p /app/frontend/ /app/server/ /app/collector/ /app/.cache /app/.yarn && \
chown -R anythingllm:0 /app && \
chmod -R g+w /app && \
chmod g=u /etc/passwd
# Copy docker helper scripts (use OpenShift-specific entrypoint)
COPY ./cloud-deployments/openshift/docker-entrypoint.sh /usr/local/bin/
COPY ./docker/docker-healthcheck.sh /usr/local/bin/
COPY --chown=anythingllm:0 ./docker/.env.example /app/server/.env
# Ensure the scripts are executable
RUN chmod +x /usr/local/bin/docker-entrypoint.sh && \
chmod +x /usr/local/bin/docker-healthcheck.sh
#############################################
# COMMON BUILD FLOW FOR ALL ARCHS
#############################################
# hadolint ignore=DL3006
FROM build-${TARGETARCH} AS build
RUN echo "Running common build flow of AnythingLLM image for all architectures"
USER anythingllm
WORKDIR /app
# Install & Build frontend layer
# Use BUILDPLATFORM to run on the native host architecture (not emulated).
# This avoids esbuild crashing under QEMU when cross-compiling.
# The output (static HTML/CSS/JS) is platform-independent.
FROM --platform=$BUILDPLATFORM node:18-slim AS frontend-build
WORKDIR /app/frontend
COPY ./frontend/package.json ./frontend/yarn.lock ./
RUN yarn install --network-timeout 100000 && yarn cache clean
COPY ./frontend/ ./
RUN yarn build
WORKDIR /app
# Install server layer
# Also pull and build collector deps (chromium issues prevent bad bindings)
FROM build AS backend-build
COPY --chown=anythingllm:0 ./server /app/server/
WORKDIR /app/server
RUN yarn install --production --network-timeout 100000 && yarn cache clean
WORKDIR /app
# Install collector dependencies
COPY --chown=anythingllm:0 ./collector/ ./collector/
WORKDIR /app/collector
ENV PUPPETEER_DOWNLOAD_BASE_URL=https://storage.googleapis.com/chrome-for-testing-public
RUN yarn install --production --network-timeout 100000 && yarn cache clean
WORKDIR /app
USER anythingllm
# Since we are building from backend-build we just need to move built frontend into server/public
FROM backend-build AS production-build
WORKDIR /app
COPY --chown=anythingllm:0 --from=frontend-build /app/frontend/dist /app/server/public
# Ensure all app files are owned by group 0 (root) and group-writable so that:
# - Docker/Compose: access works via user ownership (UID match)
# - OpenShift: access works via group ownership (GID 0 match + g+w)
# - Generic Kubernetes: access works via user ownership or fsGroup
# This must run after all COPY and yarn install steps to cover node_modules, etc.
USER root
RUN chown -R anythingllm:0 /app && \
chmod -R g+w /app && \
mkdir -p /app/server/storage
# Setup the environment
ENV NODE_ENV=production
ENV ANYTHING_LLM_RUNTIME=docker
ENV DEPLOYMENT_VERSION=1.12.1
ENV HOME=/app
# Setup the healthcheck
HEALTHCHECK --interval=1m --timeout=10s --start-period=1m \
CMD /bin/bash /usr/local/bin/docker-healthcheck.sh || exit 1
USER anythingllm
# Run the server
# CMD ["sh", "-c", "tail -f /dev/null"] # For development: keep container open
ENTRYPOINT ["/bin/bash", "/usr/local/bin/docker-entrypoint.sh"]

View File

@ -0,0 +1,146 @@
> [!IMPORTANT]
> This is a community-maintained template and is not officially supported by the AnythingLLM team. You could encounter issues or even deployment failures in future versions of AnythingLLM. We do our best to keep this template and all community contributions backwards compatible, but we cannot guarantee it.
# OpenShift Deployment Template for AnythingLLM
This directory contains a specialized Dockerfile and entrypoint script for deploying AnythingLLM on **Red Hat OpenShift** clusters.
## Why This Template Exists
OpenShift has a unique security model that differs from standard Docker/Kubernetes deployments:
1. **Arbitrary UIDs**: OpenShift runs containers with randomly assigned user IDs (UIDs) that don't exist in `/etc/passwd`
2. **GID 0 Requirement**: All containers run with GID 0 (root group) as the primary group
3. **Restricted SCCs**: The default Security Context Constraints (SCCs) prevent containers from running as specific users
These requirements are incompatible with the standard AnythingLLM Docker image, which uses a fixed `anythingllm` user with UID/GID 1000.
## Key Differences from Standard Dockerfile
| Feature | Standard Docker | OpenShift Template |
|---------|-----------------|-------------------|
| File ownership | `anythingllm:anythingllm` | `anythingllm:0` (root group) |
| File permissions | Standard | Group-writable (`g+w`) |
| `/etc/passwd` | Read-only | Group-writable for UID injection |
| Supplementary groups | None | Added to group 0 |
| Entrypoint | Standard | Handles arbitrary UID scenarios |
## When to Use This Template
Use this template **only** if you are deploying to:
- Red Hat OpenShift (any version)
- OKD (OpenShift Origin)
- Any Kubernetes cluster with OpenShift-style restricted SCCs
**Do NOT use this for:**
- Standard Docker deployments
- Docker Compose
- Generic Kubernetes (use the standard image with appropriate `securityContext`)
- Cloud container services (AWS ECS, Google Cloud Run, Azure Container Instances)
## Building the Image
From the repository root:
```bash
docker build -f cloud-deployments/openshift/Dockerfile -t anythingllm:openshift .
```
For multi-architecture builds:
```bash
docker buildx build \
--platform linux/amd64,linux/arm64 \
-f cloud-deployments/openshift/Dockerfile \
-t your-registry/anythingllm:openshift \
--push .
```
## Deploying to OpenShift
### Using `oc` CLI
```bash
# Create a new project (namespace)
oc new-project anythingllm
# Create a deployment
oc new-app your-registry/anythingllm:openshift
# Expose the service
oc expose svc/anythingllm --port=3001
# Set required environment variables
oc set env deployment/anythingllm \
STORAGE_DIR=/app/server/storage \
JWT_SECRET=$(openssl rand -hex 32)
```
### Using a DeploymentConfig YAML
```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: anythingllm
spec:
replicas: 1
selector:
matchLabels:
app: anythingllm
template:
metadata:
labels:
app: anythingllm
spec:
containers:
- name: anythingllm
image: your-registry/anythingllm:openshift
ports:
- containerPort: 3001
env:
- name: STORAGE_DIR
value: /app/server/storage
- name: JWT_SECRET
valueFrom:
secretKeyRef:
name: anythingllm-secrets
key: jwt-secret
volumeMounts:
- name: storage
mountPath: /app/server/storage
volumes:
- name: storage
persistentVolumeClaim:
claimName: anythingllm-storage
```
## Persistent Storage
OpenShift PersistentVolumeClaims work with this image. Ensure the PVC is created before deployment:
```yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: anythingllm-storage
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 10Gi
```
## Troubleshooting
### Permission Denied Errors
If you see permission errors, verify:
1. You're using this OpenShift-specific image, not the standard one
2. The PVC has correct access modes
3. No custom SCCs are overriding the default behavior
### User Not Found in passwd
The entrypoint script automatically handles this by injecting a passwd entry at runtime. If issues persist, check that `/etc/passwd` is group-writable in your image.

View File

@ -0,0 +1,39 @@
#!/bin/bash
# OpenShift runs containers with an arbitrary UID that may not exist in /etc/passwd.
# Many tools (npm, prisma, git, etc.) expect a passwd entry for the running user.
# If the current UID has no entry, dynamically add one using nss_wrapper-style injection.
if ! whoami &> /dev/null 2>&1; then
if [ -w /etc/passwd ]; then
echo "anythingllm:x:$(id -u):0:AnythingLLM User:/app:/bin/bash" >> /etc/passwd
fi
fi
export HOME=/app
# Check if STORAGE_DIR is set
if [ -z "$STORAGE_DIR" ]; then
echo "================================================================"
echo "⚠️ ⚠️ ⚠️ WARNING: STORAGE_DIR environment variable is not set! ⚠️ ⚠️ ⚠️"
echo ""
echo "Not setting this will result in data loss on container restart since"
echo "the application will not have a persistent storage location."
echo "It can also result in weird errors in various parts of the application."
echo ""
echo "Please run the container with the official docker command at"
echo "https://docs.anythingllm.com/installation-docker/quickstart"
echo ""
echo "⚠️ ⚠️ ⚠️ WARNING: STORAGE_DIR environment variable is not set! ⚠️ ⚠️ ⚠️"
echo "================================================================"
fi
{
cd /app/server/ &&
# Disable Prisma CLI telemetry (https://www.prisma.io/docs/orm/tools/prisma-cli#how-to-opt-out-of-data-collection)
export CHECKPOINT_DISABLE=1 &&
npx prisma generate --schema=./prisma/schema.prisma &&
npx prisma migrate deploy --schema=./prisma/schema.prisma &&
node /app/server/index.js
} &
{ node /app/collector/index.js; } &
wait -n
exit $?