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

# Configure

> Configure environment variables, AI models, email, and database

## Environment configuration

Basedash is configured with environment variables. With Docker Compose, set them in the environment template in the Distr portal (or your `.env` file). With Kubernetes, set the equivalent Helm values instead; run `helm show values oci://registry.distr.sh/basedash/basedash-charts` to list them.

### Quick start: generate secrets

```bash theme={"dark"}
# CRYPTO_KEY — exactly 32 bytes
openssl rand -hex 16

# PRISMA_FIELD_ENCRYPTION_KEY
npx @47ng/cloak generate

# POSTGRES_PASSWORD — use hex, not base64. Characters like +, /, and = break DATABASE_URL.
openssl rand -hex 24
```

For Distr-managed deployments, store `LICENSE_KEY`, `POSTGRES_PASSWORD`, `CRYPTO_KEY`, and `PRISMA_FIELD_ENCRYPTION_KEY` as customer secrets in the Distr portal before the first deploy. The environment template reads them from there.

Store `CRYPTO_KEY` and `PRISMA_FIELD_ENCRYPTION_KEY` somewhere safe. They encrypt data in the database, and changing them after you've saved data makes that data unreadable.

### Required variables

**Public URL:**

`BASE_URL` must be a full URL including `http://` or `https://`. The app refuses to start with a bare hostname like `basedash.example.com`.

```bash theme={"dark"}
# Direct to the app on the default port (plain HTTP)
BASE_URL=http://basedash.example.com:3000

# HTTPS on 443 (reverse proxy, load balancer, or ingress)
BASE_URL=https://basedash.example.com
```

`BASE_URL` is used for login redirects, email links, OAuth and SSO callbacks, share links, and webhooks, so it must be the address your users actually open.

**License:**

The server won't start without a valid key. Contact [support@basedash.com](mailto:support@basedash.com) if you don't have one.

```bash theme={"dark"}
LICENSE_KEY=your-license-key-here
```

**Database:**

For the bundled Compose PostgreSQL, set `POSTGRES_PASSWORD`. Compose derives `DATABASE_URL` from `POSTGRES_USER`, `POSTGRES_PASSWORD`, and `POSTGRES_DB`, using host `postgres` (the service name), not `localhost`. Inside the app container, `localhost` is the app itself.

```bash theme={"dark"}
POSTGRES_PASSWORD=your_secure_password_here
# Derived. Leave this unless you use an external PostgreSQL.
# DATABASE_URL=postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@postgres:5432/${POSTGRES_DB}
```

**Encryption keys:**

```bash theme={"dark"}
# Encrypts database passwords and configuration overrides.
# Must be exactly 32 bytes. Generate with: openssl rand -hex 16
CRYPTO_KEY=your_crypto_key_here

# Encrypts sensitive database fields. Format: k1.aesgcm256.[base64-string]
# Generate with: npx @47ng/cloak generate
PRISMA_FIELD_ENCRYPTION_KEY=your_prisma_encryption_key_here
```

**App image (manual Compose files only):**

Compose files downloaded from the Distr portal already pin the app image, so you don't need this. If you use your own Compose file that reads `APP_IMAGE`, set it to a release, never `latest`:

```bash theme={"dark"}
APP_IMAGE=registry.distr.sh/basedash/charts-self-hosted-app:2026.09.30
```

### Optional application settings

```bash theme={"dark"}
# Log level: fatal, error, warn (default), info, debug, trace
LOG_LEVEL=warn

# Log format: standard (default), or gcp on Google Cloud Run / GKE
LOG_FORMAT=standard

# Port published on the host. Default access is http://<host>:3000
APP_PORT=3000

# Number of reverse proxy hops in front of the app. See TLS and reverse proxy.
# TRUST_PROXY=1

# Private address the app uses to call itself (for example, to render chart
# images). Only set this if the app can't reach its own public BASE_URL.
# INTERNAL_BASE_URL=http://basedash:3000
```

## TLS and reverse proxy

HTTPS isn't automatic. The app listens on port **3000** over plain HTTP.

**Direct access (default):** open inbound TCP 3000 and use `BASE_URL=http://<host>:3000`.

**Reverse proxy (recommended for production):** terminate TLS on 443 with your existing proxy or load balancer (nginx, Cloudflare, AWS ALB, Kubernetes ingress) and forward to port 3000. Then set:

```bash theme={"dark"}
BASE_URL=https://basedash.example.com
TRUST_PROXY=1
```

