Skip to content

Configure

Object storage

Configure where Sim stores uploaded files — local disk, AWS S3, Azure Blob, or Google Cloud Storage

Configure file storage before uploading durable files or adding app replicas. Sim supports local disk, AWS S3, Azure Blob Storage, and Google Cloud Storage (GCS).

BackendWhen to use
Local diskSingle-node Docker, local development, evaluation
AWS S3Production, especially when running more than one app replica
Azure BlobProduction on Azure
Google Cloud StorageProduction on GCP

How the backend is selected

Set STORAGE_PROVIDER to local, s3, azure, or gcs to select a backend explicitly. When it is unset, Sim infers the backend from the configured environment variables in this order:

  1. Azure Blob Storage — used if AZURE_STORAGE_CONTAINER_NAME is set and either (AZURE_ACCOUNT_NAME + AZURE_ACCOUNT_KEY) or AZURE_CONNECTION_STRING is set.
  2. AWS S3 — used if S3_BUCKET_NAME and AWS_REGION are set (and Azure is not configured).
  3. Google Cloud Storage — used if GCS_BUCKET_NAME is set (and neither Azure nor S3 is configured).
  4. Local disk — the fallback when no cloud backend is activated. Azure activates from its main container or credentials; S3 activates from any of its bucket names or S3_ENDPOINT; GCS activates from GCS_BUCKET_NAME. Dedicated Azure container names or GCS bucket names alone do not activate those providers.

If STORAGE_PROVIDER is unset, Sim uses the first backend whose configuration is complete, in that order. An explicit STORAGE_PROVIDER takes precedence and must be valid and complete.

A partially configured backend is skipped, so a fully configured one later in the order still wins. A backend whose values are present but malformed is different: it stops startup immediately, and a later fully configured one does not take over. An activated but incomplete backend does not fall back to local disk: if no other backend is complete, startup fails with File storage is partially or incorrectly configured. AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY are checked as a pair — set both or neither.

