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

# Deployment Options

> Install Bag of Words on your own server using Docker or Kubernetes

## Install with Docker

***

You can install Bag of Words with a single docker command. By default, it will use SQLite as the database.
You can also configure it to use PostgreSQL by passing `BOW_DATABASE_URL` environment variable.

```bash theme={null}
docker run --pull always -d -p 3000:3000 bagofwords/bagofwords
```

<Info>
  To use PostgreSQL, you need to set the `BOW_DATABASE_URL` environment variable.
  For example: `BOW_DATABASE_URL=postgresql://postgres:postgres@localhost:5432/bagofwords`
</Info>

### Update

* Re-run the same `docker run --pull always ...` command to fetch and start the latest image.
* Optionally, pull explicitly and restart:
  ```bash theme={null}
  docker pull bagofwords/bagofwords:latest
  # stop/remove your existing container if needed, then start again
  # docker stop <container_name> && docker rm <container_name>
  docker run --pull always -d -p 3000:3000 bagofwords/bagofwords
  ```

## Install with Docker Compose

***

Run Bag of Words with Docker Compose and Caddy (built-in TLS on port 443). We recommend using the canonical files from the repo to avoid drift:

* `docker-compose.yaml`: [https://github.com/bagofwords1/bagofwords/blob/main/docker-compose.yaml](https://github.com/bagofwords1/bagofwords/blob/main/docker-compose.yaml)
* `Caddyfile`: [https://github.com/bagofwords1/bagofwords/blob/main/Caddyfile](https://github.com/bagofwords1/bagofwords/blob/main/Caddyfile)

### Steps

1. Make sure Docker and Docker Compose are installed.
2. Clone the repo:
   ```bash theme={null}
   git clone https://github.com/bagofwords1/bagofwords
   cd bagofwords
   ```
3. Create a `.env` file (for domain and credentials). Example:
   ```env theme={null}
   # Domain used by Caddy for HTTPS (must resolve to your server's public IP)
   DOMAIN=yourdomain.com
   # PostgreSQL (use stronger values for production)
   POSTGRES_USER=bow
   POSTGRES_PASSWORD=your_secure_pw
   POSTGRES_DB=bagofwords
   # Optional but recommended: encryption key (Fernet, 44 chars incl. '=')
   # Generate with OpenSSL: openssl rand -base64 32 | tr '+/' '-_'
   BOW_ENCRYPTION_KEY=
   ```
   Generate `BOW_ENCRYPTION_KEY` with OpenSSL:
   ```bash theme={null}
   openssl rand -base64 32 | tr '+/' '-_'
   ```
4. Start services:
   ```bash theme={null}
   docker compose up -d
   ```
5. Point your domain to the server's public IP:
   * Create an A record for `yourdomain.com` → your instance public IP.
   * Caddy will automatically obtain/renew the TLS certificate and serve on port 443.
6. Open `https://yourdomain.com`

<Info>
  Caddy is included by default as a reverse proxy on port 443. If you prefer to run without Caddy, remove the `caddy` service from `docker-compose.yaml` and expose the app directly on port 3000. For local, no-SSL testing, you can also use `docker-compose.dev.yaml`.
</Info>

### Update

```bash theme={null}
# pull latest images and recreate containers
docker compose pull
docker compose up -d
```

## Air-gapped downloads

Download the bundle on a connected machine, then transfer it to your offline environment. Choose the architecture of the destination Linux host: `amd64` for Intel/AMD or `arm64` for ARM.

Each bundle includes the Bag of Words, PostgreSQL, and Caddy container images, Docker Compose configuration, and an installation README. The destination host needs Docker Engine and Docker Compose v2 installed.

### Download URL pattern

Find a version in [GitHub Releases](https://github.com/bagofwords1/bagofwords/releases/latest). Use the version number without the leading `v` and replace `{arch}` with `amd64` or `arm64`:

```text theme={null}
https://s3.us-west-2.amazonaws.com/bagofwords.com/releases/{version}/bagofwords-airgap-{version}-linux-{arch}.tar.gz
```

Append `.sha256` to the download URL for its checksum file. The architecture-independent source archive uses:

```text theme={null}
https://s3.us-west-2.amazonaws.com/bagofwords.com/releases/{version}/bagofwords-source-{version}.tar.gz
```

The source archive contains tracked source code and a `SOURCE_COMMIT` file identifying the build commit; it does not include Python or Node.js dependencies.

### Example: version 0.0.587

| Package | Archive | Checksum |
| - | - | - |
| Linux AMD64 (Intel/AMD) | [Download](https://s3.us-west-2.amazonaws.com/bagofwords.com/releases/0.0.587/bagofwords-airgap-0.0.587-linux-amd64.tar.gz) | [SHA256](https://s3.us-west-2.amazonaws.com/bagofwords.com/releases/0.0.587/bagofwords-airgap-0.0.587-linux-amd64.tar.gz.sha256) |
| Linux ARM64 | [Download](https://s3.us-west-2.amazonaws.com/bagofwords.com/releases/0.0.587/bagofwords-airgap-0.0.587-linux-arm64.tar.gz) | [SHA256](https://s3.us-west-2.amazonaws.com/bagofwords.com/releases/0.0.587/bagofwords-airgap-0.0.587-linux-arm64.tar.gz.sha256) |
| Source code (all architectures) | [Download](https://s3.us-west-2.amazonaws.com/bagofwords.com/releases/0.0.587/bagofwords-source-0.0.587.tar.gz) | [SHA256](https://s3.us-west-2.amazonaws.com/bagofwords.com/releases/0.0.587/bagofwords-source-0.0.587.tar.gz.sha256) |

Download the archive and matching checksum into the same directory, then verify before extracting. For example, on Linux:

```bash theme={null}
sha256sum -c bagofwords-airgap-0.0.587-linux-amd64.tar.gz.sha256
tar -xzf bagofwords-airgap-0.0.587-linux-amd64.tar.gz
```

Follow the README inside `bagofwords-airgap/` for offline installation and internal TLS configuration.

## Other configurations

***

You can also configure additional settings in the bow-config.yaml file.

```yaml theme={null}
# bow-config.yaml

# Deployment Configuration
base_url: http://0.0.0.0:3000

database:
  url: ${BOW_DATABASE_URL}

# Feature Flags
features:
  allow_uninvited_signups: false
  allow_multiple_organizations: false
  verify_emails: false

google_oauth:
  enabled: false
  client_id: ${BOW_GOOGLE_CLIENT_ID}
  client_secret: ${BOW_GOOGLE_CLIENT_SECRET}

smtp_settings:
  host: "smtp.resend.com"
  port: 587
  username: "resend"
  password: ${BOW_SMTP_PASSWORD}

encryption_key: ${BOW_ENCRYPTION_KEY}

intercom:
  enabled: true
```

To use the custom config file, you can run the following command:

```bash theme={null}
docker run --pull always -d -p 3000:3000 -v $(pwd)/bow-config.yaml:/app/bow-config.yaml bagofwords/bagofwords
```

## Install with Kubernetes

***

You can install Bag of Words on a Kubernetes cluster. The Helm chart can deploy the app with a bundled PostgreSQL instance **or** connect to an external managed database such as AWS Aurora with IAM authentication.

### 1. Add the Helm Repository

```bash theme={null}
helm repo add bow https://helm.bagofwords.com/
helm repo update
```

### 2. Install or Upgrade the Chart

Here are a few examples of how to install or upgrade the Bag of Words Helm chart:

**Deploy with a bundled PostgreSQL instance:**

```bash theme={null}
helm upgrade -i --create-namespace \
 -nbowapp-1 bowapp bow/bagofwords \
 --set postgresql.auth.username=<PG-USER> \
 --set postgresql.auth.password=<PG-PASS> \
 --set postgresql.auth.database=<PG-DB>
```

**Deploy without TLS with a custom hostname:**

```bash theme={null}
helm upgrade -i --create-namespace \
 -nbowapp-1 bowapp bow/bagofwords \
  --set host=<HOST> \
 --set postgresql.auth.username=<PG-USER> \
 --set postgresql.auth.password=<PG-PASS> \
 --set postgresql.auth.database=<PG-DB> \
 --set ingress.tls=false
```

**Deploy with TLS, cert-manager, and Google OAuth:**

```bash theme={null}
helm upgrade -i --create-namespace \
 -nbowapp-1 bowapp bow/bagofwords \
 --set host=<HOST> \
 --set postgresql.auth.username=<PG-USER> \
 --set postgresql.auth.password=<PG-PASS> \
 --set postgresql.auth.database=<PG-DB> \
 --set config.googleOauthEnabled=true \
 --set config.googleClientId=<CLIENT_ID> \
 --set config.googleClientSecret=<CLIENT_SECRET>
```

### Deploy with AWS Aurora and IAM Authentication

When using a managed database like AWS Aurora PostgreSQL, the chart skips the bundled PostgreSQL subchart and connects directly to your Aurora cluster. Passwords are never stored — short-lived IAM tokens are generated at runtime for every new database connection.

**Prerequisites:**

* An Aurora PostgreSQL cluster with **IAM database authentication** enabled
* A database user created with: `GRANT rds_iam TO <username>`
* An IAM role/policy with `rds-db:connect` permission
* In EKS: an **IRSA** (IAM Roles for Service Accounts) annotation on the pod's service account so the app can assume the IAM role

```bash theme={null}
helm upgrade -i --create-namespace \
 -nbowapp-1 bowapp bow/bagofwords \
 --set host=<HOST> \
 --set database.auth.provider=aws_iam \
 --set database.auth.region=us-east-1 \
 --set database.auth.sslMode=require \
 --set database.host=<AURORA-CLUSTER-ENDPOINT> \
 --set database.port=5432 \
 --set database.username=<DB-USER> \
 --set database.name=<DB-NAME> \
 --set serviceAccount.annotations.'eks\.amazonaws\.com/role-arn'=arn:aws:iam::<ACCOUNT>:role/<ROLE-NAME>
```

Or use a values file:

```yaml theme={null}
# aurora-values.yaml
host: bow.example.com

database:
  auth:
    provider: aws_iam
    region: us-east-1
    sslMode: require
  host: my-cluster.cluster-xxx.us-east-1.rds.amazonaws.com
  port: 5432
  username: bow_user
  name: postgres

serviceAccount:
  name: bowapp
  annotations:
    eks.amazonaws.com/role-arn: arn:aws:iam::123456789012:role/bow-rds-role

config:
  encryptionKey: "<your-encryption-key>"
  baseUrl: "https://bow.example.com"
```

```bash theme={null}
helm upgrade -i --create-namespace \
 -nbowapp-1 bowapp bow/bagofwords \
 -f aurora-values.yaml
```

<Info>
  When `database.auth.provider` is set to `aws_iam`, the bundled PostgreSQL subchart is automatically skipped. The app uses `boto3` to call `generate_db_auth_token()` before each new connection, so tokens rotate automatically and no static database password is needed.
</Info>

### Update

```bash theme={null}
# Restart the Bag of Words deployment(s) to pick up the latest image
# Adjust namespace (-n) and selector if you used different names
kubectl rollout restart deployment -n bowapp-1 -l app.kubernetes.io/instance=bowapp
kubectl rollout status deployment -n bowapp-1 -l app.kubernetes.io/instance=bowapp
```

## AWS Aurora Configuration

***

Bag of Words supports connecting to **AWS Aurora PostgreSQL** using IAM database authentication. This eliminates static database passwords entirely — the application generates short-lived tokens (valid for 15 minutes) at connection time using AWS IAM.

### How it works

1. The app's service account assumes an IAM role (via IRSA in EKS, or instance profile on EC2)
2. On every new database connection, the app calls `generate_db_auth_token()` to get a temporary password
3. The token is used as the PostgreSQL password — established connections are not affected when it expires
4. SSL is required (`require` or `verify-full`)

### AWS Setup

**1. Enable IAM authentication on the Aurora cluster:**

```bash theme={null}
aws rds modify-db-cluster \
  --db-cluster-identifier <cluster-id> \
  --enable-iam-database-authentication \
  --apply-immediately
```

**2. Create the database user with IAM grants:**

```sql theme={null}
CREATE USER bow_user;
GRANT rds_iam TO bow_user;
GRANT ALL PRIVILEGES ON DATABASE postgres TO bow_user;
```

**3. Create an IAM policy allowing `rds-db:connect`:**

```json theme={null}
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": "rds-db:connect",
      "Resource": "arn:aws:rds-db:<REGION>:<ACCOUNT>:dbuser:<DB-RESOURCE-ID>/<DB-USER>"
    }
  ]
}
```

**4. For EKS — create an IRSA-enabled service account:**

```bash theme={null}
eksctl create iamserviceaccount \
  --name bowapp \
  --namespace bowapp-1 \
  --cluster <CLUSTER-NAME> \
  --attach-policy-arn arn:aws:iam::<ACCOUNT>:policy/bow-rds-connect \
  --approve
```

Then set the IRSA annotation in the Helm chart:

```bash theme={null}
--set serviceAccount.annotations.'eks\.amazonaws\.com/role-arn'=arn:aws:iam::<ACCOUNT>:role/<ROLE-NAME>
```

### bow-config.yaml for Aurora (non-Kubernetes)

If you are running Bag of Words on EC2 or ECS (not Kubernetes), you can configure Aurora IAM auth directly in `bow-config.yaml`:

```yaml theme={null}
database:
  host: "my-cluster.cluster-xxx.us-east-1.rds.amazonaws.com"
  port: 5432
  name: "postgres"
  username: "bow_user"
  auth:
    provider: "aws_iam"
    region: "us-east-1"
    ssl_mode: "require"
```

The EC2 instance or ECS task must have an IAM role with the `rds-db:connect` policy attached.

## Google OAuth

To enable Google OAuth authentication, configure the following parameters in your bow config (or in env/k8s configmap):

```yaml theme={null}
google_oauth:
  enabled: true
  client_id: ${BOW_GOOGLE_CLIENT_ID}
  client_secret: ${BOW_GOOGLE_CLIENT_SECRET}
```

You should also set the following in your Google OAuth configurations:

1. Callback URL: `https://yourbaseurl.com/api/auth/google/callback`
2. Scopes: `/auth/userinfo.email`, `/auth/userinfo.profile`, `openid`
3. Enable People API

## OpenID Connect (OIDC)

```yaml theme={null}
oidc_providers:
- name: okta
  enabled: true
  issuer: https://***********.okta.com/oauth2/default
  client_id: ${OKTA_CLIENT_ID}
  client_secret: ${OKTA_CLIENT_SECRET}
  scopes: ["openid", "profile", "email"]
  pkce: true
  client_auth_method: basic
  discovery: true
  uid_claim: sub
```

### For Okta

1. Set a new OIDC application: web
2. Set callback URL `https://your-base-bow-url.com/api/auth/okta/callback`

### For Microsoft Entra ID (Azure AD)

```yaml theme={null}
oidc_providers:
- name: entra
  enabled: true
  issuer: https://login.microsoftonline.com/${ENTRA_TENANT_ID}/v2.0
  client_id: YOUR_CLIENT_ID
  client_secret: ${BOW_ENTRA_CLIENT_SECRET}
  scopes: ["openid", "profile", "email", "api://YOUR_CLIENT_ID/access_as_user"]
  pkce: true
  client_auth_method: post
  discovery: true
  uid_claim: sub
  sync_groups: true
  group_claim: "groups"
  resolve_group_names: true
```

1. In the Azure portal, register a new application under **Microsoft Entra ID → App registrations**.
2. Set the redirect URI (type: Web) to `https://your-base-bow-url.com/api/auth/entra/callback`.
3. Copy the **Directory (tenant) ID** into `${ENTRA_TENANT_ID}` and the **Application (client) ID** into `${BOW_ENTRA_CLIENT_ID}`.
4. Under **Certificates & secrets**, create a client secret and set it as `${BOW_ENTRA_CLIENT_SECRET}`.
5. Use `client_auth_method: post` — Entra ID expects credentials in the request body rather than the `Authorization` header.

**Group sync**

When `sync_groups: true`, group memberships from the `groups` claim are synced to Bag of Words on each login. To make Entra ID emit the claim, edit the app's **Token configuration → Add groups claim** and select the group types you want.

* `group_claim` — the token claim that carries group membership (default `groups`).
* `resolve_group_names: true` — by default Entra ID emits group **object IDs**, not display names. Enabling this resolves the IDs to human-readable names via Microsoft Graph (requires the app to have the `GroupMember.Read.All` or `Directory.Read.All` permission granted).

## SAML 2.0

For identity providers that require SAML, add a `saml_providers` entry. `base_url` must be an HTTPS origin.

```yaml theme={null}
saml_providers:
- name: company-sso
  enabled: true
  label: Company SSO
  organization_id: <existing-bow-organization-id>
  idp:
    metadata_url: https://idp.company.com/app/metadata
  attributes:
    subject: name_id
    email: mail
    name: displayName
```

Register `https://your-base-bow-url.com/api/auth/saml/company-sso/acs` as the Reply (ACS) URL and `https://your-base-bow-url.com/saml/company-sso` as the Entity ID. See [Sign-in and SSO](/sign-in-and-sso#saml-2-0) for trust options, admission rules, and troubleshooting.

## LDAP / Active Directory

For environments that authenticate against an on-premises directory, enable the LDAP provider:

```yaml theme={null}
ldap:
  enabled: true
  url: ldaps://ad.company.com:636
  bind_dn: cn=service-account,ou=Services,dc=company,dc=com
  bind_password: ${BOW_LDAP_BIND_PASSWORD}
```

* `url` — the directory endpoint. Use `ldaps://` (port 636) for TLS; avoid plain `ldap://` (port 389) outside a trusted network.
* `bind_dn` — the distinguished name of a read-only service account used to look up users.
* `bind_password` — the service account password, supplied via the `${BOW_LDAP_BIND_PASSWORD}` environment variable rather than committed to the config.

## SMTP Settings

Configure an outbound mail server to enable email features — dashboard and conversation share notifications, scheduled report and prompt results (with optional PDF attachments), password-reset and email-verification links, and the AI `send_email` tool.

Define the `smtp_settings` block in `bow-config.yaml`:

```yaml theme={null}
smtp_settings:
  host: "smtp.resend.com"
  port: 587
  username: "resend"
  password: ${BOW_SMTP_PASSWORD}
  from_name: "Bag of words"
  from_email: "hi@bagofwords.com"
  use_tls: true
  use_ssl: false
  use_credentials: true
  validate_certs: true
```

| Key | Default | Env var | Notes |
| - | - | - | - |
| `host` | `smtp.resend.com` | `BOW_SMTP_HOST` | SMTP server |
| `port` | `587` | `BOW_SMTP_PORT` | |
| `username` | — | `BOW_SMTP_USERNAME` | |
| `password` | — | `BOW_SMTP_PASSWORD` | Sensitive — supply via env var |
| `from_name` | `Bag of words` | `BOW_SMTP_FROM_NAME` | Display name on outgoing mail |
| `from_email` | `hi@bagofwords.com` | `BOW_SMTP_FROM_EMAIL` | From address |
| `use_tls` | `true` | `BOW_SMTP_USE_TLS` | STARTTLS |
| `use_ssl` | `false` | `BOW_SMTP_USE_SSL` | SSL at connect |
| `use_credentials` | `true` | `BOW_SMTP_USE_CREDENTIALS` | Set `false` for anonymous relays |
| `validate_certs` | `true` | `BOW_SMTP_VALIDATE_CERTS` | |

<Info>
  Email is enabled only when credentials are present (or `use_credentials: false` for anonymous relays). If no mail client can be built at startup, email features are silently inert — outgoing notifications are skipped and the `send_email` tool stays disabled.
</Info>


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