The deployed console called its own origin instead of the platform. The BFF
route would throw "LOYALY_API_BASE is required in production — refusing to
guess the Loyaly platform host", and from the browser that reads as a broken
login form rather than as a missing variable.
The guard was right; nothing ever set the variable. `.gitignore` had a blanket
`.env*` and `.dockerignore` excluded `.env` and `.env.*`, so the image carried
no environment at all and the only copy of the production host was a comment
in `.env.example`. Injecting it by hand at the orchestrator was the single
point of failure, and it failed.
The platform host is not a secret, so it is now committed in `.env` and copied
into the runner stage. `next build` does not fold `.env` into
`.next/standalone`, which is why the COPY is explicit; server.js chdirs to
/app and Next runs loadEnvConfig there, so the file sits beside it at the
WORKDIR root. `npm run bundle` stages it the same way for a non-Docker deploy.
This pins nothing. @next/env never overwrites a variable already present in
process.env, so anything set in Dokploy still wins — verified against
@next/env directly: a bare image resolves https://mcp.loyaly.ai, an injected
LOYALY_API_BASE overrides it, and a leaked .env.local beats both.
That last case is why `.dockerignore` still excludes `.env.*`. A developer's
.env.local points at http://127.0.0.1:8088 and loads AHEAD of .env, so one
leaking into the build context would make the deployed console call localhost
with no error to read. Confirmed the context now carries `.env` and nothing
else.
AUTH_SECRET stays out of every committed file and out of the image. It signs
the session cookie and encrypts the token bundle, so a committed value is a
session-forging key in git — the thing 8b3fbab removed from the Dockerfile.
It remains a Dokploy secret, and production still refuses to sign without it.
`.env.example` is now the template for `.env.local` rather than a second copy
of the production values, so the two files cannot drift.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
38 lines
1.7 KiB
Plaintext
38 lines
1.7 KiB
Plaintext
# ---------------------------------------------------------------------------
|
|
# Template for `.env.local` — your LOCAL overrides. Copy it:
|
|
#
|
|
# cp .env.example .env.local
|
|
#
|
|
# Do not copy it to `.env`. `.env` is committed and already holds the
|
|
# production values; `.env.local` is loaded ahead of it and is gitignored.
|
|
# ---------------------------------------------------------------------------
|
|
|
|
# The one shared Loyaly platform API (Behavision). Server-side only and
|
|
# deliberately NOT NEXT_PUBLIC: publishing the host would let a browser bypass
|
|
# the BFF, which is what keeps the access token out of JavaScript.
|
|
#
|
|
# local dev http://127.0.0.1:8088 ← what belongs in .env.local
|
|
# production https://mcp.loyaly.ai ← already set in the committed .env
|
|
#
|
|
# NOT platform.loyaly.ai — that host serves THIS console, not the API. Pointing
|
|
# the variable there makes the BFF call its own origin, which fails in a way
|
|
# that looks like a broken login form rather than a misconfiguration.
|
|
#
|
|
# There is no remote fallback: production refuses to serve without this set.
|
|
# That is why it is committed in `.env` rather than left to a dashboard.
|
|
LOYALY_API_BASE=http://127.0.0.1:8088
|
|
|
|
# Signs the session cookie and encrypts the platform token bundle.
|
|
#
|
|
# The ONLY variable that is a real secret, and the only one production takes
|
|
# solely from the environment — it is in no committed file, by design. Set it
|
|
# as a Dokploy environment variable / secret. Locally, any string works; leave
|
|
# it blank and a development key is used.
|
|
#
|
|
# Generate with: openssl rand -base64 48
|
|
AUTH_SECRET=
|
|
|
|
# Browser → this app's own BFF routes. Same origin, so leave it empty. Inlined
|
|
# at BUILD time (NEXT_PUBLIC), so changing it at runtime does nothing.
|
|
NEXT_PUBLIC_API_BASE=
|