fix(deploy): ship the production environment instead of injecting it
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>
This commit is contained in:
@@ -27,6 +27,14 @@ for p in .next/standalone .next/static public; do
|
||||
fi
|
||||
done
|
||||
|
||||
# .env is the production environment, not a secret — LOYALY_API_BASE lives in
|
||||
# it and the server reads it at boot. It is tracked in git, so a missing one
|
||||
# means the tree is wrong, not that this deploy opted out.
|
||||
if [ ! -f .env ]; then
|
||||
echo "error: .env missing — it is committed; restore it with 'git checkout .env'" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
rm -rf "$STAGE"
|
||||
mkdir -p "$STAGE"
|
||||
|
||||
@@ -37,6 +45,10 @@ mkdir -p "$STAGE/.next"
|
||||
cp -R .next/static "$STAGE/.next/static"
|
||||
cp -R public "$STAGE/public"
|
||||
|
||||
# Beside server.js, which is where Next's loadEnvConfig looks. NOT .env.local —
|
||||
# that is the dev override and would point the deployed server at 127.0.0.1.
|
||||
cp .env "$STAGE/.env"
|
||||
|
||||
TARBALL="$OUT/loyaly-mer-login.tar.gz"
|
||||
rm -f "$TARBALL"
|
||||
tar -czf "$TARBALL" -C "$STAGE" .
|
||||
@@ -46,4 +58,7 @@ echo "bundle: $(du -sh "$STAGE" | cut -f1) ($STAGE)"
|
||||
echo "tarball: $(du -sh "$TARBALL" | cut -f1) ($TARBALL)"
|
||||
echo
|
||||
echo "deploy: scp $TARBALL <host>:/srv/ && tar -xzf loyaly-mer-login.tar.gz -C /srv/app"
|
||||
echo "run: PORT=3000 HOSTNAME=0.0.0.0 NODE_ENV=production node server.js"
|
||||
echo "run: AUTH_SECRET=... PORT=3000 HOSTNAME=0.0.0.0 NODE_ENV=production node server.js"
|
||||
echo
|
||||
echo "note: LOYALY_API_BASE ships in the bundled .env. AUTH_SECRET does not —"
|
||||
echo " it signs sessions and must come from the host's environment."
|
||||
|
||||
Reference in New Issue
Block a user