Set up AWS S3

  1. Create the buckets

    Sim separates files into purpose-specific buckets. Sim never creates buckets — create each one yourself before configuring it. Most S3 bucket variables do not fall back to S3_BUCKET_NAME: leave S3_KB_BUCKET_NAME, S3_CHAT_BUCKET_NAME, S3_COPILOT_BUCKET_NAME, or S3_PROFILE_PICTURES_BUCKET_NAME unset and it resolves to an empty bucket name, so that file type fails to store. Three behave differently — S3_OG_IMAGES_BUCKET_NAME and S3_WORKSPACE_LOGOS_BUCKET_NAME fall back to S3_BUCKET_NAME, and S3_EXECUTION_FILES_BUCKET_NAME falls back to a literal default name (see the reference table below).

    Code
    # Set your region once
    export AWS_REGION=us-east-1
    
    # The eight purpose-specific buckets. The CORS and IAM steps below reuse this list.
    export SIM_BUCKETS="workspace-files knowledge-base execution-files chat-files
                        copilot-files profile-pictures og-images workspace-logos"
    
    # Create buckets (names must be globally unique — prefix with your org)
    for name in ${SIM_BUCKETS:?run the export above first}; do
      if [ "$AWS_REGION" = "us-east-1" ]; then
        aws s3api create-bucket --bucket "myorg-sim-$name" --region "$AWS_REGION"
      else
        aws s3api create-bucket \
          --bucket "myorg-sim-$name" \
          --region "$AWS_REGION" \
          --create-bucket-configuration LocationConstraint="$AWS_REGION"
      fi
    done

    The command omits LocationConstraint in us-east-1, which requires that exception. See AWS CreateBucket.

    Keep all buckets private (block public access). Uploads use short-lived signed URLs and downloads are proxied through the app. Buckets do not need public read access.

  2. Configure CORS on every bucket

    Uploads are sent directly from the browser to S3 via presigned PUT requests, so each bucket needs a CORS policy that allows your Sim origin. A missing or mismatched policy can block browser uploads even when server credentials work.

    Code
    cat > /tmp/cors.json <<'EOF'
    {
      "CORSRules": [
        {
          "AllowedOrigins": ["https://sim.yourdomain.com"],
          "AllowedMethods": ["GET", "PUT"],
          "AllowedHeaders": ["*"],
          "MaxAgeSeconds": 3600
        }
      ]
    }
    EOF
    
    for name in ${SIM_BUCKETS:?run the export above first}; do
      aws s3api put-bucket-cors --bucket "myorg-sim-$name" --cors-configuration file:///tmp/cors.json
    done

    Set AllowedOrigins to your exact Sim origin (scheme + host, no trailing slash). Add every origin users reach Sim from, including an apex/www pair if both are live.

  3. Grant access with an IAM policy

    Create an IAM policy scoped to your buckets and attach it to the user (or role) Sim runs as:

    Code
    {
      "Version": "2012-10-17",
      "Statement": [
        {
          "Effect": "Allow",
          "Action": [
            "s3:GetObject",
            "s3:PutObject",
            "s3:DeleteObject",
            "s3:ListBucket",
            "s3:AbortMultipartUpload",
            "s3:ListMultipartUploadParts"
          ],
          "Resource": [
            "arn:aws:s3:::myorg-sim-*",
            "arn:aws:s3:::myorg-sim-*/*"
          ]
        }
      ]
    }

    You then have two ways to supply credentials:

    • Static keys — create an IAM user with this policy and set AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY.
    • Instance/role credentials (recommended) — attach the policy to the EC2 instance role, ECS task role, or EKS IRSA role. Leave AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY unset and Sim falls back to the default AWS credential chain automatically.
  4. Configure environment variables

    Set the region, optionally the credentials, and the bucket names:

    Code
    # Region + credentials
    AWS_REGION=us-east-1
    AWS_ACCESS_KEY_ID=AKIA...          # omit when using an instance/IRSA role
    AWS_SECRET_ACCESS_KEY=...          # omit when using an instance/IRSA role
    
    # Buckets (per purpose)
    S3_BUCKET_NAME=myorg-sim-workspace-files
    S3_KB_BUCKET_NAME=myorg-sim-knowledge-base
    S3_EXECUTION_FILES_BUCKET_NAME=myorg-sim-execution-files
    S3_CHAT_BUCKET_NAME=myorg-sim-chat-files
    S3_COPILOT_BUCKET_NAME=myorg-sim-copilot-files
    S3_PROFILE_PICTURES_BUCKET_NAME=myorg-sim-profile-pictures
    S3_OG_IMAGES_BUCKET_NAME=myorg-sim-og-images
    S3_WORKSPACE_LOGOS_BUCKET_NAME=myorg-sim-workspace-logos

    AWS_REGION and S3_BUCKET_NAME are what switch Sim into S3 mode, but they do not cover the other file types — set every bucket above, because only the OpenGraph and workspace-logo buckets fall back to the general one.

S3 bucket reference

VariableStoresRequired
AWS_REGIONRegion for all bucketsYes (enables S3)
AWS_ACCESS_KEY_IDAccess keyNo (uses credential chain if unset)
AWS_SECRET_ACCESS_KEYSecret keyNo (uses credential chain if unset)
S3_BUCKET_NAMEGeneral workspace filesYes (enables S3)
S3_KB_BUCKET_NAMEKnowledge base documentsYes, to use knowledge bases (no fallback)
S3_EXECUTION_FILES_BUCKET_NAMEWorkflow execution files. Falls back to the literal name sim-execution-files, which may not exist or belong to you — set this explicitlySet explicitly to your own bucket
S3_CHAT_BUCKET_NAMEDeployed chat assetsYes, to use deployed chats (no fallback)
S3_COPILOT_BUCKET_NAMEChat attachmentsYes, to attach files in Chat (no fallback)
S3_PROFILE_PICTURES_BUCKET_NAMEUser avatarsYes, for avatar uploads (no fallback)
S3_OG_IMAGES_BUCKET_NAMEOpenGraph preview imagesOptional (falls back to S3_BUCKET_NAME)
S3_WORKSPACE_LOGOS_BUCKET_NAMEWorkspace logosOptional (falls back to S3_BUCKET_NAME)
S3_ENDPOINTCustom endpoint for S3-compatible storage (R2, MinIO, B2)Optional (AWS S3 if unset)
S3_FORCE_PATH_STYLEtrue for path-style addressing (MinIO/Ceph)Optional (defaults false)

