All questions

Self-hosted Next.js standalone build in Docker, Croct env vars behave differently than on Vercel

Asked by Marek Dvorak on

MDMarek Dvorak

We self-host everything, no PaaS. Next.js with output: 'standalone' in Docker.

The app ID comes up undefined in the browser even though the container definitely has NEXT_PUBLIC_CROCT_APP_ID set. I checked inside the running container, the variable is there.

It works if I bake the value into the image at build time, which I dislike because now the image is environment-specific. Why does the runtime variable get ignored, and is there a way around baking it in

Was this helpful?

2 answers

OHOmar Haddad

This is Next.js behavior, not Croct. Anything prefixed NEXT_PUBLIC_ is inlined into the client bundle when next build runs. By the time your container starts, the value (or lack of it) is already frozen in the JavaScript files. Setting it at runtime does nothing for the browser bundle, which is exactly what you observed.

So the variable must be present at build time. If you want one image per environment, pass it as a build arg:

ARG NEXT_PUBLIC_CROCT_APP_IDENV NEXT_PUBLIC_CROCT_APP_ID=$NEXT_PUBLIC_CROCT_APP_IDRUN npm run build

The important split: CROCT_API_KEY is different. It is read server-side at runtime, never ships to the browser, and can stay a proper container secret injected at deploy time. Only the public app ID has the build-time constraint.

Was this helpful?
HWHannah Weiss

Worth noting the environment-specific image is less of a problem here than it feels. The app ID is public by design, it is visible in the browser bundle anyway, so baking it in leaks nothing. If each Croct environment (dev/prod) has its own application ID, one image per environment actually maps cleanly to that model.

Keep CROCT_API_KEY out of the image though. It needs the "Issue user tokens" permission and should live in your secret store, injected at runtime like Omar showed.

Was this helpful?
MDMarek Dvorak

Fine, I can live with build args for a value that is public anyway. API key stays in the secret store. Case closed.

Still have questions?