The self-hosted stack reads its configuration from a single `.env` file next to
`compose.yml`. The [configurator](/docs/self-hosting/configurator) is the
authoritative source of the field list, defaults, and validation — it always
matches the release you installed. This page explains how the configuration is
organized and the variables you are most likely to set by hand.

The configurator groups variables into sections:

| Section | What it covers |
|---|---|
| Basic | Core runtime — environment, deployment mode, Transactional Email sender and Resend key. |
| Database | Bundled Postgres or an external database. |
| Cache | Bundled Redis or an external cache. |
| Solana RPC | Network and RPC endpoint. |
| Authentication (Clerk) | Required Clerk keys and JWT settings. |
| Signing provider | Which signer(s) to enable and their credentials. |
| Fee payment | How transaction fees are paid. |
| Secrets | App secrets, generated locally. |
| Advanced | Image source, ports, and internal service URLs. |

## Required variables

These must be set or the stack will refuse to start:

| Variable | Description |
|---|---|
| `POSTGRES_PASSWORD` | Password for the bundled Postgres. Always required — the bundled `postgres` service starts even when the API points at an external `DATABASE_URL`. |
| `API_KEY_PEPPER` | Server-side pepper for API key hashing. Generate with `openssl rand -hex 32`. |
| `CUSTODY_ENCRYPTION_KEY` | Encryption key for custody material at rest. Generate with `openssl rand -base64 32`. |
| `NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY` | Clerk publishable key for the web app. |
| `CLERK_SECRET_KEY` | Clerk secret key for the API. |
| `CLERK_ISSUER` | Clerk issuer URL used to validate tokens. |
| `SOLANA_RPC_URL` | Solana RPC endpoint the API calls. |
| `SIGNING_PROVIDER` | The default signing provider. Each non-local provider adds its own required credentials. |

The configurator generates `API_KEY_PEPPER` and `CUSTODY_ENCRYPTION_KEY` for you
and can auto-generate `POSTGRES_PASSWORD`.

## Transactional Email

SDP-owned Transactional Email uses Resend. Set `EMAIL_FROM` to the sender address
and `RESEND_API_KEY` to the Resend API key before enabling product flows that send
email. Organization member invitation emails are handled by Clerk and do not use
these settings.

## Database

In the configurator, `DATABASE_MODE` chooses between the bundled Postgres and an
external database. It is a generation-time selector and is not written to `.env`.

For the bundled database, set `POSTGRES_DB`, `POSTGRES_USER`, and
`POSTGRES_PASSWORD`; the API and migration services build their connection string
from them. To use an external database, set `DATABASE_URL` to your own
`postgresql://…` string and the API and migrations connect there instead. Note
that `compose.yml` always starts the bundled `postgres` container — with an
external `DATABASE_URL` it simply goes unused, and `POSTGRES_PASSWORD` is still
required for it to start.

The configurator hides `POSTGRES_PASSWORD` once you select an external database
but still emits an auto-generated value for it, since the bundled `postgres`
container needs one to start even when nothing connects to it.

## Cache

Like `DATABASE_MODE`, `CACHE_MODE` is a configurator-only selector and is not
written to `.env`. To use an external cache, set `REDIS_URL` to your own
`redis://…` endpoint; the bundled `redis` container still starts but goes unused.

## Signing providers

`SIGNING_PROVIDER` is the default signer written to `.env`. Supported values are
`local`, `fireblocks`, `privy`, `coinbase_cdp`, `para`, `turnkey`, and `utila`.
The configurator also tracks a `SIGNING_PROVIDERS` selection to decide which
fields to show, but that key is configurator-only and is not written to `.env`.
Each non-local provider requires its own credentials — for example Fireblocks
needs an API key, secret, and vault ID — and the configurator only prompts for
the providers you enable.

For provider-managed co-signer runtimes that live outside the default stack, see
[External co-signers](/docs/self-hosting/external-co-signers).

## Advanced

| Variable | Default | Description |
|---|---|---|
| `SDP_IMAGE_REGISTRY` | `ghcr.io/solana-foundation/sdp` | Registry the service images are pulled from. |
| `SDP_VERSION` | `latest` | Image tag for every SDP service. Pin this to a release tag in production. |
| `SDP_API_PORT` | `8787` | Host port for the API. |
| `SDP_WEB_PORT` | `3000` | Host port for the dashboard. |
| `SDP_DOCS_PORT` | `3001` | Host port for these docs. |

`SDP_API_BASE_URL` defaults to the in-network service name `http://sdp-api:8787`
for server-to-server calls. The browser-facing `NEXT_PUBLIC_*` URLs default to
`localhost` (for example `http://localhost:8787` and `http://localhost:3000`) and
need changing when you put the services behind your own domains or ingress.