Why can __ENV be populated under k6 run but empty under k6 archive or k6 cloud run?
answer
- there is no process.env in k6
- two sources, different rules
- the default is per-command
- run says yes, archive says no
- a deliberate secret-leak guard
basics
~10 sBecause system environment variables reach __ENV by default only under k6 run. Every other k6 command defaults --include-system-env-vars to false, deliberately, so ambient secrets are not swept into an archive or a cloud upload.
solid answer
~40 sk6 has no `process.env`, because it runs JavaScript on **Sobek** rather than Node; the replacement is the global `__ENV` object, whose values are always strings. It is filled from two sources: every `-e VAR=value` flag, always, and the real process environment, but only when `--include-system-env-vars` is on. That setting defaults to **true for `k6 run` and false for every other command** — `k6 archive`, `k6 cloud run`, `k6 inspect`, `k6 deps` — precisely so a developer's or build agent's ambient secrets are not packaged up or uploaded. Nothing about the script changed; only which command asked for its `__ENV` did. The robust fix is to pass what the test needs with explicit `-e` flags so the values travel with the invocation.
code
bash · 5 linesexport TARGET_HOST=test.k6.io
k6 run script.js # __ENV.TARGET_HOST is set
k6 archive script.js # __ENV.TARGET_HOST is undefined
k6 archive -e TARGET_HOST=test.k6.io script.js # explicit: always worksgo deeper
Learn the shape first: k6 has no process.env, and configuration arrives in the global __ENV object as strings, usually supplied with the -e flag.
Explain the two sources. Explicit -e flags always apply, while the real process environment reaches __ENV only when system environment variables are included, which is not the default everywhere.
Diagnose it cleanly: when a value vanishes between k6 run and k6 archive or a cloud run, name the per-command default rather than hunting in the script, and know it exists to stop secrets being uploaded.
Set the convention. Decide whether tests may read ambient environment at all, or must take every input through explicit flags, and where genuine secrets live so they never end up in an archive or a log line.
## What `__ENV` is Because k6 runs JavaScript on **Sobek**, a Go engine with no Node layer, there is no `process` object and therefore no `process.env`. k6's replacement is a global object called `__ENV`, and a script reads configuration out of it exactly the way Node code reads `process.env`: ```javascript const host = __ENV.MY_HOSTNAME; ``` Its values are always **strings** — a numeric-looking setting still arrives as text and needs parsing. It is a plain JavaScript object, present in every VU's runtime. ## The two sources of its keys `__ENV` is filled from two places, and they behave differently: 1. **Explicit flags.** Every `-e VAR=value` (long form `--env VAR=value`) puts one key in, always, on every command that accepts the flag. Names must be plain identifiers — a letter or underscore followed by letters, digits or underscores — and k6 refuses to start with an `invalid environment variable name` error otherwise. 2. **The real process environment**, but only when system environment variables are included, which is where the surprise lives. ## The per-command default that catches people out The `--include-system-env-vars` setting does not have one default. It is enabled for `k6 run` and disabled for every other command, deliberately, so that ambient secrets in a developer's or a build agent's shell are not swept into an archive or shipped to the cloud: | command | system environment reaches `__ENV` by default? | |---|---| | `k6 run` | **yes** | | `k6 cloud run` | no | | `k6 archive` | no | | `k6 inspect` | no | | `k6 deps` | no | This is why a script that works perfectly on a laptop can read `undefined` the moment the same script is archived or handed to a cloud run: nothing about the script changed, only which command asked for its `__ENV`. The setting can be turned on explicitly where it is supported, but the safer fix is usually the other direction — pass what the script needs with `-e` so the value travels with the invocation instead of depending on the ambient shell. ## `-e` supplies the script; it does not configure k6 The single most common misreading of the flag is that it configures the run. It does not. `-e` only puts a key in `__ENV`; whether the script uses it is entirely up to the script. The documentation calls this out with its own example: `-e K6_ITERATIONS=120` places the string `"120"` under `__ENV.K6_ITERATIONS` and configures nothing, whereas exporting the same variable into k6's own process environment is a different mechanism entirely. Two mechanisms, similar-looking syntax, different effects. There is a second asymmetry worth holding alongside the first. The per-command default decides *whether the ambient environment is consulted at all*; the `-e` flag decides *what is added on top of whatever was consulted*. So a missing value is always one of two questions, and they are worth asking in that order: was the environment read for this command, and was the key supplied explicitly on the command line? ## Mutability and the `freeze-env` flag By default `__ENV` is an ordinary mutable object. An assignment such as `__ENV.MY_HOSTNAME = 'other'` succeeds and persists in that VU's runtime, which is occasionally convenient and more often a source of confusion, because one VU's mutation is invisible to the others. k6 v2 ships an opt-in `freeze-env` feature flag. With it enabled, each property is defined non-writable and non-configurable and the object is frozen, so the same assignment throws a `TypeError` in strict-mode code and fails silently outside it. Turning it on is a reasonable hardening step for a shared test suite where you want configuration to be genuinely read-only. ## Operating advice - Pass every value the test depends on with an explicit `-e`, so that `k6 run`, `k6 cloud run` and `k6 archive` all see the same inputs. - Never assume a variable exists: read it, check it, and fail loudly with a clear message rather than sending requests to `http://undefined/`. - Treat everything from `__ENV` as an untrusted string and convert it deliberately — `parseInt`, an explicit comparison against `'true'`, and so on. - Remember that secrets placed in the shell environment are exactly what the per-command default is protecting; if you find yourself enabling system environment variables for `k6 archive`, check whether the archive is about to be uploaded somewhere. - If a value is genuinely a secret rather than a setting, prefer a mechanism designed for secrets over stuffing it into `__ENV`, where it can end up in logs and archives.
- Does k6's -e flag configure the run as well as filling __ENV?No. `-e` only adds a key to `__ENV`; whether the script uses it is up to the script. The documented example is `-e K6_ITERATIONS=120`, which stores the string under `__ENV.K6_ITERATIONS` and configures nothing. Supplying the same name to k6's own process environment is a different mechanism with a different effect.
- Can a k6 script write to __ENV?By default yes — it is an ordinary mutable object, and an assignment persists in that VU's runtime while remaining invisible to other VUs. Enabling the `freeze-env` feature flag defines every property non-writable and freezes the object, so the assignment throws a `TypeError` in strict-mode code and fails silently otherwise.
- What restrictions apply to names passed with k6's -e flag?The name must be a plain identifier: a letter or underscore followed by letters, digits or underscores. Anything else, such as a name containing a dash or a dot, makes k6 refuse to start with an invalid environment variable name error rather than silently dropping it.
saying these in an interview costs you the question
- Expects process.env to work in a k6 script
- Assumes every k6 command inherits the shell environment
- Thinks -e configures k6 options rather than __ENV
- Treats __ENV values as numbers instead of strings
- Says __ENV is read-only in k6 by default