# Keep local values in env files

> The env files kindgi dev reads, Kindgi's own secrets file, what reaches your pack's code from them, and how to keep them out of git.

This guide keeps your pack's local settings and secrets in env files, and shows what reads each one under `kindgi dev`.

## Before you start

- **A pack** with `kindgi dev`, from Kindgi 0.1's CLI. This guide uses the quickstart's pack, `my-pack`.
- **A one-step flow that calls the tool,** to try it. This guide uses `my-pack.link`.

## Where they live

On your machine, a pack's settings live in env files at the pack's root: your app's own `.env`, then `.env.local`. The secrets you store with `kindgi secrets set` live in Kindgi's own file, `.kindgi/secrets.env`, which your app doesn't load. `kindgi dev` reads them in that order, and a name in more than one takes its value from the last: `.kindgi/secrets.env` wins.

`.env`

```sh
ORDERS_APP_URL=http://localhost:3000
```

`kindgi dev` says which files it read when it starts:

```text
  ✓ env files: .env, .env.local — 4 name(s) for the pack
```

## What reads them

Under `kindgi dev`, the env files are two things at once.

**The pack service's environment.** Your tools' code runs in the pack service, and the names in your app's env files are in its environment. Ask your coding agent, for example: "I want a tool that gives me the link to an order in our back office." Kindgi's skills in your pack tell it how ([How it knows Kindgi](https://docs.kindgi.com/v0.1.9/start/coding-agents.md)). For reference, the tool looks like this. It reads the back office's URL, `ORDERS_APP_URL`, from its environment:

- **TypeScript**

  `tools/order-link/index.ts`

  ```ts
  import { defineTool } from '@kindgi/sdk/define';
  import type { ToolId } from '@kindgi/sdk/types';
  import { z } from 'zod';

  const defined = defineTool({
    id: 'my-pack.order-link' as ToolId,
    description: 'Returns the link to an order in the back office.',
    version: '0.1.0',
    input: z.object({ orderId: z.string() }),
    output: z.object({ url: z.string() }),
    effects: [],
    mutating: false,
    handler: async ({ orderId }) => {
      const base = process.env.ORDERS_APP_URL;
      if (base === undefined) throw new Error('ORDERS_APP_URL is not set');
      return { url: `${base}/orders/${orderId}` };
    },
  });

  if (defined.kind === 'err') throw new Error(defined.error.message);
  export default defined.value;
  ```

- **Python**

  `tools/order_link.py`

  ```python
  import os

  from pydantic import BaseModel, Field

  from kindgi import tool


  class OrderRef(BaseModel):
      order_id: str = Field(alias="orderId")


  class Link(BaseModel):
      url: str


  @tool(id="my-pack.order-link", mutating=False)
  def order_link(ref: OrderRef) -> Link:
      """Returns the link to an order in the back office."""
      return Link(url=f"{os.environ['ORDERS_APP_URL']}/orders/{ref.order_id}")
  ```

- **Java**

  `src/main/java/mypack/tools/OrderLink.java`

  ```java
  package mypack.tools;

  import com.kindgi.pack.Tool;

  public final class OrderLink {
    public record Input(String orderId) {}

    public record Output(String url) {}

    public static final Tool<Input, Output> TOOL = Tool.define("my-pack.order-link")
        .description("Returns the link to an order in the back office.")
        .version("0.1.0")
        .input(Input.class)
        .output(Output.class)
        .mutating(false)
        .handler((input, ctx) -> {
          String base = System.getenv("ORDERS_APP_URL");
          if (base == null) throw new IllegalStateException("ORDERS_APP_URL is not set");
          return new Output(base + "/orders/" + input.orderId());
        });

    private OrderLink() {}
  }
  ```

- **Scala**

  `src/main/scala/mypack/tools/OrderLink.scala`

  ```scala
  package mypack.tools

  import com.kindgi.pack.scaladsl._

  object OrderLink {
    final case class Input(orderId: String)
    final case class Output(url: String)

    val tool: Tool[Input, Output] = Tool[Input, Output]("my-pack.order-link")
      .description("Returns the link to an order in the back office.")
      .version("0.1.0")
      .readOnly
      .handler { (input, _) =>
        val base = sys.env.getOrElse("ORDERS_APP_URL", throw new IllegalStateException("ORDERS_APP_URL is not set"))
        Output(s"$base/orders/${input.orderId}")
      }
  }
  ```

Call it from `my-pack.link`. `--flow` is the flow, and `--input` its input:

- **TypeScript**

  ```sh
  pnpm exec kindgi runs start --flow=my-pack.link --input='{"orderId":"ord_1001"}'
  ```

- **Python**

  ```sh
  uv run kindgi runs start --flow=my-pack.link --input='{"orderId":"ord_1001"}'
  ```

- **Java**

  ```sh
  ./kindgiw runs start --flow=my-pack.link --input='{"orderId":"ord_1001"}'
  ```

- **Scala**

  ```sh
  ./kindgiw runs start --flow=my-pack.link --input='{"orderId":"ord_1001"}'
  ```

```json excerpt
  "flowId": "my-pack.link",
  "flowVersion": "0.1.0",
  "status": "completed",
  …
  "output": {
    "url": "http://localhost:3000/orders/ord_1001"
  },
```

Nothing else reaches it. A variable exported in the shell you start `kindgi dev` from isn't in the pack service's environment, unless an env file refers to it, as [Refer to another value](https://docs.kindgi.com/v0.1.9/guides/secrets/env-files.md#refer-to-another-value) shows. A `KINDGI_*` name never is, from anywhere. Neither is a secret: no name from `.kindgi/secrets.env`, and no model provider's key, whichever file holds it. Save an env file and the pack service restarts with the new values.

### Refer to another value

A value can use another name's, as `${NAME}` or `$NAME`, with `${NAME:-fallback}` for when it's unset or empty:

`.env`

```sh
ORDERS_HOST=localhost:3000
ORDERS_APP_URL=http://${ORDERS_HOST}
ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY}
```

- **A name the env files define** takes the files' value, wherever it's defined.
- **Any other name** takes the value from the shell you start `kindgi dev` from.
- **A key that refers to itself,** like `ANTHROPIC_API_KEY` here, takes the shell's value too. This keeps the key out of the file, as a docker-compose `.env` does.

If the shell doesn't have it, the value is empty. `kindgi dev` warns as it starts, and so does `kindgi env list`:

```text
  ⚠ 1 warning(s):
      .env: ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY} takes the environment's ANTHROPIC_API_KEY, and the environment doesn't have it — expanded to ""
```

With the key exported in the shell, `kindgi env list` shows it set:

```text
    ANTHROPIC_API_KEY        sk****************al    [.env]
```

**The `local` secrets store.** Tools' declared secrets, HTTP tools' credentials, model providers' keys and MCP endpoints' credentials resolve in an environment. Under `kindgi dev`, it's `local`, and `local` is these files, Kindgi's own last. `kindgi secrets set … --env=local` writes to `.kindgi/secrets.env`, as [Store a secret](https://docs.kindgi.com/v0.1.9/guides/secrets/store-a-secret.md) shows. The file is owner-only, under `.kindgi/`, which `kindgi init` keeps out of git. It's in no file your app loads. A framework like Next.js or Vite loads `.env.local` into every route of your app, and your app has no use for a model key.

For a value your app reads too, such as a webhook signing secret, add `--app`. It writes your app's own env file instead, where both sides read it: the last of the files, `.env.local` by default.

- **TypeScript**

  ```sh
  pnpm exec kindgi secrets set ACME_WEBHOOK_SECRET --env=local --scope=tenant --app
  ```

- **Python**

  ```sh
  uv run kindgi secrets set ACME_WEBHOOK_SECRET --env=local --scope=tenant --app
  ```

- **Java**

  ```sh
  ./kindgiw secrets set ACME_WEBHOOK_SECRET --env=local --scope=tenant --app
  ```

- **Scala**

  ```sh
  ./kindgiw secrets set ACME_WEBHOOK_SECRET --env=local --scope=tenant --app
  ```

It prompts for the value, then prints `Set ACME_WEBHOOK_SECRET at tenant in local, in your app's env file.`

> **Caution**
>
> A value you put in your app's env files is also in the pack service's environment under `kindgi dev`: it's your app's configuration. A secret you store with `kindgi secrets` is not, and neither is a model provider's key, wherever it sits, which only its provider uses. A deployment gives the pack service only the names it declares. Read a secret from `ctx.secrets`, as [Give a tool a secret](https://docs.kindgi.com/v0.1.9/guides/tools/give-a-tool-a-secret.md) shows, never from the environment.

## A key your app's env files already hold

A model provider's key can sit in `.env` or `.env.local` already: put there before Kindgi kept its own file, or because your app calls the provider itself. Kindgi still uses it from there. `kindgi dev` says so once as it starts, naming the key and the file, never the value.

To give Kindgi its own copy:

- **TypeScript**

  ```sh
  pnpm exec kindgi secrets copy
  ```

- **Python**

  ```sh
  uv run kindgi secrets copy
  ```

- **Java**

  ```sh
  ./kindgiw secrets copy
  ```

- **Scala**

  ```sh
  ./kindgiw secrets copy
  ```

It copies the keys Kindgi's providers use from your app's env files into `.kindgi/secrets.env`, a line exactly as written, and merges: a name Kindgi's file already holds keeps Kindgi's value. To copy other keys, name them: `kindgi secrets copy ANTHROPIC_API_KEY`.

**It never edits or deletes anything in your app's files.** Whether the key stays there is your call: it's your app's file, and your app may use the key itself. A provider's SDK reads `ANTHROPIC_API_KEY` or `OPENAI_API_KEY` from the environment without your code naming it. `copy` says, for each key:

- that it's still in your app's file, and removing it is up to you;
- if your app uses the key itself, to keep it there;
- if the file is tracked by git, that the key is in your repository's history: rotate it with its provider.

**When your app calls the provider too, give each its own key.** Keep your app's key in its env file, and give Kindgi its own key under the same name in `.kindgi/secrets.env`, which takes precedence. Each spends on its own key. One shared key in your app's file works as well, but it stays readable by your app and by a coding agent working in it. So keep your agent out of those files, as [Your coding agent](https://docs.kindgi.com/v0.1.9/start/coding-agents.md#what-stays-with-you) shows.

## See what's set

List the values in your app's env files. `--env=local` is the environment `kindgi dev` serves:

- **TypeScript**

  ```sh
  pnpm exec kindgi env list --env=local
  ```

- **Python**

  ```sh
  uv run kindgi env list --env=local
  ```

- **Java**

  ```sh
  ./kindgiw env list --env=local
  ```

- **Scala**

  ```sh
  ./kindgiw env list --env=local
  ```

```text
  Environment: local
  Env file:    .env
  Env file:    .env.local
  Reveal:      NO (redacted; --reveal to show)

  4 key(s):
    HTTPBIN_TOKEN            de**********23    [.env.local]
    INVENTORY_TOKEN          in**************56    [.env.local]
    ORDERS_APP_URL           ht*****************00    [.env]
    RECEIPT_SIGNING_KEY      a6************************************************************83    [.env.local]
```

Values are redacted. `--reveal` prints them to a terminal, never to a pipe or a file. `kindgi env list` shows your app's env files. The names in Kindgi's own file are listed by `kindgi secrets list --env=local --scope=tenant`, never their values.

## Read other files

To read more of your app's files, list them in the pack's config, lowest precedence first. Kindgi's own `.kindgi/secrets.env` is always read last. Ask your coding agent, for example: "Have kindgi dev read my other env files too." For reference, it writes the list like this:

- **TypeScript**

  ```ts excerpt
  // in kindgi.config.ts
  dev: { envFiles: ['.env', '.env.development', '.env.local'] },
  ```

- **Python**

  ```toml excerpt
  # in pyproject.toml
  [tool.kindgi.dev]
  envFiles = [".env", ".env.development", ".env.local"]
  ```

- **Java**

  ```jsonc excerpt
  // in kindgi.config.json
  "dev": { "envFiles": [".env", ".env.development", ".env.local"] }
  ```

- **Scala**

  ```jsonc excerpt
  // in kindgi.config.json
  "dev": { "envFiles": [".env", ".env.development", ".env.local"] }
  ```

`kindgi dev` reads the list when it starts: restart it after a change.

```text
  ✓ env files: .env, .env.development, .env.local — 6 name(s) for the pack
```

## Keep them out of git

The files hold secrets. `kindgi init` adds `.kindgi/` and `.env.local` to your `.gitignore`. Make sure it covers every env file:

```text
.kindgi/
.env
.env.*
!.env.example
```

The Python template's `.gitignore` has these lines. A TypeScript pack made with `kindgi init` 0.1.0 has no `.gitignore`, because its lines went into `.npmignore`. Add one.

## Next

- [Store a secret](https://docs.kindgi.com/v0.1.9/guides/secrets/store-a-secret.md)
- [Declare the environment your code reads](https://docs.kindgi.com/v0.1.9/guides/secrets/pack-env.md)
- [Give a tool a secret](https://docs.kindgi.com/v0.1.9/guides/tools/give-a-tool-a-secret.md)