Set `TRUST_PROXY` to the number of proxies in front of the app (usually `1`; `2` for a load balancer in front of another proxy). This applies to Docker Compose; the Helm chart doesn't expose it yet. Without it, Google sign-in redirects break and rate limiting sees every request as coming from the proxy. Don't use `TRUST_PROXY=true`, because it lets any client forge its IP address.

Cloudflare doesn't proxy port 3000, so put Cloudflare in front of a proxy on 443, or connect to the server directly.

**Bundled Caddy:** the Compose file includes an optional Caddy service for existing deployments that already use it. It's off by default and the Distr agent doesn't start it. For new deployments, use your own proxy.

## PostgreSQL database

PostgreSQL is the only service Basedash depends on.

### Bundled PostgreSQL

Docker Compose deployments (agent and manual) include a PostgreSQL 16 container:

* **Migrations** run automatically every time the app starts
* **Health checks** hold the app until the database is ready
* **Persistent storage** in a Docker volume

The bundled database uses these variables:

```bash theme={"dark"}
POSTGRES_DB=basedash_charts
POSTGRES_USER=basedash
POSTGRES_PASSWORD=your_secure_password_here
POSTGRES_PORT=5432
```

Postgres only reads `POSTGRES_PASSWORD` the first time it initializes the data volume. Changing the variable later doesn't change the database password. Reset it inside the container (`ALTER USER basedash WITH PASSWORD '...'`) or recreate the volume.

### External PostgreSQL

