Install with Docker
You can install BOW 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.
To use PostgreSQL, you need to set the
BOW_DATABASE_URL environment variable.
For example: BOW_DATABASE_URL=postgresql://postgres:postgres@localhost:5432/bagofwordsUpdate
- Re-run the same
docker run --pull always ...command to fetch and start the latest image. - Optionally, pull explicitly and restart:
Install with Docker Compose
Run BOW 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.yamlCaddyfile: https://github.com/bagofwords1/bagofwords/blob/main/Caddyfile
Steps
- Make sure Docker and Docker Compose are installed.
- Clone the repo:
- Create a
.envfile (for domain and credentials). Example:GenerateBOW_ENCRYPTION_KEYwith OpenSSL: - Start services:
- 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.
- Create an A record for
- Open
https://yourdomain.com
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.Update
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 BOW, 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. Use the version number without the leadingv and replace {arch} with amd64 or arm64:
.sha256 to the download URL for its checksum file. The architecture-independent source archive uses:
SOURCE_COMMIT file identifying the build commit; it does not include Python or Node.js dependencies.
Example: version 0.0.587
Download the archive and matching checksum into the same directory, then verify before extracting. For example, on Linux:
bagofwords-airgap/ for offline installation and internal TLS configuration.
Other configurations
You can also configure additional settings in the bow-config.yaml file.
Install with Kubernetes
The
bow/bagofwords Helm chart (chart version 3.0.0) runs BOW on any Kubernetes cluster. It deploys:
- A
Deploymentrunning thebagofwords/bagofwordsimage on port3000, with startup, readiness and liveness probes on/health - A
ClusterIPServiceand anIngress(classnginxby default) - A
ServiceAccount(namedbowappby default) that you can annotate for IRSA or Workload Identity - A
ConfigMapholding environment variables and the generatedbow-config.yaml, mounted at/app/bow-config.yaml - Optionally, a bundled single-instance PostgreSQL 17 (a
StatefulSetrunning the officialpostgres:17-bookwormimage), enabled by default. The chart has no chart dependencies. - Optionally, a
HorizontalPodAutoscaler,PodDisruptionBudgetandNetworkPolicy(all disabled by default)
- A Kubernetes cluster and
kubectlaccess to it - Helm 3.8 or newer
- An ingress controller (the chart defaults to
ingress-nginx), or setingress.enabled=falseand expose theServiceyourself - For the bundled PostgreSQL: a default
StorageClass(the database requests a 20Gi volume) - For TLS: cert-manager or an existing TLS Secret
The chart validates your values against a strict schema. Unknown keys, or a wrong type such as
--set ingress.tls=false, fail the install with values don't meet the specifications of the schema. If you are coming from older docs or an older values file, see Upgrading from chart 2.x and Upgrading from chart 1.x.Quick start
1
Add the Helm repository
2
Create a namespace and a Secret
Keep sensitive values in a Kubernetes Secret, not in your values file. The chart loads every key of the Secret named in
config.secretRef as an environment variable.BOW_ENCRYPTION_KEYencrypts stored credentials. Back it up. See Day 2 operations.passwordandpostgres-passwordare the keys the bundled PostgreSQL reads for the app user and thepostgressuperuser. The app user is created as the database owner, not as a superuser.BOW_DATABASE_URLpoints the app at the bundled database. The host is<release-name>-postgresql, so adjust it if your release is not calledbowapp.
3
Write a values file
4
Install
5
Verify
https://bow.example.com. Before DNS and TLS are in place, you can reach the app with:Database options
- Bundled PostgreSQL
- External PostgreSQL
- AWS Aurora / RDS with IAM
This is the default (Other keys: The chart sets
postgresql.enabled: true). The quick start above uses it.The chart runs its own PostgreSQL 17 StatefulSet from the official postgres image, with a <release>-postgresql Service and a 20Gi volume. Common settings:postgresql.image.registry / repository / tag (default docker.io / library/postgres / 17-bookworm), postgresql.imagePullSecrets, postgresql.primary.podAnnotations, podSecurityContext, containerSecurityContext and terminationGracePeriodSeconds (60). Run helm show values bow/bagofwords for the full list. Keys from the old Bitnami subchart are rejected.If you render the chart without cluster access (
helm template, Argo CD, Flux), set postgresql.auth.postgresPassword or use postgresql.auth.existingSecret. Otherwise the chart cannot read the stored superuser password and writes a new random one to its Secret on every sync, while the database keeps the original.max_connections = 300 on the bundled server. The bundled database is a single instance. For production, prefer a managed database. See Day 2 operations for backups.Connection pool sizing
Each Uvicorn worker keeps its own connection pool. The most connections BOW can open is:max_connections minus its reserved superuser slots, or new connections are refused and agent runs fail. Tune it with:
Ingress and TLS
Also set
config.baseUrl to the public URL (for example https://bow.example.com). BOW uses it to build links in emails and OAuth callback URLs.
HTTP only, custom hostname:
config.forwardedAllowIps to your ingress controller’s pod CIDR (comma-separated IPs or CIDRs). Without it, BOW sees every request as coming from the ingress controller and all anonymous users share one rate limit.
Secrets
Values that start with${ are placeholders. The chart does not put them in the ConfigMap, and BOW reads the real value from the environment at startup. Put the real values in the Secret named by config.secretRef, using these keys:
If you set a sensitive value as plain text (for example
config.googleClientSecret=abc), the chart stores it in the ConfigMap and prints a warning.
Authentication
config.authMode controls sign-in: hybrid (default, local accounts and SSO), local_only or sso_only. Related flags: config.allowUninvitedSignups, config.allowMultipleOrganizations and config.verifyEmails (all false by default).
Google OAuth
Google OAuth
OIDC (Okta, Microsoft Entra ID, Auth0)
OIDC (Okta, Microsoft Entra ID, Auth0)
redirectPath, extraAuthorizeParams and extraTokenParams. See OpenID Connect for provider-side setup.LDAP / Active Directory
LDAP / Active Directory
BOW_LDAP_BIND_PASSWORD in the Secret. Other keys, with their defaults: startTls (false), kerberosRealm, allowLocalSuperuserLogin (false), userSearchBase, userSearchFilter ((objectClass=person)), userEmailAttribute (mail), userNameAttribute (displayName), groupSearchBase, groupSearchFilter ((objectClass=group)), groupNameAttribute (cn), groupMemberAttribute (member), groupMemberFormat (dn or uid), connectionTimeout (10) and pageSize (500).Email (SMTP)
config.smtp.host also enables SMTP. If enabled is true and host is empty, the install fails. See SMTP Settings for what email is used for.
Persistent storage for uploads
The chart does not mount a volume for uploaded files or branding assets. Without one, they are lost when a pod is rescheduled. Create a PersistentVolumeClaim and mount it:ReadWriteMany (for example EFS, Filestore or Azure Files).
Scaling and availability
Observability
Send traces to an OpenTelemetry collector:Other values
To see every value with its default:
Update
With a pinned tag, changeimage.tag in your values file and run the same upgrade command:
latest tag, restart the deployment to pull the newest image:
Upgrading from chart 2.x
Chart3.0.0 replaces the Bitnami PostgreSQL subchart with the chart’s own PostgreSQL, running the official postgres:17 image. Bitnami no longer publishes the image that chart 2.x used, so new 2.x installs cannot start their database.
Your data is reused in place. The StatefulSet name, volume (data-<release>-postgresql-0), on-disk layout and Secret keys are the same, so the upgrade restarts the database pod on the new image against the same volume. Expect about a minute of database downtime. There is no rollback to 2.x.
1
Back up the database
Snapshot the data volume or run
pg_dump. This backup is your only way back if something goes wrong.2
Clean up your values file
Remove Bitnami-only keys under
postgresql:, such as architecture, global, primary.resourcesPreset, metrics and readReplicas. Also remove any postgresql.image override that points at bitnami or bitnamilegacy. The chart now rejects them.If you changed the volume’s storage class in 2.x (for example primary.persistence.storageClass or global.storageClass), set the same value under postgresql.primary.persistence.storageClass.3
Upgrade with the migration flag
The safety check needs cluster access, so it does not run under
helm template or Argo CD. Pin the chart there (for example targetRevision: 2.*) until you are ready to migrate.updates to statefulset spec for fields other than ... are forbidden, delete only the StatefulSet object (the volume and data are kept) and upgrade again:
Upgrading from chart 1.x
If you are on 1.x, follow these steps first and then Upgrading from chart 2.x. Chart2.0.0 changes the Deployment’s selector labels to app.kubernetes.io/name and app.kubernetes.io/instance. Kubernetes does not allow selectors to change, so a plain helm upgrade fails with field is immutable: spec.selector. Delete the Deployment, then upgrade. The app is unavailable until the new pod is ready. The database and its volume are not affected.
argocd.argoproj.io/sync-options: Replace=true to the Deployment.
Also update your values file:
Uninstall
data-bowapp-postgresql-0) is kept. Delete it with kubectl delete pvc only if you no longer need the data.
AWS Aurora Configuration
BOW 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
- The app’s service account assumes an IAM role (via IRSA in EKS, or instance profile on EC2)
- On every new database connection, the app calls
generate_db_auth_token()to get a temporary password - The token is used as the PostgreSQL password — established connections are not affected when it expires
- SSL is required (
requireorverify-full)
AWS Setup
1. Enable IAM authentication on the Aurora cluster:rds-db:connect:
bowapp ServiceAccount itself, so create only the IAM role with eksctl:
bow-config.yaml for Aurora (non-Kubernetes)
If you are running BOW on EC2 or ECS (not Kubernetes), you can configure Aurora IAM auth directly inbow-config.yaml:
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):- Callback URL:
https://yourbaseurl.com/api/auth/google/callback - Scopes:
/auth/userinfo.email,/auth/userinfo.profile,openid - Enable People API
OpenID Connect (OIDC)
For Okta
- Set a new OIDC application: web
- Set callback URL
https://your-base-bow-url.com/api/auth/okta/callback
For Microsoft Entra ID (Azure AD)
- In the Azure portal, register a new application under Microsoft Entra ID → App registrations.
- Set the redirect URI (type: Web) to
https://your-base-bow-url.com/api/auth/entra/callback. - Copy the Directory (tenant) ID into
${ENTRA_TENANT_ID}and the Application (client) ID into${BOW_ENTRA_CLIENT_ID}. - Under Certificates & secrets, create a client secret and set it as
${BOW_ENTRA_CLIENT_SECRET}. - Use
client_auth_method: post— Entra ID expects credentials in the request body rather than theAuthorizationheader.
sync_groups: true, group memberships from the groups claim are synced to BOW 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 (defaultgroups).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 theGroupMember.Read.AllorDirectory.Read.Allpermission granted).
SAML 2.0
For identity providers that require SAML, add asaml_providers entry. base_url must be an HTTPS origin.
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 for trust options, admission rules, and troubleshooting.
LDAP / Active Directory
For environments that authenticate against an on-premises directory, enable the LDAP provider:url— the directory endpoint. Useldaps://(port 636) for TLS; avoid plainldap://(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 AIsend_email tool.
Define the smtp_settings block in bow-config.yaml:
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.