Set up Azure Blob Storage

Create one private container per purpose; Sim does not create them. Authenticate with either a connection string or an account name and key.

Code
# Credentials — provide ONE of these forms
AZURE_ACCOUNT_NAME=mystorageaccount
AZURE_ACCOUNT_KEY=...
# or
AZURE_CONNECTION_STRING='DefaultEndpointsProtocol=https;AccountName=...;AccountKey=...;EndpointSuffix=core.windows.net'

# Containers (per purpose)
AZURE_STORAGE_CONTAINER_NAME=workspace-files
AZURE_STORAGE_KB_CONTAINER_NAME=knowledge-base
AZURE_STORAGE_EXECUTION_FILES_CONTAINER_NAME=execution-files
AZURE_STORAGE_CHAT_CONTAINER_NAME=chat-files
AZURE_STORAGE_COPILOT_CONTAINER_NAME=copilot-files
AZURE_STORAGE_PROFILE_PICTURES_CONTAINER_NAME=profile-pictures
AZURE_STORAGE_OG_IMAGES_CONTAINER_NAME=og-images
AZURE_STORAGE_WORKSPACE_LOGOS_CONTAINER_NAME=workspace-logos

Create all eight containers before applying the variables. For example, with account-name/key credentials:

Code
for name in workspace-files knowledge-base execution-files chat-files copilot-files profile-pictures og-images workspace-logos; do
  az storage container create \
    --name "$name" \
    --account-name "$AZURE_ACCOUNT_NAME" \
    --account-key "$AZURE_ACCOUNT_KEY" \
    --public-access off
done

For connection-string credentials, replace the account options with --connection-string "$AZURE_CONNECTION_STRING".

Container variableFallback when unset
AZURE_STORAGE_KB_CONTAINER_NAME, AZURE_STORAGE_CHAT_CONTAINER_NAME, AZURE_STORAGE_COPILOT_CONTAINER_NAME, AZURE_STORAGE_PROFILE_PICTURES_CONTAINER_NAMENone; that file type fails to store
AZURE_STORAGE_EXECUTION_FILES_CONTAINER_NAMELiteral sim-execution-files; set your intended name explicitly
AZURE_STORAGE_OG_IMAGES_CONTAINER_NAME, AZURE_STORAGE_WORKSPACE_LOGOS_CONTAINER_NAMEAZURE_STORAGE_CONTAINER_NAME

Direct browser uploads require a Blob service CORS rule on the storage account. Allow your exact Sim origin, GET and PUT, the Content-Type header, and the x-ms-* prefix used by signed blob and metadata headers. Small-file uploads also send If-None-Match; the signature itself is create-only, so a signed URL cannot overwrite an existing final object. Multipart completion verifies uploaded parts on the server:

Code
az storage cors add \
  --services b \
  --methods GET PUT OPTIONS \
  --origins https://sim.yourdomain.com \
  --allowed-headers content-type if-none-match 'x-ms-*' \
  --exposed-headers ETag \
  --max-age 3600 \
  --account-name mystorageaccount \
  --account-key '<account-key>'

If you authenticate with a connection string, replace the last two options with --connection-string "$AZURE_CONNECTION_STRING". CORS is configured once for the account's Blob service and applies to all of its containers.

A full Helm example lives at helm/sim/examples/values-azure.yaml.