For production above 500 users, use a managed PostgreSQL instance (Amazon RDS, Cloud SQL, Azure Database for PostgreSQL). See [Sizing](https://basedash.com/docs/self-hosting/deploy.md#sizing).

1. Use **PostgreSQL 16 or later**.

2. Create the database:

   ```sql theme={"dark"}
   CREATE DATABASE basedash_charts;
   ```

3. Set `max_connections` to at least 60 per app replica, plus 20.

4. Point `DATABASE_URL` at it (with Helm, set `postgres.enabled: false` and `postgres.external.url`). This overrides the derived Compose URL:

   ```bash theme={"dark"}
   DATABASE_URL=postgresql://user:password@your-postgres-host:5432/basedash_charts
   ```

   Percent-encode reserved URL characters in the password (`+`, `/`, `=`, `@`).

Migrations run automatically on first startup.

### Database backups

Back up the database regularly, and always before upgrading. For managed databases, use your provider's automated backups. For the bundled Compose database:

```bash theme={"dark"}
# Create backup
docker compose exec postgres pg_dump -U basedash basedash_charts > backup.sql

# Restore backup
docker compose exec -T postgres psql -U basedash basedash_charts < backup.sql
```

## AI configuration

AI is optional on first boot. The app starts without a key and shows a banner until you add one. Chat, chart generation, and insights need a provider key.

Supported providers: OpenAI, Anthropic, Azure OpenAI, Amazon Bedrock, xAI, and OpenRouter. Choose models with `AI_MODEL` (main agent) and `AI_FAST_MODEL` (lightweight tasks), using a provider prefix such as `openai/` or `anthropic/`. If you leave them unset, Basedash picks defaults from the keys you've configured.

### OpenAI

```bash theme={"dark"}
AI_OPENAI_API_KEY=sk-your-openai-api-key

# Optional. Defaults: openai/gpt-6.1-sol and openai/gpt-6-luna
# AI_MODEL=openai/gpt-6.1-sol
# AI_FAST_MODEL=openai/gpt-6-luna

# Optional: organization and project
# AI_OPENAI_ORGANIZATION_ID=org-your-org-id
# AI_OPENAI_PROJECT_ID=proj_your-project-id
```

### Anthropic

```bash theme={"dark"}
AI_ANTHROPIC_API_KEY=sk-ant-your-api-key

# Optional. Defaults when Anthropic is the only provider:
# AI_MODEL=anthropic/claude-opus-4-8
# AI_FAST_MODEL=anthropic/claude-haiku-4-5
```

If you set both OpenAI and Anthropic keys, OpenAI models are the default.

### Azure OpenAI

Point the OpenAI provider at your Azure OpenAI v1 endpoint. This supports every AI feature, including web search, chat attachments, and voice input.

```bash theme={"dark"}
AI_OPENAI_API_KEY=your-azure-api-key
AI_OPENAI_BASE_URL=https://your-resource-name.openai.azure.com/openai/v1
```

Name each Azure deployment exactly like the model it serves: `gpt-6.1-sol`, `gpt-6-luna`, and `gpt-transcribe` for the defaults, or whatever you set in `AI_MODEL` and `AI_FAST_MODEL`.

Existing deployments using `AI_MODEL=azure/<deployment-name>` with `AI_AZURE_OPENAI_*` variables keep working, but that setup doesn't support web search, uploads, or voice input.

### Amazon Bedrock

```bash theme={"dark"}
AI_MODEL=amazon-bedrock/anthropic.claude-sonnet-5
AI_BEDROCK_REGION=us-east-1

# Optional. Omit to use the AWS credential chain (for example, an IAM role).
# AI_BEDROCK_ACCESS_KEY_ID=
# AI_BEDROCK_SECRET_ACCESS_KEY=
```

### xAI

```bash theme={"dark"}
AI_XAI_API_KEY=your-xai-api-key
AI_MODEL=xai/grok-4.6
```

### OpenRouter

```bash theme={"dark"}
AI_OPENROUTER_API_KEY=your-openrouter-api-key
AI_MODEL=openrouter/provider/model
```

### OpenAI-compatible APIs

Any OpenAI-compatible endpoint (such as an internal LLM gateway) works through the OpenAI provider:

```bash theme={"dark"}
AI_OPENAI_API_KEY=your-api-key
AI_OPENAI_BASE_URL=https://llm-gateway.internal.example.com/v1
AI_MODEL=openai/your-model-name
```

### Model considerations

* We test and benchmark against the default models. Other models may produce lower-quality SQL and charts.
* Some features need specific model capabilities, such as tool calling and structured output.
* Older variable names (`OPENAI_API_KEY`, `OPENAI_MODEL`, `ANTHROPIC_API_KEY`, `AI_PROVIDER`) still work, but the `AI_*` variables above take precedence.

## Email configuration

Email delivers login codes, invitations, alerts, automation deliveries, and dashboard subscriptions.

### First boot: log provider

The Distr templates set `EMAIL_PROVIDER=log`. Login codes are written to the app logs instead of being emailed, so you can sign in before email is set up. Switch to SMTP or SES before inviting users: anyone who can read the logs can read login codes.

```bash theme={"dark"}
EMAIL_PROVIDER=log
```

Read codes with `docker compose logs app` (or `kubectl logs` on Kubernetes).

### SMTP

```bash theme={"dark"}
EMAIL_PROVIDER=smtp

SMTP_HOST=smtp.your-provider.com
SMTP_PORT=587
SMTP_USER=your-smtp-username
SMTP_PASSWORD=your-smtp-password
SMTP_SECURE=false
SMTP_FROM_EMAIL=noreply@your-domain.com
```

Use port 587 with `SMTP_SECURE=false` (STARTTLS), or port 465 with `SMTP_SECURE=true`. Compose defaults `SMTP_SECURE` to `true`, so set it explicitly when you use port 587.

**Common SMTP providers:**

* **Resend**: smtp.resend.com:587
* **SendGrid**: smtp.sendgrid.net:587
* **Mailgun**: smtp.mailgun.org:587
* **AWS SES**: email-smtp.us-east-1.amazonaws.com:587
* **Microsoft 365**: smtp.office365.com:587

### Amazon SES

```bash theme={"dark"}
EMAIL_PROVIDER=ses

SES_ACCESS_KEY_ID=your-access-key-id
SES_SECRET_ACCESS_KEY=your-secret-access-key
SES_REGION=us-east-1
SES_FROM_EMAIL=noreply@your-domain.com
```

Make sure your SES account is out of sandbox mode and `SES_FROM_EMAIL` is a verified identity.

## Authentication and access control

Self-hosted Basedash has the same access controls as the cloud product:

* **SSO**: SAML and OIDC with your identity provider. See [SSO](https://basedash.com/docs/administration/sso.md).
* **SCIM provisioning**: provision and deactivate members and sync groups from Okta, Microsoft Entra ID, and other identity providers. Generate a SCIM token under **Settings → Security**.
* **Permissions**: groups and per-dashboard, chart, and data source access.
* **Audit logs**: available to Enterprise organizations.

## License key

Self-hosted Basedash requires a license key from Basedash. Contact [support@basedash.com](mailto:support@basedash.com) to get one.

```bash theme={"dark"}
LICENSE_KEY=your-license-key-here
```

### License behavior

**At startup:**

* The server validates the key against `https://charts.basedash.com` before it accepts requests.
* If the key is missing, invalid, expired, or inactive, or the license server can't be reached, the server refuses to start.

**While running:**

* The server sends a heartbeat every 6 hours with the app version and organization and user counts, for billing.
* If a heartbeat fails, it retries with exponential backoff, starting at 5 minutes and capped at 6 hours.
* If the license expires or is deactivated, an error banner appears and new organizations can't be created.
* If the license server can't be reached, a warning banner appears and new organizations can't be created until the next successful heartbeat. Existing users keep working.

### Sandbox licenses

Sandbox licenses are for QA and CI environments. They show a "Sandbox environment" banner, and their usage isn't billed.

### Troubleshooting license issues

**Server won't start:** check the app logs for the license error.

* Missing key: set `LICENSE_KEY` and restart.
* Can't reach the license server: allow outbound HTTPS to `charts.basedash.com` (check firewalls, proxies, and egress rules).
* Invalid, expired, or inactive: contact [support@basedash.com](mailto:support@basedash.com).

**"Unable to verify license" banner:** the server couldn't reach `charts.basedash.com` during a heartbeat. Check outbound HTTPS access. The server retries automatically.

**"License has expired" or "License has been deactivated" banner:** contact [support@basedash.com](mailto:support@basedash.com).

## Update management

Basedash never updates your deployment automatically. You choose when to move to a new version:

* **Distr agent (Compose or Kubernetes):** select a new version in the Distr portal.
* **Manual Compose:** download the new version's Compose file from the Distr portal, then run `docker compose pull && docker compose up -d`.
* **Manual Helm:** set `app.image.tag` to the new release and run `helm upgrade` with the matching chart version.

Before upgrading, back up the database and read the release notes attached to each version in the Distr portal. Some releases include migrations that can't be rolled back. For strict change control, upgrade a staging instance first.

## Advanced configuration

### Object storage (recommended)

Configure S3-compatible storage to enable:

* Avatar and organization icon uploads
* Chart images in Slack and email notifications and in the public API (without storage, images are rendered on demand and some notifications omit them)
* CSV downloads generated by the AI

```bash theme={"dark"}
S3_BUCKET_NAME=your-bucket-name
S3_BUCKET_ACCESS_KEY_ID=your-access-key
S3_BUCKET_SECRET_ACCESS_KEY=your-secret-key
S3_BUCKET_REGION=us-east-1
S3_BUCKET_ENDPOINT=https://s3.amazonaws.com
```

Works with AWS S3, Google Cloud Storage, DigitalOcean Spaces, MinIO, and other S3-compatible services. Uploaded images are served from `https://{S3_BUCKET_NAME}.{endpoint host}`.

**Bucket access:**

* Basedash marks avatars, icons, and chart images `public-read` itself. Don't make the whole bucket public.
* Keep the `ai-csv-artifacts/` prefix private, including through any CDN. It holds query results.
* Grant the credentials get, put, delete, and list access, plus multipart upload listing and abort.
* Add a lifecycle rule on `ai-csv-artifacts/` only that expires objects after 30 days and aborts incomplete multipart uploads after 1 day.

> [accordion] Google Cloud Storage setup
>
> Follow these steps to use Google Cloud Storage (GCS) through its S3-compatible API.
>
> ## Step 1: Create a bucket
>
> 1. Go to the [Google Cloud Console](https://console.cloud.google.com/)
> 2. Navigate to **Cloud Storage** → **Buckets**
> 3. Click **Create bucket**
> 4. Configure the bucket:
>    * **Name**: choose a globally unique name (e.g., `basedash-uploads-yourcompany`)
>    * **Location type**: Regional is typically fine
>    * **Region**: close to your Basedash deployment
>    * **Storage class**: Standard
>    * **Access control**: **Fine-grained** (required, because Basedash sets per-object ACLs)
>    * **Public access prevention**: **uncheck** "Enforce public access prevention on this bucket", so per-object `public-read` ACLs work
> 5. Click **Create**
>
> Don't grant `allUsers` access on the bucket. That makes every object public, including AI-generated CSV files.
>
> ## Step 2: Create HMAC keys
>
> 1. Go to **Cloud Storage** → **Settings**
> 2. Open the **Interoperability** tab
> 3. Under "Access keys for service accounts," click **Create a key for a service account**
> 4. Select or create a service account with the **Storage Object Admin** role on the bucket
> 5. Click **Create key** and save both values:
>    * **Access key**: your `S3_BUCKET_ACCESS_KEY_ID`
>    * **Secret**: your `S3_BUCKET_SECRET_ACCESS_KEY`
>
> ## Step 3: Configure CORS
>
> Browsers upload files directly with pre-signed URLs, so the bucket needs a CORS rule for your Basedash URL. Create `cors.json`:
>
> ```json theme={"dark"}
> [
>   {
>     "origin": ["https://your-basedash-domain.com"],
>     "method": ["GET", "PUT", "HEAD"],
>     "responseHeader": ["Content-Type", "Content-Length", "x-amz-acl"],
>     "maxAgeSeconds": 3600
>   }
> ]
> ```
>
> Apply it:
>
> ```bash theme={"dark"}
> gcloud storage buckets update gs://YOUR_BUCKET_NAME --cors-file=cors.json
> ```
>
> ## Step 4: Set environment variables
>
> ```bash theme={"dark"}
> S3_BUCKET_NAME=your-bucket-name
> S3_BUCKET_ENDPOINT=https://storage.googleapis.com
> S3_BUCKET_REGION=auto
> S3_BUCKET_ACCESS_KEY_ID=GOOG1E...  # Your HMAC access key
> S3_BUCKET_SECRET_ACCESS_KEY=...    # Your HMAC secret
> ```
>
> ## Step 5: Verify
>
> 1. Upload a user avatar or organization icon
> 2. Check that the file appears in your bucket
> 3. Open the file's public URL directly

### Slack app (optional)

Enable Slack notifications, automations, and chatting with Basedash from Slack. Set all five values:

```bash theme={"dark"}
SLACK_CLIENT_ID=your-slack-client-id
SLACK_CLIENT_SECRET=your-slack-client-secret
SLACK_SIGNING_SECRET=your-signing-secret
SLACK_BOT_TOKEN=xoxb-your-bot-token
SLACK_OAUTH_STATE_SECRET=your-random-secret # openssl rand -hex 32
```

## Support and troubleshooting

### Getting help

* **Distr customer portal**: deployment status, versions, and release notes
* **Email**: [support@basedash.com](mailto:support@basedash.com)

### Health checks

```bash theme={"dark"}
# Application health endpoint (returns 200 when ready)
curl http://localhost:3000/health

# Database connectivity
docker compose exec postgres pg_isready -U basedash

# Application logs
docker compose logs -f app

# Database logs
docker compose logs -f postgres
```

On Kubernetes, use `kubectl logs -l app.kubernetes.io/component=app`.

### Common configuration issues

**Application won't start:**

1. Check that all required variables are set: `LICENSE_KEY`, `CRYPTO_KEY`, `PRISMA_FIELD_ENCRYPTION_KEY`, `POSTGRES_PASSWORD`, and `BASE_URL`
2. With bundled PostgreSQL, leave `DATABASE_URL` derived (host `postgres`, not `localhost`). Override it only for an external database
3. Make sure `BASE_URL` includes `http://` or `https://`
4. Make sure `CRYPTO_KEY` is exactly 32 bytes (`openssl rand -hex 16`)
5. Make sure the server can reach `charts.basedash.com` over HTTPS
6. Read the logs: `docker compose logs app`

**Postgres password authentication failed after changing env:**

Postgres only applies `POSTGRES_PASSWORD` when the data volume is first created. Reset it inside the container (`ALTER USER basedash WITH PASSWORD '...'`) so it matches `POSTGRES_PASSWORD`, or recreate the volume (this deletes all data). If you overrode `DATABASE_URL`, its password must match too.

**Browser never loads, but Distr shows healthy:**

Distr's health check calls `/health` inside the container, so it can pass while the app is unreachable from outside. Check:

1. `curl http://127.0.0.1:3000/health` on the host
2. Inbound TCP 3000 is open in your security group or firewall
3. You're using `http://<host>:3000`, not `https://`
4. If the hostname is on Cloudflare, port 3000 isn't proxied. Try the server IP, or turn off proxying for the record

**"Something went wrong" on first page load:**

Expand the error message on that screen or check the app logs. The usual cause is a `BASE_URL` without `http://` or `https://`.

**Google sign-in redirects to `http://` or fails behind a proxy:**

With Docker Compose, set `TRUST_PROXY` to the number of proxies in front of the app (usually `1`).

**AI features not working:**

1. Set a provider key (for example `AI_OPENAI_API_KEY`) and, if you override models, a matching provider-prefixed `AI_MODEL`
2. Check the provider account's credits and rate limits
3. Check that the server can reach the provider's API endpoint

**Email not arriving:**

1. If `EMAIL_PROVIDER=log`, codes are in the app logs, not in email
2. For SMTP, check the host, port, and credentials
3. Match `SMTP_SECURE` to the port: `false` for 587, `true` for 465
4. Make sure the firewall allows outbound SMTP

**Connecting a database shows "Database connection not found":**

`CRYPTO_KEY` must be exactly 32 bytes. Generate one with `openssl rand -hex 16`, restart the app, and connect again. You can safely replace the key only if no database connection has been saved yet.


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