Web Container Docker Configuration#
The Dify web container packages a Next.js (and optionally vinext) frontend into a minimal Alpine-based image. The key files are:
| File | Role |
|---|---|
web/Dockerfile | Multi-stage build definition |
web/docker/entrypoint.sh | Runtime env mapping + server selection |
web/next.config.ts | Next.js standalone output config |
web/scripts/copy-and-start.mjs | Dev-mode static copy + subprocess launcher |
Multi-Stage Build#
The Dockerfile has four stages on top of node:24.20.0-alpine:
baseβ Enablescorepack/pnpm, setsPNPM_HOME, accepts theNEXT_PUBLIC_BASE_PATHbuild arg (the only variable baked in at image build time).packagesβ Installs workspace dependencies withpnpm install --frozen-lockfile --ignore-scripts. The--ignore-scriptsflag skips lifecycle scripts during Docker dependency installation, preventing development-only scripts (like the rootprepare: vp confighook setup) from requiring Git during image builds.builderβ SetsNODE_OPTIONS="--max-old-space-size=4096"and runspnpm build && pnpm build:vinextto produce both the Next.js and vinext outputs.productionβ Copies compiled outputs, sets default environment values, creates the non-rootdifyuser, and declares the entrypoint.
Build outputs are copied into two parallel trees :
targets/next/βweb/.next/standalone(Next.js standalone server)targets/vinext/βweb/dist/standalone(vinext server)
Next.js standalone output is enabled by output: 'standalone' in next.config.ts.
Non-Root User#
The production stage creates a dedicated system user and group dify with UID/GID 1001 (configurable via the dify_uid build arg) . All /app files are chown-ed to this user, and the final USER dify directive ensures the container process runs unprivileged . The entrypoint script itself is copied with --chmod=755 so it remains executable under the non-root user .
Entrypoint Behavior#
entrypoint.sh runs with set -e and does two things:
1. Environment variable mapping β Docker-native variable names are translated into NEXT_PUBLIC_* equivalents that the running Node server exposes to the client :
| Docker variable | β NEXT_PUBLIC_* variable |
|---|---|
DEPLOY_ENV | NEXT_PUBLIC_DEPLOY_ENV |
EDITION | NEXT_PUBLIC_EDITION |
CONSOLE_API_URL | NEXT_PUBLIC_API_PREFIX (appended with /console/api) |
APP_API_URL | NEXT_PUBLIC_PUBLIC_API_PREFIX (appended with /api) |
MARKETPLACE_API_URL | NEXT_PUBLIC_MARKETPLACE_API_PREFIX (appended with /api/v1) |
SENTRY_DSN | NEXT_PUBLIC_SENTRY_DSN |
TEXT_GENERATION_TIMEOUT_MS | NEXT_PUBLIC_TEXT_GENERATION_TIMEOUT_MS |
Feature-flag variables (e.g., MARKETPLACE_ENABLED, ALLOW_REGISTER) use shell fallback syntax ${NEXT_PUBLIC_FOO:-${PLAIN_FOO}}, so either form works at runtime .
2. Server selection β At the end of the script, a conditional selects the server to exec :
EXPERIMENTAL_ENABLE_VINEXT=trueβexec node /app/targets/vinext/server.js- Otherwise (default) β
exec node /app/targets/next/web/server.js
Using exec replaces the shell process with the Node process, so Node becomes PID 1 and receives OS signals directly.
Next.js Standalone Server Process Model#
In production the Node process (server.js) is the direct container process (PID 1) β there is no intermediate process manager. The standalone build bundles all required Node modules so no node_modules install is needed at runtime.
Default environment values set in the production stage :
| Variable | Default |
|---|---|
NODE_ENV | production |
EDITION | SELF_HOSTED |
DEPLOY_ENV | PRODUCTION |
CONSOLE_API_URL | http://127.0.0.1:5001 |
APP_API_URL | http://127.0.0.1:5001 |
MARKETPLACE_API_URL | https://marketplace.dify.ai |
PORT | 3000 |
EXPERIMENTAL_ENABLE_VINEXT | false |
NEXT_TELEMETRY_DISABLED | 1 |
Port 3000 is exposed via EXPOSE 3000 .
copy-and-start.mjs β Dev/Local Use#
web/scripts/copy-and-start.mjs is not used by the Docker image. It is the dev/local next start replacement (described in its header comment as "intended to be used as a replacement for next start") .
It:
- Locates the standalone
server.jsby checkingSTANDALONE_ROOT_CANDIDATES(.next/standalone/webthen.next/standalone) . - Copies
.next/staticandpublic/into the standalone root β the step that the Dockerfile handles at build time via explicitCOPYinstructions . - Spawns the server as a child process (not
exec) via Node'schild_process.spawn, inheriting stdio and forwarding the exit code . This means the parent script holds the process and the actual server runs as a subprocess β different from the production container where Node is PID 1.
The listening host and port are resolved from pnpm_config_host/pnpm_config_port with fallback to HOSTNAME/PORT .