Secure process-only secret injection with Infisical CLI for Next.js and Payload CMS, CI, Docker, and rotation steps.
·Updated on:··
⚡ Next.js Implementation Guides
In-depth Next.js guides covering App Router, RSC, ISR, and deployment. Get code examples, optimization checklists, and prompts to accelerate development.
Infisical CLI lets you inject secrets straight into a running process instead of writing them to a .env file, and that one property shapes almost every command in this guide. Pin the CLI version, link your project once, upload your existing .env as a starting point, then switch to infisical run for local development, CI builds, and production runtime. Version 0.43.84 has one quirk worth knowing before you touch it: --silent does not stop the secrets set command from printing a result table full of secret values, so every upload command in this guide redirects output to /dev/null. This article walks through the full path, from a fresh clone to a rotated production credential, using a Next.js and Payload CMS project as the running example.
I set this up for a client project that runs Payload CMS on Next.js across dev, staging, and production, with GitHub Actions building the Docker image and a Compose file handling runtime. The team had been passing .env files around in Slack before this, which is exactly the failure mode Infisical exists to prevent. What follows is the workflow that replaced it, including the two mistakes that cost me the most time: assuming --silent actually silenced the CLI, and pointing a CI runner at localhost:8085.
Why process injection instead of a .env file
A .env file sitting on disk is a file that can get committed, screenshotted, or copied into a support ticket. infisical run -- <command> skips that step by fetching secrets and setting them as environment variables directly on the child process. The values live in memory for that process only. Reach for infisical export --output-file when you genuinely need a local file, such as feeding a tool that only reads dotenv files, and treat that as the exception rather than the default.
Platform configuration
localhost:8085 is a developer-facing local forward. CI runners and containers cannot reach it because localhost always resolves to the current host. Server bootstrap files need the approved HTTPS Infisical hostname instead, which shows up later in the CI and runtime sections.
Security rules to follow from the start
A few habits prevent most of the incidents that happen with secrets tooling:
Never commit , machine client secrets, access tokens, or exported values.
1. Install the CLI
Pin the version for reproducibility across every machine that touches the project.
Swap infisical for pnpm dlx @infisical/cli@0.43.84 in any command below if you'd rather not install globally.
2. Authenticate
Start the local Infisical forward, then log in:
bash
infisical login --domain=http://localhost:8085
This opens a browser callback. Complete it with your personal account. The CLI stores the session in its local credential vault and never writes the login token into the repository.
Confirm the session is active:
bash
infisical login status --domain=http://localhost:8085
If the browser callback doesn't fire on its own, open the URL the CLI prints in the terminal.
3. Link the repository
A checked-in .infisical.json links the repo to the right project and defaults to dev. To recreate that linkage from scratch:
bash
infisical init --domain=http://localhost:8085
Pick your organization, then your project, from the prompts. The resulting file holds project metadata only:
This creates any missing keys and updates the ones that already exist, including empty values. Comment lines in the source file are skipped automatically. The output redirect matters here: without it, the terminal fills with a table of your secret values despite --silent being set. Keep dev, staging, and production values independent; don't push the same .env into more than one environment.
5. Verify an upload without exposing values
Injecting secrets into a short Node script and checking for presence is safer than printing them:
Steer clear of printenv, env, infisical secrets --output=json, or piping an export to standard output for this check. Each of those prints the actual values.
6. Run the application with process-only injection
For day-to-day development:
bash
infisical run \
--domain=http://localhost:8085 \
--env=dev \
-- pnpm dev
If your package.json wraps this in a script (for example pnpm dev:infisical), use that instead. The same pattern works for any command that needs environment access:
bash
# Seed development data
infisical run \
--domain=http://localhost:8085 \
--env=dev \
-- pnpm db:seed
# Run catalog validation with the same environment
infisical run \
--domain=http://localhost:8085 \
--env=dev \
-- pnpm catalog:validate
The child process reads process.env normally. infisical run never touches .env on disk.
7. Pull secrets into a local .env when you actually need one
For anything sensitive, put the value in a permission-restricted temporary file rather than your shell history, or use the Infisical dashboard directly:
If your project carries DATABASE_URL_DIRECT from an older setup, rename it to DATABASE_UNPOOLED_URL. Migration scripts written against the newer name won't find the old one.
13. Validate staging or production keys
Run this from an authenticated developer machine, never from a CI runner pointed at localhost:
bash
infisical run \
--domain=http://localhost:8085 \
--env=staging \
-- bash deployment-templates/validate-infisical-env.sh staging runtime
infisical run \
--domain=http://localhost:8085 \
--env=prod \
-- bash deployment-templates/validate-infisical-env.sh production runtime
A good validator reports missing key names only and flags obviously invalid database hosts, without ever printing a value.
14. Machine identities for CI and runtime
Copying your personal login token onto a server defeats the point of having separate access levels. Create three Universal Auth identities instead:
Staging CI/build/migration — read-only access to staging, path /.
Production CI/build/migration — read-only access to prod, path /.
Production runtime — read-only access to , path .
Each identity gets its own bootstrap file on the target host:
Production runtime can go tighter still, with root:root ownership and mode 600.
15. Test a machine identity
From the target host:
bash
INFISICAL_BOOTSTRAP_FILE=/etc/infisical/website-platform-staging.env \
sh scripts/infisical-exec.sh \
bash deployment-templates/validate-infisical-env.sh staging runtime
A wrapper script like this one reads the root-protected bootstrap file, exchanges the Universal Auth client credential for a short-lived token, strips the long-lived credential out of the child environment, runs infisical run, and hands the application process its values.
16. Build-time injection with Docker BuildKit
CI (GitHub Actions or Bitbucket Pipelines) runs BuildKit inside the same exec wrapper. Infisical typically injects three build-time values:
BuildKit receives each one through --secret id=...,env=..., and the Dockerfile mounts them under /run/secrets/ only for the duration of pnpm build. They never become build arguments and never land in an image layer. If you're used to --build-arg, resist the pull toward it here:
text
--build-arg PAYLOAD_SECRET=...
Build arguments get baked into image metadata, which is the opposite of what a secret mount is for.
17. Runtime injection
Staging and production Compose files mount a single bootstrap credential:
text
/run/secrets/infisical_bootstrap
The image entrypoint authenticates to Infisical, fetches the target environment, drops privileges to a non-root user, and starts the server with the fetched values already in process.env. After changing any runtime secret, restart the container so it picks up the new value:
bash
docker compose restart app
18. Rotating a credential
Application credentials (database, external API keys)
Rotate the credential at its source.
Update the corresponding Infisical environment.
Restart or redeploy the application.
Check /api/health.
Revoke the old credential once the new one is confirmed working.
Updating Infisical without rotating the credential at its source leaves the old value live wherever it was issued.
Universal Auth client secrets
Generate a new client secret for that exact machine identity.
Update the matching /etc/infisical/*.env bootstrap file.
Restart the workload or rerun the validation wrapper.
Confirm injection succeeded and the application is healthy.
Revoke the old client secret.
Comparing the three secret-delivery paths
Troubleshooting
"Environment with slug 'demo' not found"
Use one of the environments actually configured for the project: dev, staging, or prod.
"Injecting 0 Infisical secrets"
The project, environment, and path all resolve, but nothing is accessible from them. Check the project linkage, the environment slug, the path (usually /), that secrets exist there, and that your account or machine identity has read access.
"Folder with path '/' ... was not found"
Either the environment slug is wrong, or that environment has never had a root secret or folder initialized. Recheck both.
"File does not exist"--file=.env needs a local file to already exist. Pull it fresh with infisical export --output-file=.env or recreate it from an approved source.
Login works, but commands hit Infisical Cloud instead of your self-hosted instance
Pass --domain=http://localhost:8085 on every command, or set it once for scripts:
bash
export INFISICAL_API_URL=http://localhost:8085
The local URL works, but CI or a container can't connectlocalhost always points at the current process's own host. CI runners and containers need the actual HTTPS Infisical hostname reachable from where they run, configured in the bootstrap file.
The CLI prints secret values despite --silent
Redirect the command's output:
If a value already made it into a saved log somewhere, rotate it.
Command quick reference
bash
# Login
infisical login --domain=http://localhost:8085
# Link repository
infisical init --domain=http://localhost:8085
# Upload .env to dev
infisical secrets set \
--domain=http://localhost:8085 \
--env=dev \
--path=/ \
--type=shared \
--file=.env \
--silent >/dev/null
# Run without writing .env
infisical run \
--domain=http://localhost:8085 \
--=dev \
-- pnpm dev
infisical \
--domain=http://localhost:8085 \
--=dev \
--path=/ \
--format=dotenv \
--output-file=. \
--silent >/dev/null
600 .
infisical run \
--domain=http://localhost:8085 \
--=staging \
-- bash deployment-templates/validate-infisical-env.sh staging runtime
FAQ
Does --silent actually hide secret values in Infisical CLI 0.43.84?
No, not for secrets set. That version prints a result table containing the values regardless of the flag. Redirect standard output to /dev/null on every upload or export command until you've confirmed a fix in a newer release.
Why does my CI runner fail to connect to http://localhost:8085?
Because localhost resolves to the runner's own container, not your development machine. CI and production hosts need the real HTTPS Infisical hostname configured in their bootstrap files.
Should I use one Universal Auth identity for both staging and production?
No. Provision a separate read-only identity for staging CI, production CI, and production runtime. If one leaks, the blast radius stays limited to a single environment.
What's the difference between infisical run and infisical export?infisical run injects secrets into a child process's memory and never writes them to disk. infisical export writes them into a real file, which is useful only when a tool genuinely requires a dotenv file to read from.
Can I pass a secret as a Docker build argument instead of a secret mount?
You can, but you shouldn't. Build arguments get recorded in image metadata. A BuildKit --secret mount exposes the value only inside the build step and leaves nothing behind in the image.
Setting this up once, with the right environment separation and machine identities, removes the temptation to pass a .env file around informally later. Let me know in the comments if you run into a rougher edge with a different CLI version, and subscribe for more practical development guides.
Thanks,
Matija
Setting
Value
Local Infisical URL
http://localhost:8085
Organization
Admin Org
Project
Website Platform
Project/workspace ID
00000000-0000-0000-0000-000000000000 (yours will differ)
Local project configuration
.infisical.json
Supported environments
dev, staging, prod
Default secret path
/
Pinned CLI version
0.43.84
.env
Use a personal Infisical account for development, and separate read-only Universal Auth machine identities for staging CI, production CI, and production runtime.
Keep development values out of staging and production; provision each environment independently.
Redirect every secrets set and export command's output, because CLI 0.43.84 prints values even with --silent set.
Skip shell tracing (set -x) anywhere near a secret command.
Rotate a credential immediately if it appears in a terminal transcript, CI log, screenshot, ticket, or chat message.
prod
/
Method
When to use
Trade-off
infisical run -- <command>
Local dev, CI steps, most day-to-day work
Values live only in the child process; nothing to clean up
infisical export --output-file
A tool that only reads dotenv files
Creates a real file on disk that needs chmod 600 and careful .gitignore handling
Bootstrap file + machine identity
CI runners and production containers
Requires provisioning a Universal Auth identity per environment, but keeps long-lived credentials off developer machines