What BOW stores
All paths are inside the BOW container.
Current versions store the organization logo under
uploads/branding. The /app/backend/branding_uploads directory and the Compose branding_data volume are no longer used.
Object storage
Withs3 or azure, the bucket or container holds every file BOW needs to keep. /app/backend/uploads becomes a local cache: BOW downloads a file the first time it needs it, so a new server, container or replica serves every file. For files that can be replaced, such as thumbnails and logos, BOW checks the bucket before serving a cached copy, so a replica never serves a file that another replica replaced or deleted.
Settings
Set storage inbow-config.yaml under storage:, or with BOW_STORAGE_* environment variables. A value in bow-config.yaml wins over the variable. The Helm chart sets the variables from its storage.* values.
bow-config.yaml: set them as environment variables from your secret store, or reference them with ${VAR} placeholders.
With object storage,
BOW_ARTIFACT_STORAGE is optional. Unset, data app files are cached in /app/backend/uploads/artifact_files.
Credentials
BOW needs read, write, delete and list on the bucket or container, under the prefix if you set one.
AWS IAM policy
AWS IAM policy
kms:GenerateDataKey and kms:Decrypt on the key. Keep the bucket private.Azure role
Azure role
Assign Storage Blob Data Contributor on the container to the identity BOW uses. Keep anonymous access disabled. A SAS token needs container scope with read, add, create, write, delete and list.
Startup check
BOW checks the bucket when it starts. It writes, reads and deletes a small file under<prefix>_healthcheck/, logs the result and exits if any step fails:
Set it up
- Docker
- Docker Compose
- Kubernetes (Helm)
Pass the settings as environment variables, or mount a On EC2,
bow-config.yaml with a storage: block:auth: default uses the instance profile. Containers reach it only when the instance metadata hop limit is 2 or more (IMDS settings); otherwise set BOW_STORAGE_S3_AUTH=static with keys.Move an existing installation to object storage
Plan a maintenance window and pause writes. Then copy the existing files into the bucket under the same names.- Docker and Docker Compose
- Kubernetes
Keep the existing uploads volume mounted. Set the storage settings, update to a version with object storage, then copy the files from inside the container:With plain Docker, use
docker exec <container> instead of docker compose exec app. The script uploads every file BOW keeps, skips files that are already in the bucket, and never changes the local files. It also copies BOW_ARTIFACT_STORAGE when that is set. You can run it again safely. Once it finishes, the volume is only a cache.Local disk
Withstorage.backend: local, files live in /app/backend/uploads inside the container. Mount a persistent volume there: a container’s own filesystem is lost when the container is recreated.
- Docker
- Docker Compose
- Kubernetes (Helm)
Mount a named volume:
BOW_ARTIFACT_STORAGE to a path on the same volume, for example /app/backend/uploads/artifact-files. Ensure the application user can create, read, and write files on the volume. BOW creates the folders it needs under /app/backend/uploads when it starts.
Migrate an existing Kubernetes installation
Use a maintenance window. Do not scale down or restart the old pods to begin this procedure: their local files may be the only copies.- Pause changes without replacing pods. Suspend automated deployments and scaling, block new writes at your ingress or maintenance layer, and let active runs finish. Account for scheduled jobs and integrations that can still create files.
- Inventory every application pod. Record the image version, mounted paths, artifact storage path, Helm values, and encryption-key source. With several replicas, each pod may contain different files.
- Back up the database, original encryption key, and files. Copy the whole uploads directory from every pod into a separate backup directory, and the configured artifact storage path. For example, replace
<pod-name>with one running application pod:Repeat with a different backup directory for each pod. Keep backups outside the pods and verify file counts, sizes, and checksums. - Copy the files to the new storage. For object storage, upload them as in Move an existing installation to object storage. For a persistent volume, mount it in a temporary pod and restore the files, preserving directory structure and filenames. Combine copies from every source pod without silently overwriting conflicting files.
- Deploy the new storage settings. For a volume, stop the temporary pod first so single-node disks can detach. Keep the same database, encryption key, and configured file paths.
- Verify before resuming writes. Open existing uploads, generated files, the logo, and data app files. Then run the check below. Resume traffic, integrations, scheduling, and automated deployments after these checks pass.
config.secretRef pointing at a Secret containing the original BOW_ENCRYPTION_KEY. Do not generate a replacement key during a storage migration.
Verify storage after a restart
Test first in staging.- Upload a small test file through BOW and record its contents. If you use data app file resources, test those too.
- With object storage, check that the startup log line
File storage: ...names your bucket and prefix, and that the file appears under<prefix>files/. With local disk, check that your volume is mounted at/app/backend/uploads. - Recreate the application:
docker compose up -d --force-recreate app, or on Kubernetes: - Open and download the same file through BOW and compare its contents. Also verify a pre-migration file and a stored connection credential.
- If you run multiple replicas, verify file access through each replica.
Bound PVC alone does not verify that BOW is reading the persisted files.
Backups and recovery
Back up PostgreSQL, file storage, the original encryption key, and deployment configuration together with a consistent recovery point.- Object storage: turn on bucket versioning (S3) or blob soft delete and versioning (Azure), and use your provider’s backup or replication for the bucket or container.
- Local disk: quiesce writes or use a backup procedure that coordinates database and volume snapshots. On Kubernetes, review your StorageClass reclaim policy before deleting claims or a release. A persistent volume is not a backup.