Set up Google Cloud Storage

  1. Create the buckets

    GCS uses one bucket per purpose, mirroring the S3 layout:

    Code
    export PROJECT_ID=your-project-id
    export LOCATION=us-central1
    
    # The eight purpose-specific buckets. The CORS and IAM steps below reuse this list.
    export SIM_BUCKETS="workspace-files knowledge-base execution-files chat-files
                        copilot-files profile-pictures og-images workspace-logos"
    
    # Create buckets (names must be globally unique — prefix with your org)
    for name in ${SIM_BUCKETS:?run the export above first}; do
      gcloud storage buckets create "gs://myorg-sim-$name" \
        --project "$PROJECT_ID" \
        --location "$LOCATION" \
        --uniform-bucket-level-access
    done

    Keep all buckets private (no allUsers bindings). Uploads use short-lived V4 signed URLs and downloads are proxied through the app. Buckets do not need public read access.

    Because uploads are sent directly from the browser via signed PUT requests, each bucket needs a CORS policy that allows your Sim origin:

    Code
    cat > /tmp/cors.json <<'EOF'
    [
      {
        "origin": ["https://your-sim-domain.com"],
        "method": ["GET", "PUT"],
        "responseHeader": [
          "Content-Type",
          "ETag",
          "x-goog-if-generation-match",
          "x-goog-meta-uploadid",
          "x-goog-meta-originalname",
          "x-goog-meta-uploadedat",
          "x-goog-meta-purpose",
          "x-goog-meta-userid",
          "x-goog-meta-workspaceid",
          "x-goog-meta-knowledgebaseid",
          "x-goog-meta-folderid",
          "x-goog-meta-workflowid",
          "x-goog-meta-executionid"
        ],
        "maxAgeSeconds": 3600
      }
    ]
    EOF
    
    for name in ${SIM_BUCKETS:?run the export above first}; do
      gcloud storage buckets update "gs://myorg-sim-$name" --cors-file=/tmp/cors.json
    done

    Keep the explicit metadata and x-goog-if-generation-match headers in this policy: Sim signs create-only uploads, which prevent a reused URL from replacing existing bytes. Multipart completion reads provider-authoritative part state on the server; the browser does not need ETag for completion. See GCS CORS configuration.

  2. Grant access

    Create a service account (or reuse the one your workload runs as) and grant it object access on the buckets:

    Code
    gcloud iam service-accounts create sim-storage --project "$PROJECT_ID"
    
    for name in ${SIM_BUCKETS:?run the export above first}; do
      gcloud storage buckets add-iam-policy-binding "gs://myorg-sim-$name" \
        --member "serviceAccount:sim-storage@$PROJECT_ID.iam.gserviceaccount.com" \
        --role roles/storage.objectAdmin
    done

    You then have two ways to supply credentials:

    • Application Default Credentials (recommended on GCP) — run Sim with the service account via GKE Workload Identity (or attach it to the GCE instance) and leave GCS_CREDENTIALS_JSON unset. Because there is no private key in this mode, generating signed URLs uses the IAM signBlob API — grant the service account roles/iam.serviceAccountTokenCreator on itself:

      Code
      gcloud iam service-accounts add-iam-policy-binding \
        "sim-storage@$PROJECT_ID.iam.gserviceaccount.com" \
        --member "serviceAccount:sim-storage@$PROJECT_ID.iam.gserviceaccount.com" \
        --role roles/iam.serviceAccountTokenCreator
    • Inline key (for Docker Compose or non-GCP hosts) — create a JSON key for the service account and set GCS_CREDENTIALS_JSON to its contents. With a private key present, signed URLs are generated locally and no extra IAM role is needed.

  3. Configure environment variables

    Code
    # Credentials — omit both when using Workload Identity / ADC
    GCS_PROJECT_ID=your-project-id            # optional; inferred from credentials when unset
    GCS_CREDENTIALS_JSON='{"type":"service_account","client_email":"...","private_key":"..."}'
    
    # Buckets (per purpose)
    GCS_BUCKET_NAME=myorg-sim-workspace-files
    GCS_KB_BUCKET_NAME=myorg-sim-knowledge-base
    GCS_EXECUTION_FILES_BUCKET_NAME=myorg-sim-execution-files
    GCS_CHAT_BUCKET_NAME=myorg-sim-chat-files
    GCS_COPILOT_BUCKET_NAME=myorg-sim-copilot-files
    GCS_PROFILE_PICTURES_BUCKET_NAME=myorg-sim-profile-pictures
    GCS_OG_IMAGES_BUCKET_NAME=myorg-sim-og-images
    GCS_WORKSPACE_LOGOS_BUCKET_NAME=myorg-sim-workspace-logos

    GCS_BUCKET_NAME is what switches Sim into GCS mode and is the only bucket strictly required: every purpose-specific bucket falls back to it when unset. GCS bucket names are globally unique, so Sim uses no literal defaults here — not even for execution files. Set the dedicated buckets to separate the file types; leave them unset and everything lands in the general bucket.

