# Declare the environment your code reads

> List the environment variables your pack's code needs, so kindgi dev warns about a missing one and a deployment injects exactly those.

This guide lists the environment variables your pack's code reads, so `kindgi dev` warns when a required one has no value and a deployment injects exactly those.

## Before you start

- **A pack** with `kindgi dev`, from Kindgi 0.1's CLI.

## Declare the names

Your tools' code, and the libraries it imports, read settings from the process environment: a service URL, a database your app owns. The pack declares which names it reads. Ask your coding agent: "Declare the environment variables my code reads." Kindgi's skills in your pack tell it how ([How it knows Kindgi](https://docs.kindgi.com/v0.1.8/start/coding-agents.md)). For reference, it adds them to the pack's config like this:

- **TypeScript**

  ```ts excerpt
  // in kindgi.config.ts
  env: {
    required: ['ORDERS_APP_URL', 'ORDERS_DB_URL'],
    optional: ['ORDERS_API_TIMEOUT_MS'],
  },
  ```

- **Python**

  ```toml excerpt
  # in pyproject.toml
  [tool.kindgi.env]
  required = ["ORDERS_APP_URL", "ORDERS_DB_URL"]
  optional = ["ORDERS_API_TIMEOUT_MS"]
  ```

- **Java**

  ```jsonc excerpt
  // in kindgi.config.json
  "env": {
    "required": ["ORDERS_APP_URL", "ORDERS_DB_URL"],
    "optional": ["ORDERS_API_TIMEOUT_MS"]
  }
  ```

- **Scala**

  ```jsonc excerpt
  // in kindgi.config.json
  "env": {
    "required": ["ORDERS_APP_URL", "ORDERS_DB_URL"],
    "optional": ["ORDERS_API_TIMEOUT_MS"]
  }
  ```

* **`required`**: names the pack service can't work without.
* **`optional`**: names it reads when they're set.
* Names are environment variable names. `KINDGI_*` names are Kindgi's own settings and are refused:

```text
    ✗ refresh failed [config-invalid] kindgi.config: "KINDGI_X" in `env.required`: `KINDGI_*` names configure Kindgi, not the pack
```

Your code reads them as usual: `process.env.ORDERS_APP_URL`, `os.environ["ORDERS_APP_URL"]`, `System.getenv("ORDERS_APP_URL")` in Java, or `sys.env("ORDERS_APP_URL")` in Scala. The `order-link` tool in [Keep local values in env files](https://docs.kindgi.com/v0.1.8/guides/secrets/env-files.md#what-reads-them) reads one.

## On your machine

`kindgi dev` gives the pack service the env files' values, and warns about a required name that has none:

```text
    ⚠ the pack's env.required name ORDERS_DB_URL has no value: add it to the pack's env files (a deployment won't be ready without it)
```

It warns and carries on, so the rest of the pack keeps working while you fill it in. `kindgi dev` doesn't reload on a change to a TypeScript pack's `kindgi.config.ts` or a Java or Scala pack's `kindgi.config.json`: save a tool or restart it. A Python pack reloads when `pyproject.toml` changes.

## In a deployment

The declaration is part of the pack's index, so the pack service in an image knows what it needs:

- **Only declared names are injected.** The pack service gets each declared name that has a value for that environment, and nothing else. [Set values per environment](https://docs.kindgi.com/v0.1.8/guides/secrets/per-environment-values.md) shows where those values come from, and `kindgi env plan` says when one isn't declared: `environments.staging.env.FEATURE_FLAGS isn't declared in env.required or env.optional, so it isn't injected`.

- **The pack service keeps only the declared names.** However it's started, such as with `docker run --env-file` or a Terraform input with a key added by hand, it removes every other variable from its environment before your code loads. So a model key or a password meant for something else never reaches a tool, or a process a tool starts. Kindgi's own `KINDGI_*` settings and the platform's stay: the process's basics, the language runtime's settings, `PORT`, proxies and certificates, and Cloud Run's, AWS's and Azure's workload identity and metadata. A static credential such as `AWS_SECRET_ACCESS_KEY` isn't the platform's: declare it if your code reads it. The service names what it removed, never a value:

  ```json
  {"time":"2026-10-10T07:01:47.789Z","level":"warn","severity":"WARNING","subsystem":"pack","message":"Dropped 2 variables the pack doesn't declare: ACME_UNDECLARED_KEY, AWS_SECRET_ACCESS_KEY (declare them in the pack's env, or set KINDGI_PACK_ENV_FILTER=off)","event":"env-dropped","kind":"env-dropped","names":["ACME_UNDECLARED_KEY","AWS_SECRET_ACCESS_KEY"]}
  ```

  A Python pack's image always names `GPG_KEY` there: its base image sets it, and nothing reads it. `KINDGI_PACK_ENV_FILTER=off` on the pack service keeps every variable. `kindgi dev` runs with it off, since there the pack service gets your env files. The [environment variable reference](https://docs.kindgi.com/v0.1.8/reference/env-vars.md#kindgi_pack_env_filter) has the setting.

- **A required name with no value keeps the pack service from serving.** It starts, says which names are missing, and answers `503` until they're set:

```text
{"kind":"missing-env","check":"strict","names":["ORDERS_DB_URL"]}
```

```json
{"error":"missing env","missingEnv":["ORDERS_DB_URL"]}
```

`KINDGI_PACK_ENV_CHECK=warn` on the pack service makes it serve anyway and only report the names, as `kindgi dev` does. The [environment variable reference](https://docs.kindgi.com/v0.1.8/reference/env-vars.md#kindgi_pack_env_check) has the setting.

## What doesn't go here

A secret that belongs to a tenant, such as an API key a customer gives you, isn't part of the pack's environment: one pack service serves every tenant. A tool declares it and reads it from its context, as [Give a tool a secret](https://docs.kindgi.com/v0.1.8/guides/tools/give-a-tool-a-secret.md) shows.

## Next

- [Set values per environment](https://docs.kindgi.com/v0.1.8/guides/secrets/per-environment-values.md)
- [Keep local values in env files](https://docs.kindgi.com/v0.1.8/guides/secrets/env-files.md)
- [Give a tool a secret](https://docs.kindgi.com/v0.1.8/guides/tools/give-a-tool-a-secret.md)
