Which file does k6's --config layer read by default, and when does a missing file fail the run?
answer
- a file you never asked for
- OS config directory, k6 subfolder
- missing default is silent
- named but missing is fatal
basics
~20 sk6 reads config.json from the operating system's configuration directory on every run and ignores it silently when absent. --config or K6_CONFIG names a different path; if that named file is missing, the run exits 104.
solid answer
~40 sThe JSON configuration file is layer 2 of k6's five, just above the built-in defaults. With no flag, k6 still looks for `config.json` in a `k6/` folder inside the OS configuration directory — `${HOME}/.config/k6/config.json` on Unix-like systems, `${HOME}/Library/Application Support/k6/config.json` on macOS, `%AppData%/k6/config.json` on Windows. `K6_CONFIG` changes that path and `--config` / `-c` overrides the variable. The asymmetry to remember: a missing file at the **default** path is silently ignored, but a missing file you named explicitly fails with `failed to load the configuration file from the local file system` and exit code 104. The file uses the same key names as the exported `options` object.
code
bash · 11 lines# Read implicitly on every run when it exists; silent when it does not:
# ~/.config/k6/config.json (Unix-like)
# ~/Library/Application Support/k6/config.json (macOS)
k6 run script.js
# K6_CONFIG moves the path; -c beats K6_CONFIG.
K6_CONFIG=./ci.json k6 run script.js
k6 run -c ./k6.config.json script.js
# Named but absent -> config load fails, exit code 104.
k6 run --config ./does-not-exist.json script.js; echo $?go deeper
Remember that --config, short form -c, points k6 at a JSON file whose keys match the options object, and that this file is the lowest layer you can actually set — only defaults sit below it.
Explain the default lookup path and the K6_CONFIG variable, and that -c wins over the variable. Know the file keys are the same names as the exported options keys.
Diagnose the machine-specific run: the default config.json is read unasked on every run, so check it before blaming a script, and know a named-but-missing file exits 104 rather than degrading.
Decide whether the team relies on the implicit per-machine file at all, or mandates an explicit -c path from the repository so no untracked file joins the merge.
## The config-file layer, precisely The JSON configuration file is layer 2 of k6's five: above the built-in defaults, and below the script's exported `options`, the `K6_*` variables and the CLI flags. Anything it sets is therefore the easiest thing in the whole chain to override — and also the easiest to forget is there at all. ## Where k6 looks k6 reads a configuration file on **every** run, whether or not you asked for one. With no flag, it looks for `config.json` inside a `k6/` directory in the operating system's configuration directory: | OS | Default path | |---|---| | Unix-like | `${HOME}/.config/k6/config.json` | | macOS | `${HOME}/Library/Application Support/k6/config.json` | | Windows | `%AppData%/k6/config.json` | Two things move that path, in this order: 1. `K6_CONFIG=/path/to/options.json` in the environment. 2. `--config /path/to/options.json` (short form `-c`), which wins over the variable. `--config` is a persistent flag, so it is available on `k6 run` and `k6 cloud run` alike. ## Missing file: silent or fatal, depending on who named it This is the asymmetry worth remembering: - If the path is the **default** one and the file does not exist, k6 says nothing and moves on. That is the normal case for a machine that has never written a k6 config. - If **you** named the path — via `--config` or via `K6_CONFIG` — and the file is not there, loading fails. k6 reports `failed to load the configuration file from the local file system` and exits **104**, the invalid-configuration exit code. It does not fall back to the default path and it does not create the file. The same 104 comes back for a file that exists but holds an unparseable value, for example `{"duration": "fails"}`. ## What goes in it The file is plain JSON using the **same key names as the exported `options` object** — `vus`, `duration`, `stages`, `thresholds`, `scenarios`, `hosts`, `userAgent`, and so on: - Keys that the environment and CLI layers cannot express, such as `scenarios` and `ext`, *can* live here. - k6 writes this file itself when you authenticate against Grafana Cloud, so on a real workstation it often already exists with a token in it. - Because it can hold that token, k6 creates the file with owner-only permissions (`0600`) inside a directory created `0700`, and tightens the permissions on an existing looser file the next time it writes. ## The lowest layer can still own the workload Being layer 2 sounds harmless until you remember that `duration`, `iterations`, `stages` and `scenarios` are resolved as one execution group. The file loses that group the moment a higher layer names any member of it — but if no higher layer names one, the file's workload is the run's workload, in full: 1. A file holding `{"stages": [{"duration": "20s", "target": 10}]}` and a script that sets only `vus: 5` produce a `ramping-vus` scenario starting at 5 VUs and ramping to 10 over 20 seconds. The script never mentioned stages. 2. Add `K6_ITERATIONS=17` to the same run and the file's stages vanish entirely, replaced by a `shared-iterations` scenario — the group rule at work across layers 2 and 4. 3. A file holding a full `scenarios` map behaves identically: it runs when nothing above touches the group, and disappears the instant something does. So the file layer is weak per key and complete per workload. Reading only the script tells you what a run did **only** when you also know no config file contributed. ## Why a stale file bites A `config.json` left over from an experiment months ago participates in every subsequent `k6 run` on that machine, invisibly, with no flag on the command line to hint at it. It cannot beat the script's `options` for a key the script sets — but it can supply keys the script never mentions, and if it happens to contain `stages` or `scenarios` while the script sets none of the four execution keys, it decides the whole workload. Practical habits: - On a shared or CI machine, prefer an explicit `-c ./k6.config.json` checked into the repository over the implicit default path. - When a run behaves differently on one laptop, look at that laptop's default `config.json` before suspecting the script. - Do not commit the default file itself if it has ever been through a cloud login — it may hold a token.
- How does a stale k6 config.json change a run if the script sets the same keys?For a key the script sets, it cannot — the config file is layer 2 and the script is layer 3. The damage comes from keys the script never mentions. If the script sets none of the execution group and the stale file holds `stages` or `scenarios`, that file decides the whole workload while nothing on the command line hints at it.
- Why is the k6 config file created with owner-only permissions?Because it can hold a Grafana Cloud token: `k6 cloud login` writes the token into the same `config.json`. k6 therefore creates the file `0600` inside a directory created `0700`, and tightens permissions on an existing looser file the next time it writes one.
It behaves like a shell startup file: nobody mentions it on the command line, it is read every single time, and a line someone added months ago quietly shapes today's run.
saying these in an interview costs you the question
- Thinks k6 only reads a config file when --config is passed
- Expects a missing --config path to be skipped with a warning
- Places the config file above the script in precedence
- Guesses K6_CONFIG_FILE or K6_CONFIG_PATH as the variable name
- Believes k6 creates the config file when the path is missing