GCS bucket reference

VariableStoresRequired
GCS_BUCKET_NAMEGeneral workspace filesYes (enables GCS)
GCS_PROJECT_IDGCP project IDNo (inferred from credentials/ADC)
GCS_CREDENTIALS_JSONInline service-account JSONNo (uses Application Default Credentials if unset)
GCS_KB_BUCKET_NAMEKnowledge base documentsRecommended (falls back to GCS_BUCKET_NAME)
GCS_EXECUTION_FILES_BUCKET_NAMEWorkflow execution filesRecommended (falls back to GCS_BUCKET_NAME)
GCS_CHAT_BUCKET_NAMEDeployed chat assetsRecommended (falls back to GCS_BUCKET_NAME)
GCS_COPILOT_BUCKET_NAMEChat attachmentsRecommended (falls back to GCS_BUCKET_NAME)
GCS_PROFILE_PICTURES_BUCKET_NAMEUser avatarsRecommended (falls back to GCS_BUCKET_NAME)
GCS_OG_IMAGES_BUCKET_NAMEOpenGraph preview imagesRecommended (falls back to GCS_BUCKET_NAME)
GCS_WORKSPACE_LOGOS_BUCKET_NAMEWorkspace logosRecommended (falls back to GCS_BUCKET_NAME)

A full Helm example (Workload Identity, GKE) lives at helm/sim/examples/values-gcp.yaml.

Set up S3-compatible storage

Sim works with any S3-compatible store by pointing the S3 client at a custom endpoint. Configure it exactly like AWS S3 (buckets, access key, secret), then add S3_ENDPOINT — and S3_FORCE_PATH_STYLE where the provider requires path-style addressing. The examples below cover Cloudflare R2, MinIO, and RustFS. For other providers, including Backblaze B2, verify signed uploads, multipart completion, and lifecycle behavior against that provider's current S3 support.

S3_ENDPOINT is trusted operator configuration. Sim accepts HTTP and private hosts without its outbound-request guard; do not derive it from untrusted input.

Cloudflare R2 uses virtual-hosted style (the default) and the region auto:

Code
AWS_REGION=auto
S3_ENDPOINT=https://<account-id>.r2.cloudflarestorage.com
AWS_ACCESS_KEY_ID='<r2-access-key-id>'
AWS_SECRET_ACCESS_KEY='<r2-secret-access-key>'
S3_BUCKET_NAME=myorg-sim-workspace-files
# ...remaining S3_*_BUCKET_NAME vars, one R2 bucket each

Leave S3_FORCE_PATH_STYLE unset — R2 supports the default virtual-hosted addressing.

Apply the configuration

Add the storage variables to the .env file used by docker-compose.prod.yml, then restart:

Code
docker compose -f docker-compose.prod.yml up -d

New uploads now use the selected backend. Preserve any existing local files until you have migrated them; changing the storage configuration does not copy their bytes.

Configure incomplete multipart cleanup

Sim uploads directly to a create-only final object key and keeps upload-session state in PostgreSQL. The /api/cron/cleanup-tasks endpoint claims expired sessions before deleting uploaded objects or aborting provider multipart state. Neither shipped scheduler calls this endpoint. Configure an authenticated operator-owned schedule as shown in Background jobs. Provider lifecycle cleanup is a second layer for multipart state that outlives its database row:

  • On AWS S3 and Google Cloud Storage, abort incomplete multipart uploads after two days on every purpose-specific bucket.
  • Azure automatically removes uncommitted blocks after seven days.
  • For an S3-compatible provider, configure incomplete-multipart cleanup when its lifecycle implementation supports it. Check the provider's documentation because support varies.

The provider window should exceed the 24-hour upload-session lifetime so an in-progress completion can still recover. Do not add an object-expiration rule for final upload keys.

Verify storage

After restarting with the new configuration:

  1. Open the app and upload a document to a knowledge base (or set a profile picture).
  2. Confirm an object appears in the corresponding bucket/container.
  3. Reload the page — the file should still render (downloads stream back through the app at /api/files/serve).

If uploads fail, check the app logs for credential or permission errors (see Troubleshooting).

Was this helpful?
Suggest an edit