Skip to content

fix(docker): set NODE_ENV=production in the runner stage - #6

Merged
Mudit-Lal merged 1 commit into
mainfrom
fix/node-env-production
Aug 19, 2026
Merged

Mudit-Lal merged 1 commit into
mainfrom
fix/node-env-production

Conversation

@Mudit-Lal

Copy link
Copy Markdown
Contributor

Why

docker/Dockerfile never sets NODE_ENV, and nothing else in docker/ sets it either — so every self-hosted Documenso built this way runs with it unset. Two consequences observed live on sign.devalok.in:

1. Session cookies were not marked Secure. packages/lib/constants/auth.ts:68:

export const useSecureCookies =
  env('NODE_ENV') === 'production' && String(NEXT_PUBLIC_WEBAPP_URL()).startsWith('https://');

NEXT_PUBLIC_WEBAPP_URL is https://sign.devalok.in, so the failing half was NODE_ENV. Result: cookies issued with no Secure flag, no __Secure- name prefix, and sameSite: 'lax' instead of 'none'. Verified against the running instance:

Set-Cookie: google_oauth_state=...; Max-Age=600; Domain=sign.devalok.in; Path=/; HttpOnly; SameSite=Lax

2. React Router served its development bundle. Stack traces in the deploy logs resolve to react-router/dist/development/chunk-EPOLDU6W.mjs — slower, higher memory, and verbose internals surfaced to clients.

Why the runner stage specifically

NODE_ENV=production as a Railway service variable is the obvious move and it breaks the build. Railway injects service variables at build time too, and docker/Dockerfile:80 runs npm ci in the installer stage — with NODE_ENV=production, npm omits devDependencies, and docker/Dockerfile:89 (turbo run build --filter=@documenso/remix...) fails immediately after.

The runner stage is past every build step and already installs with npm ci --only=production (line 118), so nothing there needs dev dependencies.

Side effect

The session cookie name changes from sessionId to __Secure-sessionId, so existing sessions are invalidated once when this deploys. Everyone signs in again — one time.

Fork hygiene

DEVALOK_FORK_NOTES.md gains a Fork-local code changes table and adds docker/Dockerfile to the fork-delta.patch capture. Without that, the next git read-tree -u --reset upstream/main sync would silently drop this line — which is exactly how the stale-file drift in #5 happened.

Worth upstreaming to documenso/documenso since it affects every self-hoster; until that lands, it lives here.

🤖 Generated with Claude Code

https://claude.ai/code/session_01DYNE3dT1E7bGHk8Y8joXMm

Upstream's Dockerfile never sets NODE_ENV, and nothing in docker/ sets it
either, so every self-hosted instance runs with it unset. Two consequences
on sign.devalok.in:

  - `useSecureCookies` in packages/lib/constants/auth.ts requires
    NODE_ENV === 'production', so it evaluated false: session cookies were
    issued without the Secure flag and without the __Secure- name prefix,
    and sameSite fell back to 'lax' instead of 'none'.
  - react-router resolved its *development* bundle at runtime (visible as
    react-router/dist/development/... in stack traces) -- slower, more
    memory, verbose errors surfaced to clients.

Set in the runner stage only, deliberately. Setting NODE_ENV as a Railway
service variable would also apply during the build, where `npm ci` in the
installer stage would omit devDependencies and break `turbo run build`
immediately after. The runner stage already installs with
`npm ci --only=production`, so nothing there needs dev deps.

Side effect: the session cookie name changes from `sessionId` to
`__Secure-sessionId`, so existing sessions are invalidated once on deploy.

DEVALOK_FORK_NOTES.md gains a "Fork-local code changes" table and adds
docker/Dockerfile to the fork-delta capture, so the next read-tree upstream
import doesn't silently drop this line.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DYNE3dT1E7bGHk8Y8joXMm
@Mudit-Lal
Mudit-Lal merged commit 91dcd9c into main Aug 19, 2026
7 checks passed
@Mudit-Lal
Mudit-Lal deleted the fix/node-env-production branch August 19, 2026 18:20
Mudit-Lal added a commit that referenced this pull request Sep 19, 2026
Third upstream sync, v2.17.0 -> v2.18.0 (134 files).

Motivation is a security fix we do not have: #3166 blocks SSRF via
IPv4-mapped IPv6 webhook URLs. Also picks up #3323 (libpdf upgrade),
#3309 (default unset signing transport to local, which is what this
instance runs) and #3351 (react router middleware).

One Prisma migration, additive and nullable:
20260818081941_add_api_token_last_used_property adds ApiToken.lastUsedAt.

docker/Dockerfile was hand-merged rather than patched. Upstream #3332
moved the image to node 24 / alpine 3.23, rewrote `ENV K V` into
`ENV K=V`, pinned turbo from the lockfile, switched the runner stage to
`npm ci --omit=dev` and strips esbuild residue. Our fork-local
`ENV NODE_ENV="production"` anchors on the two lines that rewrite
touched, so re-applying the patch would have conflicted; the block was
placed by hand in the runner stage instead. Without it the instance
serves React Router's development bundle and useSecureCookies stays
false, which is the dev-mode cookie bug fixed in #6.

Imported with `git read-tree -u --reset` per DEVALOK_FORK_NOTES.md, so
upstream deletions carry across: apps/remix/Dockerfile.bun and
apps/remix/Dockerfile.pnpm are removed. Workflow guards re-applied to 13
files / 14 jobs; upstream added and deleted no workflows this cycle. A
per-job audit leaves only the two intentionally open jobs,
ci.yml/build_docker and codeql-analysis.yml/analyze.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant