> ## Documentation Index
> Fetch the complete documentation index at: https://docs.outerlayer.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Destinations and secrets

> Let a build reach a private service through the runner, without the build holding the secret.

A build sometimes needs a service that wants a credential, such as a private
package registry or a test API. A destination lets the build use it without
holding the secret. The runner serves the service on a local address and
forwards each request to the service's HTTPS address. It sets the credential
itself, so the secret never enters the build.

```mermaid theme={"system"}
flowchart LR
    B["Build"] -- "http://127.0.0.1:3129/<br/>no credential" --> R["Runner"]
    R -- "HTTPS, header set<br/>from the runner's environment" --> U["The service"]
```

List destinations in `runner.build.destinations`:

```json theme={"system"}
{
  "runner": {
    "build": {
      "destinations": [
        {
          "name": "npm-private",
          "upstream": "https://npm.acme.dev/",
          "header": "Authorization: Bearer ${NPM_TOKEN}",
          "env": {
            "NPM_CONFIG_REGISTRY": "{local}",
            "NPM_CONFIG_REPLACE_REGISTRY_HOST": "always"
          }
        }
      ]
    }
  }
}
```

| Key | Meaning |
| - | - |
| `name` | The name the build's record shows. 1 to 63 lower-case letters, digits and hyphens, starting with a letter or digit. At most 18 destinations; `gateway` and `claude` are taken. |
| `upstream` | The service's HTTPS address, with no login, query or fragment in it. Its path is put in front of each request's path. |
| `header` | `Name: value`, set on every forwarded request. The value may name variables of the runner's own environment as `${NAME}`. |
| `env` | Variables that point the build's tools at the destination. In a value, `{local}` is replaced by the destination's local address. |

## How the runner forwards a request

The runner drops any credential the build sends and sets the destination's
header itself. It reads the `${NAME}` variables from its own environment. It
refuses to start while one is unset, and so does `outerlayer runner check`.

Under the service `runner init` sets up, the runner's environment is
`runner.env` beside the config file. Put each secret there, then restart the
service, which reads the file only when it starts:

```bash theme={"system"}
echo "NPM_TOKEN=<token>" >> ~/.outerlayer/runner.env
sudo systemctl restart outerlayer-runner
```

`outerlayer runner check` reads the Claude credential from `runner.env`
beside the config when your shell has none. It checks no other variable in
that file; see
[Check the host](/set-up-a-host#check-the-host-before-it-takes-work).

The build's record (`outerlayer work status --json`, under `claims`) names
the destinations a build called, never a secret. A destination may point at
a private address; the tunnel's address rules do not apply to it.

The local address depends on how the build runs:

* A [container or microVM build](/container-builds) reaches the first
  destination in the list at `http://127.0.0.1:3129/`, the second at
  `3130`, and so on.
* A `builtin:process` build reaches it on a loopback port the runner chooses.
* With your own hooks, a command run through an `exec` uses the same fixed
  ports. The hook's relay must forward each one with `--forward`, as
  [The provision hook](/write-your-own-hooks#the-provision-hook) shows. A
  command with no `exec` uses the loopback port.

<Note>
  The runner does not rewrite answers. A tool that follows an absolute URL in
  an answer leaves the local address and reaches the upstream with no
  credential. Set the tool to stay on the local address, such as npm's
  `NPM_CONFIG_REPLACE_REGISTRY_HOST=always` above.
</Note>

## Settings for common tools

Each row is a destination with the tool's own variable in `env`:

| Tool | `header` | `env` | Notes |
| - | - | - | - |
| Go module proxy | `Authorization: Bearer ${GOPROXY_TOKEN}` | `"GOPROXY": "{local}"` | Set `GONOSUMDB` to a private module's path prefix. |
| Maven | `Authorization: Basic ${MAVEN_BASIC}` | `"MAVEN_MIRROR_URL": "{local}"` | Set a mirror `<url>${env.MAVEN_MIRROR_URL}</url>` in a `settings.xml` committed with the recipe, and pass it with `-s`. |
| pip | `Authorization: Basic ${PIP_BASIC}` | `"PIP_INDEX_URL": "{local}simple/"` | Serve files from the index's own address; links to another host get no credential. |
| A test API | `X-Api-Key: ${STAGING_API_KEY}` | `"TEST_API_URL": "{local}"` | The build's tests call `$TEST_API_URL`; the key stays on the host. |

## When a secret must be a variable

A destination works only when the tool can be pointed at another address.
The runner does not terminate TLS for the build's own requests. So a tool that
only reaches the service's real HTTPS address cannot use one, such as a SaaS
client with the address compiled in.

For that tool, name the secret in
[`build.variables`](/container-builds#the-build-block). It is then in the
build's environment, readable by everything the build runs. The build's
record names the variable, never its value.

<Warning>
  A secret in `build.variables` is as exposed as it would be in a build that
  runs as the host's user. Give it the narrowest credential the tool accepts.
</Warning>

A variable may not be both a header variable and a `build.variables` name.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.