Skip to main content

Standalone (Single Node)

Run the entire DocAI Fabric platform on a single machine with one Docker Compose file. This is the simplest way to host the product yourself and is intended for demos, evaluations, and small workloads.

For production workloads that need redundancy and horizontal scale, use the High Availability (Multi-Node) model instead.

What you get​

Everything runs on one host: the application, the workflow workers that process your documents, the built-in OCR engines, the web interface, and a Redis instance for the job queue. The only things that live outside the host are the language model endpoints you connect (your own GPU servers and/or cloud providers) and the licensing service.

┌───────────────────────────────────────────────────────────────┐
│ Your machine (VM or laptop) │
│ Docker + Docker Compose │
│ │
│ ┌──────────────────────┐ ┌──────────────┐ │
│ │ docaifabric │◄──────►│ redis │ │
│ │ app + workers │ │ job queue │ │
│ │ + OCR (Paddle/Tess) │ └──────────────┘ │
│ │ + web UI │ │
│ └──────────┬───────────┘ │
│ │ local volume: ./data/storage │
└───────────────┼───────────────────────────────────────────────┘
│ outbound HTTPS
├──► Your OpenAI-compatible language model endpoints (added in the UI)
└──► license.docaifabric.com (licensing & usage)

Key points:

  • OCR runs entirely on this host. The image bundles the PaddleOCR and Tesseract engines, so no cloud OCR service is required and no document images leave your machine for text recognition.
  • A default language model is set in your .env (any OpenAI-compatible endpoint). Additional models are added in the app on the Models page after setup. See Connecting Models for details.
  • All your data lives in one folder (./data/storage by default), which makes backup and restore a simple file copy.

Where it runs​

You can run this on a dedicated virtual machine, a physical server, or even a laptop for a quick demo. The only requirement is a working Docker installation.

Operating system​

The application ships as a Linux container for the x86_64 (amd64) architecture, but Docker lets you run it on any major operating system:

Operating systemSupportedNotes
Linux (x86_64)Yes (recommended)Native. Best choice for anything beyond a quick demo. Install Docker Engine and the Compose plugin.
Windows 10 / 11 (x86_64)YesInstall Docker Desktop with the WSL 2 backend. The Linux container runs natively.
macOS (Intel)YesInstall Docker Desktop. The container runs natively.
macOS (Apple Silicon: M1/M2/M3/M4)Yes, with one settingDocker Desktop runs the x86_64 image through emulation. Enable Settings > General > Use Rosetta for x86/amd64 emulation, and set RULE_EXECUTOR_USE_VM=true in your .env (see below). Fine for demos, slower than a native x86_64 host.

In short: any OS that can run Docker works. The only customization is for Apple Silicon Macs, which need the Rosetta emulation setting and one extra environment variable.

Sizing​

ResourceMinimum (demo)Recommended
CPU4 cores4 or more cores
RAM8 GB8 GB or more
Disk20 GB free50 GB or more, depending on document volume

OCR is the most CPU-intensive part of the pipeline and runs on this host, so add CPU cores if you plan to process many documents at once.

Prerequisites​

Before you start, make sure you have:

  1. Docker and Docker Compose. Docker Engine 20.10 or newer (Linux), or the latest Docker Desktop (Windows / macOS). Compose v2 is included with both.

  2. Outbound network access from the host for the image pull, licensing, and the language models you connect. The exact destinations, protocols, and ports are listed under Network and connectivity below.

  3. At least one OpenAI-compatible language model endpoint. The deployment needs one default LLM to start the processing pipeline, set via .env (Step 4). It can be an on-prem GPU server (Ollama, vLLM, ...), a cloud provider (OpenAI, Azure, Google, Anthropic, or compatible), or a combination. You can add more models in the app afterward on the Models page.

  4. Registry access from us. Before you can pull the image, request a registry username and access token for signing in to ghcr.io from deployment@docaifabric.com. The image reference itself is already set in the .env below (ghcr.io/docaifabric/docaifabric:latest).

    You generate your own ADMIN_API_KEY and CREDENTIAL_ENCRYPTION_KEY later in this guide; those never come from us.

Network and connectivity​

Every connection the application makes is outbound. The only inbound requirement is the port your users use to reach the web interface.

Outbound (from the host)​

DestinationProtocol and portRequiredPurpose
license.docaifabric.comHTTPS (TCP 443)YesLicensing and usage reporting
ghcr.io (GitHub Container Registry)HTTPS (TCP 443)YesPull the application image and updates
Your on-prem model serversHTTP or HTTPS (TCP, the port you expose)If usedClassification and extraction on your own GPU servers
api.anthropic.comHTTPS (TCP 443)If usedAnthropic Claude models
api.openai.comHTTPS (TCP 443)If usedOpenAI models
Your Azure OpenAI / Google endpointsHTTPS (TCP 443)If usedModels hosted on those providers
<your-resource>.cognitiveservices.azure.comHTTPS (TCP 443)If usedVoice input for the assistant (see Enable voice input)
Your SMTP relaySMTP (TCP 587 or 25, per your relay)If usedOutgoing email: invitations, password resets, notifications (see Enable email notifications)

Inbound (to the host)​

PortProtocolPurpose
8080HTTP, or HTTPS if you enable itWeb interface and API. Plain HTTP is fine for a local demo. For anything beyond that, see Serve HTTPS with your own certificate.
note
  • On-prem model endpoints use whichever port you chose when you deployed the model server (for example, 8000 for vLLM or 11434 for Ollama). This traffic stays on your internal network. Plain HTTP is acceptable there because it never leaves your network; use HTTPS if your security policy requires it.
  • Cloud model providers are all reached over HTTPS on port 443. You only need to open the ones you actually connect.
  • Outbound proxy. If your network requires an outbound HTTP proxy, set the standard HTTP_PROXY, HTTPS_PROXY, and NO_PROXY variables in your .env file.

Setup: your part​

Step 1: Install Docker​

Follow the official instructions for your operating system:

  • Linux: install Docker Engine and the Compose plugin.
  • Windows / macOS: install Docker Desktop and start it.

Verify it works:

docker --version
docker compose version

Step 2: Create a working folder​

Create one working folder for the deployment. It holds your Compose file, your .env settings file, and a data/storage subfolder where all application data is kept.

mkdir -p ~/docai/data/storage
cd ~/docai

This creates ~/docai (the working folder) with data/storage inside it, and moves you into ~/docai. Run the remaining commands from there.

Step 3: Add the Compose file​

Save the following as docker-compose.yml in the working folder (~/docai). You can copy the block below or download docker-compose.yml:

services:
redis:
image: redis:7-alpine
command: redis-server --appendonly yes --maxmemory 512mb --maxmemory-policy noeviction
volumes:
- redis_data:/data
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 3s
retries: 5
restart: unless-stopped

docaifabric:
image: ${DOCAI_IMAGE}
# The image is x86_64 (amd64) only. This line lets it run on Apple Silicon
# Macs under emulation; on a native x86_64 host it is a harmless no-op.
platform: linux/amd64
depends_on:
redis:
condition: service_healthy
ports:
- "8080:8080"
volumes:
- ./data/storage:/app/storage
environment:
# Connect to the Redis service defined below
- REDIS_URL=redis://redis:6379/0
# Deployment topology. For a docker-compose on-prem install this is always
# self_hosted_onprem; the app refuses to start if it is unset. Leave as-is.
- DEPLOYMENT_MODE=${DEPLOYMENT_MODE:-self_hosted_onprem}
# Secrets (set these in .env)
- ADMIN_API_KEY=${ADMIN_API_KEY}
- TENANT_MODELS_ENCRYPTION_KEY=${TENANT_MODELS_ENCRYPTION_KEY:-}
- CREDENTIAL_ENCRYPTION_KEY=${CREDENTIAL_ENCRYPTION_KEY:-}
- CREDENTIAL_ENCRYPTION_KEY_PREVIOUS=${CREDENTIAL_ENCRYPTION_KEY_PREVIOUS:-}
# Address used for the links inside emails and notifications. It does not
# change where the app listens: that is the "ports:" line above (8080).
- APP_URL=${APP_URL:-http://localhost:8080}
# OCR engines. The two bundled engines run locally, no cloud. Microsoft
# Read is optional and calls Azure Document Intelligence (needs the
# endpoint + key below and outbound HTTPS). Values come from .env (Step 4).
- RAPID_OCR_ENABLED=${RAPID_OCR_ENABLED:-true}
- TESSERACT_OCR_ENABLED=${TESSERACT_OCR_ENABLED:-true}
- MICROSOFT_OCR_ENABLED=${MICROSOFT_OCR_ENABLED:-false}
- MICROSOFT_OCR_ENDPOINT=${MICROSOFT_OCR_ENDPOINT:-}
- MICROSOFT_OCR_KEY=${MICROSOFT_OCR_KEY:-}
- DEFAULT_OCR_ENGINE=${DEFAULT_OCR_ENGINE:-rapid_ocr}
# Default LLM — any OpenAI-compatible endpoint. Values come from .env (Step 4).
- LLM_BASE_URL=${LLM_BASE_URL:-}
- LLM_MODEL=${LLM_MODEL:-}
- LLM_API_KEY=${LLM_API_KEY:-}
- LLM_PROTOCOL=${LLM_PROTOCOL:-openai_compatible}
- LLM_AUTH_MODE=${LLM_AUTH_MODE:-bearer}
# Advanced: several LLM endpoints (failover / load-balance) as a JSON array.
- AZURE_OPENAI_ENDPOINTS=${AZURE_OPENAI_ENDPOINTS:-}
# AI Assistant. By default it uses the Default LLM above and turns on
# automatically once a Default LLM is set. Set ASSISTANT_ENABLED=false to
# turn it off, or ASSISTANT_MODEL to give it a different model (Step 4).
- ASSISTANT_ENABLED=${ASSISTANT_ENABLED:-true}
- ASSISTANT_MODEL=${ASSISTANT_MODEL:-}
# Voice input for the AI Assistant (optional, off by default). Uses Azure
# AI Speech: needs an Azure resource and outbound HTTPS to it, and
# recordings leave this host. Values come from .env (see "Optional:
# enable voice input" in the guide).
- AUDIO_TRANSCRIPTION_ENABLED=${AUDIO_TRANSCRIPTION_ENABLED:-false}
- AUDIO_TRANSCRIPTION_PROVIDER=${AUDIO_TRANSCRIPTION_PROVIDER:-azure_speech}
- AUDIO_TRANSCRIPTION_ENDPOINT=${AUDIO_TRANSCRIPTION_ENDPOINT:-}
- AUDIO_TRANSCRIPTION_API_KEY=${AUDIO_TRANSCRIPTION_API_KEY:-}
- AUDIO_TRANSCRIPTION_LOCALES=${AUDIO_TRANSCRIPTION_LOCALES:-}
# Email notifications (optional). Set the SMTP_* values in .env to enable
# invitation and notification emails; email stays off while SMTP_HOST is empty.
- SMTP_HOST=${SMTP_HOST:-}
- SMTP_PORT=${SMTP_PORT:-587}
- SMTP_USER=${SMTP_USER:-}
- SMTP_PASSWORD=${SMTP_PASSWORD:-}
- SMTP_FROM=${SMTP_FROM:-}
- SMTP_FROM_NAME=${SMTP_FROM_NAME:-}
- SMTP_USE_TLS=${SMTP_USE_TLS:-true}
- SMTP_TIMEOUT=${SMTP_TIMEOUT:-15}
# Additional interface languages. Leave unset unless we have given you a
# value for your install (see .env).
- UI_OPTIONAL_LANGUAGES=${UI_OPTIONAL_LANGUAGES:-}
# Destinations on your own network (see .env). Empty keeps the default.
- EGRESS_ALLOW_PRIVATE_HOSTS=${EGRESS_ALLOW_PRIVATE_HOSTS:-}
# Apple Silicon Macs only (see .env). Harmless to leave as-is elsewhere.
- RULE_EXECUTOR_USE_VM=${RULE_EXECUTOR_USE_VM:-false}
restart: unless-stopped
healthcheck:
test: ["CMD-SHELL", "python -c 'import requests; requests.get(\"http://localhost:8080/health\", timeout=5)'"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s

volumes:
redis_data:

The image ships with sensible defaults for storage (local filesystem at the mounted ./data/storage folder) and the workflow workers, so the Compose file only sets what is specific to your install. The remaining settings, including which OCR engines to run, go in your .env in the next step.

Step 4: Create your settings file​

Save the following as .env in the working folder (~/docai), then fill in the values. You can copy the block below or download env.example and rename it to .env:

# Application image. :latest is the most recent release and is simplest for a
# demo. For a long-lived or production install, pin the exact version tag we
# give you at onboarding (e.g. ...:2026.06.25-2) so you can roll back to a known
# version. Check what you are running any time with GET /api/version.
DOCAI_IMAGE=ghcr.io/docaifabric/docaifabric:latest

# Deployment topology. The Compose file already sets this to self_hosted_onprem
# for you, so you normally leave this line commented out. Only uncomment to
# override, and only ever to self_hosted_onprem for a docker-compose install.
# DEPLOYMENT_MODE=self_hosted_onprem

# Admin key for management operations. Generate a random value:
# openssl rand -hex 32
ADMIN_API_KEY=

# Encrypts the secrets you save in the app or with the CLI: language model
# API keys, the client secret of a single sign-on connection, the SMTP
# password, and the credentials of import connectors. Without it, saving them
# is refused.
# Generate ONCE. Never just replace it: to change it, follow "Rotating the
# encryption key" in the guide (see warning below):
# openssl rand -base64 32 | tr '+/' '-_'
CREDENTIAL_ENCRYPTION_KEY=

# Only while rotating CREDENTIAL_ENCRYPTION_KEY: the key it replaced (several,
# comma-separated). It only decrypts. Empty the rest of the time.
CREDENTIAL_ENCRYPTION_KEY_PREVIOUS=

# Older installations only. If this already has a value, keep it: model API
# keys saved so far are encrypted with it. Once CREDENTIAL_ENCRYPTION_KEY is set
# too, they move under that key at the next start; empty this only when
# "da admin credentials status" shows nothing left to re-encrypt. A new
# installation leaves it empty.
TENANT_MODELS_ENCRYPTION_KEY=

# Address used for the links inside invitation, password-reset and
# notification emails. It does not change where the app listens, which is
# always port 8080 (see the "ports:" line in docker-compose.yml). Set it to
# the address your users type in the browser, including the port.
APP_URL=http://localhost:8080

# OCR engines (run locally, no cloud). Keep at least one enabled.
RAPID_OCR_ENABLED=true
TESSERACT_OCR_ENABLED=true
# Optional: Microsoft Read (Azure Document Intelligence). Calls Azure, so the
# container needs outbound HTTPS to the endpoint below. Leave disabled to stay
# fully local. To use it, set all three:
# MICROSOFT_OCR_ENABLED=true
# MICROSOFT_OCR_ENDPOINT=https://<your-resource>.cognitiveservices.azure.com/
# MICROSOFT_OCR_KEY=<your-key>
# Default engine for new projects (must be one you enabled above).
DEFAULT_OCR_ENGINE=rapid_ocr

# Default LLM — any OpenAI-compatible endpoint (Ollama, vLLM, a cloud key, ...).
# IMPORTANT: this URL must be reachable from INSIDE the container. A bare name
# like http://ollama:11434 only works if "ollama" is a service in THIS Compose
# file (it is not, by default). See "Default LLM" below for the right address.
# Example below: Ollama running on the same machine, reached via host.docker.internal.
LLM_BASE_URL=http://host.docker.internal:11434
LLM_MODEL=gemma4
LLM_API_KEY= # leave blank for Ollama; set the key for cloud providers

# AI Assistant (optional). It uses the Default LLM above unless you set a
# different model here. Set ASSISTANT_ENABLED=false to turn the assistant off.
# ASSISTANT_MODEL=
# ASSISTANT_ENABLED=true

# Voice input for the AI Assistant (optional, off by default). It uses Azure
# AI Speech, so the container needs outbound HTTPS to your Azure resource and
# recordings leave this host. To use it, set the first three:
# AUDIO_TRANSCRIPTION_ENABLED=true
# AUDIO_TRANSCRIPTION_ENDPOINT=https://<your-resource>.cognitiveservices.azure.com
# AUDIO_TRANSCRIPTION_API_KEY=<your-key>
# Languages to recognise, comma-separated, at most 10 (default en-US).
# AUDIO_TRANSCRIPTION_LOCALES=en-US

# Email notifications (optional). Set SMTP_HOST to enable invitation and
# notification emails; leave it unset to keep email off. See "Enable email
# notifications" in this guide for examples and details.
# SMTP_HOST=
# SMTP_PORT=587
# SMTP_USER=
# SMTP_PASSWORD=
# SMTP_FROM=
# Sender name shown in mail clients (default: DocAI Fabric). Set your own
# brand here, or empty to send just the address.
# SMTP_FROM_NAME=
# SMTP_USE_TLS=true
# Socket timeout in seconds (default 15). In locked-down / internal networks
# where outbound SMTP is blocked, this bounds the failure instead of hanging.
# SMTP_TIMEOUT=15

# Additional interface languages. Leave unset unless we have given you a value
# for your install.
# UI_OPTIONAL_LANGUAGES=

# Model endpoints, IMAP hosts, single sign-on identity providers and business
# rule HTTP calls may be on your own network (private addresses); loopback and
# link-local never.
# Set to false to allow only public internet addresses.
# EGRESS_ALLOW_PRIVATE_HOSTS=true

# Apple Silicon Macs only. This runs business rules and SLA scripts without
# their sandbox, so set it only when everyone who writes rules or SLA scripts
# may be trusted with the server itself.
# RULE_EXECUTOR_USE_VM=true
Keep your encryption keys safe and stable

CREDENTIAL_ENCRYPTION_KEY encrypts the secrets you save: model API keys, the client secret of a single sign-on connection, the SMTP password you set with the CLI, and the credentials of import connectors. Generate it once and store a copy somewhere safe. Never just replace it: to change it, follow Rotating the encryption key. If it is lost or replaced, the secrets it encrypted can no longer be decrypted and must be entered again. Back up this file together with your data (see Backup and restore).

An installation set up with TENANT_MODELS_ENCRYPTION_KEY keeps it, unchanged: the model API keys saved so far are encrypted with it. Once CREDENTIAL_ENCRYPTION_KEY is set as well, the application moves them under that key when it starts, and TENANT_MODELS_ENCRYPTION_KEY can be emptied when da admin credentials status shows nothing left to re-encrypt (see Updating to the version with a key per organization). Until then the same rules apply to it.

Generate CREDENTIAL_ENCRYPTION_KEY with the exact command shown above: it must be 32 bytes in URL-safe base64, which the tr step makes of the openssl output.

OCR engines. Both bundled engines run locally on this host, no cloud: PaddleOCR (RAPID_OCR_ENABLED) and Tesseract (TESSERACT_OCR_ENABLED). Keep at least one true, or the workflow will report that no OCR engine is available. DEFAULT_OCR_ENGINE picks which one new projects use by default. Tesseract is the more robust choice under emulation (for example on an Apple Silicon Mac). Microsoft Read is optional and not local: set MICROSOFT_OCR_ENABLED=true plus MICROSOFT_OCR_ENDPOINT and MICROSOFT_OCR_KEY to use Azure Document Intelligence (the container then needs outbound HTTPS to that endpoint), and set DEFAULT_OCR_ENGINE=microsoft_read to make it the default.

Default LLM. The deployment needs at least one language model to start the processing pipeline. Point it at any OpenAI-compatible endpoint with the three LLM_* variables. LLM_MODEL must match the model name the server actually serves (check with ollama list for Ollama).

LLM_BASE_URL must be reachable from inside the container

The application runs in a container, so LLM_BASE_URL is resolved from the container's network, not your shell. A short hostname like http://ollama:11434 only works if ollama is a service in this Compose file (it is not, by default) — otherwise you get Name or service not known and the model's circuit trips. Use the address that matches where your LLM runs:

Where the LLM runsLLM_BASE_URL
Same machine, Docker Desktop (macOS / Windows)http://host.docker.internal:11434
Same machine, Linux Docker enginehttp://host.docker.internal:11434 and add extra_hosts: ["host.docker.internal:host-gateway"] under the docaifabric service — or use the host's LAN IP
Another server (e.g. a GPU box)http://<that-host-ip-or-name>:11434

Also start Ollama with OLLAMA_HOST=0.0.0.0 so it accepts connections from the container, not just localhost. Verify reachability from inside the container:

docker compose exec docaifabric python -c "import httpx,os; u=os.environ['LLM_BASE_URL']; print(httpx.get(u.rstrip('/')+'/api/tags', timeout=10).status_code)"
  • On-prem (Ollama / vLLM, etc.): set LLM_BASE_URL per the table above, LLM_MODEL=gemma3, leave LLM_API_KEY blank. http:// is fine for endpoints on your own network.
  • Cloud (OpenAI and compatible): LLM_BASE_URL=https://api.openai.com, LLM_MODEL=gpt-4o-mini, LLM_API_KEY=sk-....

This is the deployment default. Once running, you (and tenant admins) can add more models, including your DGX servers and cloud providers, on the Models page. To configure several default endpoints for failover or load-balancing, omit the LLM_* vars and set AZURE_OPENAI_ENDPOINTS to a JSON array instead.

These model settings apply at the first start

The first time the application starts, it saves the model settings above (LLM_*, AZURE_OPENAI_ENDPOINTS, ASSISTANT_MODEL) as the deployment's system models. After that, changing them in .env changes nothing. To change system models, use da admin models with the admin key: list, add, update, remove, set-default, apply -f <file>, export. A key you type at its prompt is stored encrypted, so CREDENTIAL_ENCRYPTION_KEY must be set. To go back to what .env says, set SYSTEM_MODELS_OVERRIDE to a new value (any text, for example today's date) and restart: the application replaces the system models with the ones in .env once, after backing up the current set (da admin models backups, da admin models restore <backup>). da admin models diff --deploy shows what that would change.

AI Assistant. The in-app assistant uses the same Default LLM by default, so you do not have to configure a second model. It turns on automatically once a Default LLM is set. Two optional knobs:

  • To run the assistant on a different model than the pipeline (for example a larger model for chat), set ASSISTANT_MODEL in .env to that model's name. It must be a model your deployment can reach: either the Default LLM, one of the endpoints in AZURE_OPENAI_ENDPOINTS, or a model you add on the Models page.
  • To turn the assistant off, set ASSISTANT_ENABLED=false.

A tenant admin can also override the assistant model per tenant on the Models page (Defaults tab), which takes precedence over ASSISTANT_MODEL.

Other optional settings

For throughput tuning, add NUM_WORKFLOW_WORKERS, MAX_PARALLEL_OCR, or MAX_PARALLEL_LLM under the docaifabric service's environment: in the Compose file. Set LOG_LEVEL=DEBUG there for more detailed logs.

Step 5: Sign in to the image registry and start​

# Sign in once. We provide the username and access token during onboarding.
docker login ghcr.io

# Start the stack
docker compose up -d

The first start downloads the image and may take a few minutes. Check that it is healthy:

docker compose ps
curl http://localhost:8080/health

Step 6: First sign-in and admin user​

Open the application in a browser:

http://localhost:8080

The port is required. Plain http://localhost will not reach the app: the container publishes port 8080, and nothing is listening on port 80. If you are browsing from a different machine than the one running Docker, replace localhost with the server's address, for example http://10.0.0.5:8080.

Follow the on-screen prompts to create your first administrator account. (Full first-run guidance is in Quick Start.)

Step 7: Connect your language models​

OCR already works with the bundled local engines, so the only thing left is to connect a language model for classification and extraction.

  1. Open the Models page in the application.
  2. Add each endpoint:
    • Your own GPU server: choose the OpenAI-compatible option and enter its URL and key.
    • Cloud provider (optional): add your Anthropic or OpenAI key.
  3. Set which model to use for each task (classification, extraction, and so on).

Full instructions, including testing an endpoint, are in Connecting Models. You can connect several endpoints and the app will balance and fail over across them.

You are now ready to create a project and process documents.

Optional: serve HTTPS with your own certificate​

Out of the box the application serves plain HTTP on port 8080. That is fine for a demo on the machine running Docker, because browsers treat http://localhost as a trusted origin. As soon as users reach the application over the network, at an address like http://10.0.0.5:8080, browsers class it as an untrusted origin and switch some capabilities off.

Enable HTTPS if any of the following apply:

  • Your users open the application from other machines and your security policy requires encrypted traffic.
  • You want the camera-based features: mobile QR capture, live capture from a webcam, and voice input for the AI assistant. Browsers only grant camera and microphone access on a secure origin, and there is no way around that from the application side.

You do not need a reverse proxy for this. The container can terminate TLS itself.

Step 1: Get a certificate​

If your organization has an internal certificate authority, ask for a server certificate for the address your users type, and use it here. This is the better option: the CA is usually already trusted on every managed machine, so nobody sees a browser warning.

For a pilot, a self-signed certificate works. Run this in your working folder (~/docai), replacing 10.0.0.5 with your server's address in all three places:

mkdir -p certs
openssl req -x509 -newkey rsa:2048 -sha256 -days 398 -nodes \
-keyout certs/server.key -out certs/server.crt \
-subj "/CN=10.0.0.5" \
-addext "subjectAltName=IP:10.0.0.5"

If your users reach the server by hostname instead of by IP address, use -subj "/CN=docai.example.com" and -addext "subjectAltName=DNS:docai.example.com".

The address must be in subjectAltName

Modern browsers ignore the CN field entirely and read only subjectAltName. A certificate without the matching IP: or DNS: entry is rejected with ERR_CERT_COMMON_NAME_INVALID, and no amount of clicking through gets past it cleanly. Use IP: for an IP address and DNS: for a hostname; they are not interchangeable.

Step 2: Let the container read the files​

The application runs as a non-root user (UID 65532). openssl creates the private key readable only by the user who ran it, so without this step the container cannot open it and fails to start.

sudo chown 65532:65532 certs/server.key certs/server.crt
sudo chmod 600 certs/server.key
sudo chmod 644 certs/server.crt

Step 3: Point the container at them​

Add three things to the docaifabric service in your docker-compose.yml: the certificate folder as a second volume, a command: line that turns on TLS, and a health check that speaks HTTPS.

docaifabric:
# ... leave the existing image, ports and environment lines as they are ...
volumes:
- ./data/storage:/app/storage
- ./certs:/app/certs:ro
command: >
app:app --host 0.0.0.0 --port 8080
--log-level info --timeout-graceful-shutdown 270
--ssl-certfile /app/certs/server.crt
--ssl-keyfile /app/certs/server.key
healthcheck:
test: ["CMD-SHELL", "python -c 'import requests; requests.get(\"https://localhost:8080/health\", timeout=5, verify=False)'"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s

The port does not change. The application still listens on 8080 and is still published as 8080:8080; it now speaks HTTPS on it instead of HTTP.

Replace the health check, do not add a second one

The health check in the Compose file from Step 3: Add the Compose file uses http://. If you leave it in place, Docker will report the container as unhealthy for ever while the application runs perfectly well. Overwrite that block with the one above.

Step 4: Update the address in .env​

APP_URL=https://10.0.0.5:8080

Otherwise the links inside invitation and notification emails still point at the old http:// address.

Step 5: Restart and check​

docker compose up -d
docker compose logs docaifabric | grep "Uvicorn running"

The log line should read Uvicorn running on https://0.0.0.0:8080. To confirm the endpoint answers:

curl -k https://localhost:8080/health

Then open https://10.0.0.5:8080 in a browser.

Tell your users to type https://

Once TLS is on, plain http://10.0.0.5:8080 no longer works at all: the connection is dropped rather than redirected, and the browser reports that the site did not send any data.

Trusting the certificate​

A certificate from your internal CA needs nothing further. A self-signed certificate is its own root, so each client machine has to be told to trust it, otherwise the browser shows a warning on every visit.

Copy certs/server.crt (the certificate only, never the key) to the client machines and install it:

  • Windows: open the file, choose Install Certificate, then Local Machine, then Place all certificates in the following store, and pick Trusted Root Certification Authorities. In a domain, deploy it once by Group Policy instead.
  • macOS: open the file in Keychain Access, add it to the System keychain, then open it and set Trust to Always Trust.
  • Linux: copy it to /usr/local/share/ca-certificates/ and run sudo update-ca-certificates.

Users can also click Advanced and then Proceed on the browser warning without installing anything. The application works and the camera features become available, but the warning comes back in every new browser session and the address bar stays marked as not secure, so this is only reasonable for a short pilot.

Renewing​

The command above issues a certificate valid for 398 days. Note the expiry date, and when it approaches, run the same openssl command again, repeat Step 2, and restart the container with docker compose restart docaifabric. No other changes are needed.

Optional: use Microsoft Read OCR​

The standalone setup runs the two bundled engines (PaddleOCR and Tesseract) entirely on this host. If you have an Azure Document Intelligence resource, you can use Microsoft Read instead of, or alongside, the local engines. Read often gives higher accuracy on dense or low-quality scans.

Microsoft Read is not local

Unlike the bundled engines, Microsoft Read sends page images to Azure. The container needs outbound HTTPS to your Document Intelligence endpoint, and pages leave the host. Keep using the bundled engines if you need to stay fully on-premises.

What you need: an Azure Document Intelligence (Cognitive Services) resource. From the Azure portal, under Keys and Endpoint, copy the Endpoint URL (looks like https://<your-resource>.cognitiveservices.azure.com/) and one Key.

Step 1: confirm the Compose file passes the variables through. Microsoft Read is off by default, so the three variables must be wired from .env into the container. In your docker-compose.yml, the docaifabric service's environment: block should contain:

- MICROSOFT_OCR_ENABLED=${MICROSOFT_OCR_ENABLED:-false}
- MICROSOFT_OCR_ENDPOINT=${MICROSOFT_OCR_ENDPOINT:-}
- MICROSOFT_OCR_KEY=${MICROSOFT_OCR_KEY:-}

If your file instead has a hardcoded MICROSOFT_OCR_ENABLED=false (older template), replace it with the three lines above. A hardcoded value in environment: overrides whatever you put in .env, so Read can never turn on otherwise.

Step 2: set the three variables in .env:

MICROSOFT_OCR_ENABLED=true
MICROSOFT_OCR_ENDPOINT=https://<your-resource>.cognitiveservices.azure.com/
MICROSOFT_OCR_KEY=<your-key>

Keep RAPID_OCR_ENABLED and TESSERACT_OCR_ENABLED as they are if you want the local engines available as alternatives; you do not have to disable them.

Step 3: make Read the default for new projects (optional):

DEFAULT_OCR_ENGINE=microsoft_read

Leave DEFAULT_OCR_ENGINE as rapid_ocr (or tesseract) if you only want Read available as a per-project choice rather than the default.

Step 4: restart and verify:

docker compose up -d
docker compose logs docaifabric | grep -i ocr

You should see lines like:

✅ OCR pool: single endpoint 'ocr-default' from MICROSOFT_OCR_ENDPOINT (key_source=MICROSOFT_OCR_KEY)
✅ Initialized Microsoft OCR service

Microsoft Read will now appear as a selectable engine in each project's OCR step (and as the system default if you set DEFAULT_OCR_ENGINE=microsoft_read).

Read not showing up?

If the log shows the enabled engines as only ['rapid_ocr', 'tesseract'] and there are no OCR pool lines, the container is not seeing MICROSOFT_OCR_ENABLED=true. The usual causes are a hardcoded MICROSOFT_OCR_ENABLED=false in the Compose environment: block (see Step 1), an inline comment on the value (...=true # note is read literally, comment included), or a stale container, which you can recreate with docker compose up -d --force-recreate. Confirm what the container actually sees with docker exec docaifabric printenv | grep -i ocr.

Optional: enable voice input​

The AI assistant can take dictation: users press the microphone button and speak instead of typing. Voice input is off by default. It uses Azure AI Speech, which needs no model deployment and no quota request.

Voice input is not local

Recordings are sent to your Azure resource for transcription, in the region where you created it. The container needs outbound HTTPS to that resource. Leave voice input off if recordings must not leave the host.

What you need: an Azure AI Services (multi-service) or Speech resource, in the region you choose. From the Azure portal, under Keys and Endpoint, copy the Endpoint URL (looks like https://<your-resource>.cognitiveservices.azure.com) and one Key. Use the account endpoint only, with no path after the host name.

Step 1: confirm the Compose file passes the variables through. The variables must be wired from .env into the container. In your docker-compose.yml, the docaifabric service's environment: block should contain:

- AUDIO_TRANSCRIPTION_ENABLED=${AUDIO_TRANSCRIPTION_ENABLED:-false}
- AUDIO_TRANSCRIPTION_PROVIDER=${AUDIO_TRANSCRIPTION_PROVIDER:-azure_speech}
- AUDIO_TRANSCRIPTION_ENDPOINT=${AUDIO_TRANSCRIPTION_ENDPOINT:-}
- AUDIO_TRANSCRIPTION_API_KEY=${AUDIO_TRANSCRIPTION_API_KEY:-}
- AUDIO_TRANSCRIPTION_LOCALES=${AUDIO_TRANSCRIPTION_LOCALES:-}

If your Compose file predates voice input (it has none of these lines), add them. Without them, values in .env never reach the container.

Step 2: set the variables in .env:

AUDIO_TRANSCRIPTION_ENABLED=true
AUDIO_TRANSCRIPTION_ENDPOINT=https://<your-resource>.cognitiveservices.azure.com
AUDIO_TRANSCRIPTION_API_KEY=<your-key>
AUDIO_TRANSCRIPTION_LOCALES=en-US

AUDIO_TRANSCRIPTION_LOCALES is the list of languages the service should recognise, as comma-separated language tags, at most 10, for example en-US,fr-FR,de-DE,it-IT. The service picks the language of each recording from this list, so list only the languages your users speak.

Step 3: serve the application over HTTPS, or open it on localhost. Browsers give the microphone only to a secure origin. See Serve HTTPS with your own certificate.

Step 4: restart and verify:

docker compose up -d
docker compose logs docaifabric | grep -i transcription

You should see a line like:

TranscriptionService initialized (provider: azure_speech, endpoint: https://<your-resource>.cognitiveservices.azure.com/..., locales: en-US)

The microphone button then appears in the assistant's message box.

Microphone button not showing?

If the log says TranscriptionService disabled (AUDIO_TRANSCRIPTION_ENABLED=false), the container is not seeing AUDIO_TRANSCRIPTION_ENABLED=true: check the Compose lines in Step 1, an inline comment on the value (...=true # note is read literally, comment included), or recreate the container with docker compose up -d --force-recreate. If the log says AUDIO_TRANSCRIPTION_ENDPOINT not configured or AUDIO_TRANSCRIPTION_API_KEY not configured, disabling transcription, set the missing value. If the log shows the service initialised but the button is missing or the browser never asks for the microphone, the address is not a secure origin (Step 3).

Optional: enable email notifications​

DocAI Fabric can send transactional emails through any SMTP relay you provide: team invitations, "you've been added to a workspace" notices, self-service password reset links, license notifications, and budget alerts. Email is off by default, and the application works fully without it. Invitations are still created (an admin shares the invitation link manually), but self-service password reset is only available with email enabled.

This section covers the install-time method: set the values in .env while you are already configuring the container. You can also configure and test SMTP live after the deployment is running, without editing files or restarting, using the CLI. See Configure SMTP (CLI); its built-in test-email command is the easiest way to confirm delivery. The two methods work together: the SMTP_* env vars below are the seed default, and any value you later set with the CLI takes precedence over them.

The Compose file from Step 3 already passes the SMTP_* variables into the container, so enabling email is just a matter of setting them in .env. Two typical setups:

# A) Authenticated relay with STARTTLS (a mail provider, Microsoft 365, Exchange):
SMTP_HOST=mail.example.com
SMTP_PORT=587
SMTP_USER=docai@example.com
SMTP_PASSWORD=<password or app password>
SMTP_FROM=docai@example.com

# B) Anonymous internal relay on port 25 (the relay allows your host by source IP):
SMTP_HOST=relay.example.internal
SMTP_PORT=25
SMTP_USE_TLS=false
SMTP_FROM=docai-fabric@example.com

Apply the change and send a test email:

docker compose up -d
# Invite a user from the Team page, then check the log:
docker compose logs docaifabric | grep -i "email sent"

Notes:

  • Connection security. SMTP_USE_TLS=true (the default) uses STARTTLS, the standard for port 587. For a plain-text relay on port 25, set SMTP_USE_TLS=false. Implicit TLS (usually port 465) is not supported; use a STARTTLS port instead.
  • Authentication is optional. If SMTP_USER and SMTP_PASSWORD are both empty, the application sends without logging in. This suits internal relays that allow clients by source IP.
  • SMTP_FROM defaults to SMTP_USER. With an anonymous relay there is no user, so set SMTP_FROM explicitly or the relay will reject the mail.
  • APP_URL appears inside the emails. The action links (accept an invitation, reset a password) and the logo image point at APP_URL, so it must be an address your recipients can reach. With the default http://localhost:8080, links in the emails will not work for anyone but you.
  • The container needs outbound access to the SMTP host and port; see Network and connectivity.

Backup and restore​

Because everything is stored in one folder, backup is a file copy. The folder contains your documents, projects, transactions, model configuration, and the deployment's licensing identity.

Back up​

  1. Stop the stack so files are in a consistent state:
    docker compose stop
  2. Copy the data folder and your settings file to a safe location:
    tar czf docai-backup-$(date +%Y%m%d).tgz data .env
    On a virtual machine you can instead take a VM snapshot while the stack is stopped.
  3. Start the stack again:
    docker compose start
tip

Always include the .env file in your backup. Without CREDENTIAL_ENCRYPTION_KEY (and TENANT_MODELS_ENCRYPTION_KEY on an installation that has one), restored model keys and other saved secrets cannot be decrypted. After rotating the key, a backup taken before the rotation needs the old key.

Restore​

  1. Install Docker and place docker-compose.yml, .env, and the restored data folder in the same working folder.
  2. Start the stack:
    docker compose up -d

Your projects, documents, and configuration come back exactly as they were at backup time.

Rotating the encryption key​

Change CREDENTIAL_ENCRYPTION_KEY once a year, when someone who knew it leaves, or if you think it was exposed. Saved secrets keep working throughout, and nobody enters them again.

  1. Take a backup (see Back up) and keep it with the current key: that backup needs it.
  2. In .env, move the current value of CREDENTIAL_ENCRYPTION_KEY to CREDENTIAL_ENCRYPTION_KEY_PREVIOUS, and set CREDENTIAL_ENCRYPTION_KEY to a new key generated as in Step 4. Check that the docaifabric service's environment: block in docker-compose.yml has the line - CREDENTIAL_ENCRYPTION_KEY_PREVIOUS=${CREDENTIAL_ENCRYPTION_KEY_PREVIOUS:-}; Compose files downloaded before November 2026 lack it, so add it.
  3. Run docker compose up -d. New secrets are encrypted with the new key, and when the application starts it re-encrypts the saved ones under it, in the background; the old key only decrypts.
  4. Check that nothing is left, with the administration CLI:
    da admin credentials status # "To re-encrypt" and "Unreadable" should be 0 everywhere
    If something is still to re-encrypt (a secret saved while the application started), run docker compose restart and check again.
  5. Empty CREDENTIAL_ENCRYPTION_KEY_PREVIOUS in .env, and run docker compose up -d.

A secret status reports as unreadable was saved under a key that is not configured: add that key to CREDENTIAL_ENCRYPTION_KEY_PREVIOUS, or enter the secret again.

These steps are for this single-node install. With several replicas or a rolling update (the High Availability model), deploy the new key in two steps, so that no replica meets a secret under a key it does not have: first put the new key in CREDENTIAL_ENCRYPTION_KEY_PREVIOUS and leave the current one as it is, deploy, then swap the two and deploy again.

Getting updates​

We publish new application image versions over time. Updating is a pull and restart; your data is never touched because it lives in the mounted folder, not in the image.

If DOCAI_IMAGE ends in :latest, updating is just:

docker compose pull # fetch the newest release
docker compose up -d # recreate on the new image
docker image prune -f # optional: remove the old image

We also notify you by email (the address that requested onboarding) when a new version ships, and you can follow the What's New / changelog to track releases. To see what you are running, curl http://localhost:8080/api/version (for example {"version":"2026.06.25-2"}), which is handy when contacting support.

Pinning a version (recommended for production). To control exactly when you move and to be able to roll back, pin a specific tag instead of :latest. Find the newest version without changing your deployment, then set it in .env:

docker pull ghcr.io/docaifabric/docaifabric:latest
docker inspect ghcr.io/docaifabric/docaifabric:latest \
--format '{{range .Config.Env}}{{println .}}{{end}}' | grep APP_VERSION
# then set, e.g.: DOCAI_IMAGE=ghcr.io/docaifabric/docaifabric:2026.06.25-2
docker compose up -d

To roll back, set DOCAI_IMAGE to the previous version tag and run docker compose up -d again. (Rollback requires a specific tag, so production installs should pin rather than track :latest.)

Updating to a version that needs an encryption key for model API keys​

From the first version after 2026.10.10-1, model API keys are always encrypted with a key you set, never with a key built into the application:

  • .env has TENANT_MODELS_ENCRYPTION_KEY with a value: nothing changes, and you can update from any earlier version in one step and roll back to any (until the version with a key per organization: see below).
  • .env has only CREDENTIAL_ENCRYPTION_KEY, or neither: model API keys saved so far were encrypted with a key built into the application, so anyone who could read the data folder could read them. If CREDENTIAL_ENCRYPTION_KEY is set, the application re-encrypts them under it when it starts, and you do nothing. If neither key is set, saving or testing a model with an API key is refused with a message naming CREDENTIAL_ENCRYPTION_KEY, and a model with an API key is left out of processing. To fix it: generate the key as shown in Step 4 and set it in .env; check that the docaifabric service's environment: block in docker-compose.yml has the line - CREDENTIAL_ENCRYPTION_KEY=${CREDENTIAL_ENCRYPTION_KEY:-} (Compose files downloaded before October 2026 lack it: add it, or download the current file and copy your changes into it); then docker compose up -d. The keys already saved are re-encrypted when the application starts; nobody re-enters them. After this, versions from 2026.10.10-1 on can read them; to roll back further, re-enter the model API keys.

Updating to the version with a key per organization​

From a version released after 2026.10.10-2 (its release notes say so), every saved secret (model API keys, single sign-on client secrets, the SMTP password, import connector credentials, security log forwarding secrets) is encrypted with a key derived from CREDENTIAL_ENCRYPTION_KEY for its own organization and purpose, so one organization's secrets cannot be opened with another's key. Nothing needs doing by hand: when the application starts, it re-encrypts the saved secrets in the background, and does so again at every start for anything saved by an older version meanwhile. Saved secrets keep working throughout, and nobody enters them again.

  • Rolling back: versions from 2026.10.10-2 on read the new encryption, so you can roll back to any of them. If you update straight from an older version, roll back only as far as 2026.10.10-2, or restore the backup you took before updating (see Back up).
  • Several nodes (the High Availability model): update every node to 2026.10.10-2 or later first, or stop the nodes on older versions before starting the new one. An older node cannot read the secrets a new node has re-encrypted, so sign-in, email and imports would fail on it.
  • .env has TENANT_MODELS_ENCRYPTION_KEY and CREDENTIAL_ENCRYPTION_KEY: model API keys move under CREDENTIAL_ENCRYPTION_KEY at the first start. Keep TENANT_MODELS_ENCRYPTION_KEY until da admin credentials status shows nothing left to re-encrypt, then empty it.
  • .env has only TENANT_MODELS_ENCRYPTION_KEY: model API keys stay under it and keep working. To move to one key, set CREDENTIAL_ENCRYPTION_KEY as in Step 4 and follow the line above.

To check, with the administration CLI:

da admin credentials status # "To re-encrypt" and "Unreadable" should be 0 everywhere

Updating to a version where steps hold their own settings​

Nothing needs doing by hand. From the release that moves step settings onto the workflow's steps, the application converts every project by itself after it starts, its draft and every published version, while it keeps serving. Before it writes a project it checks that every Split, Classify, Extract and Redact step will run exactly as before, and it keeps a copy of the original files in the project's step_settings_backup/ folder. A project or version the check finds a difference in is converted all the same, and da admin migrations status lists it with the steps that differ; one with no settings file gets a new project's settings. A project that comes in later, imported, copied from the library or rolled back to an older version, is converted as it comes in, and one the background conversion has not reached yet is converted the first time anything touches it: a page that opens it, an API call, the Assistant, a transaction run. Only a project whose settings cannot be read at all is left as it is: it cannot be opened or run (pages, API calls and transactions on it fail with a message saying it could not be converted) and can only be deleted, da admin migrations status gives the reason, and the next start tries it again. One thing the check does not cover: a Data Transform script that reads a step setting (a model, a threshold, the split mode) from project_settings no longer finds it there once the project is converted.

To see where it stands, use the administration CLI:

da admin migrations status # which projects and published versions are converted, and why one is not

Nothing holds the conversion back: the STEP_SETTINGS_WRITES setting of earlier versions is ignored, and you can remove it from your environment: block. Converted and new projects can be read by versions from 2026.10.03-1 on, so rolling back to an older version means restoring the data folder, or the originals the conversion kept in each project's step_settings_backup/ folder.

Automated updates

For production, the High Availability model supports an automated update pipeline so you do not have to run these commands by hand. Ask us if you would like the same for a long-lived standalone install.

Accessing logs​

All logs are available through Docker on the host.

# Follow the application logs live
docker compose logs -f docaifabric

# Last 200 lines
docker compose logs --tail=200 docaifabric

# Redis logs
docker compose logs -f redis

To increase detail, set LOG_LEVEL=DEBUG in .env and restart (docker compose up -d).

Sharing logs with support. Save the logs to a file and send it to us:

docker compose logs --no-color --tail=2000 docaifabric > docai-logs.txt

For querying by transaction, capping how much log Docker keeps on disk, and what to look for in a failure, see Application Logs (On-Premises).

Troubleshooting​

SymptomLikely causeWhat to do
docker compose up cannot pull the imageNot signed in to the registryRun docker login ghcr.io with the username and token we provided
Container exits at startup with DeploymentModeError: DEPLOYMENT_MODE is not setAn older Compose file that predates this variableAdd - DEPLOYMENT_MODE=${DEPLOYMENT_MODE:-self_hosted_onprem} to the docaifabric service's environment: block (it is in the Compose file above), then docker compose up -d. Setting it in .env alone is not enough — the Compose file does not pass .env straight through to the container
no matching manifest for linux/arm64 on pullApple Silicon Mac pulling the x86_64-only imageKeep platform: linux/amd64 on the docaifabric service (it is in the Compose file above), enable Rosetta in Docker Desktop, and set RULE_EXECUTOR_USE_VM=true
App container is unhealthy on first startStill downloading or initializingWait for start_period (about 40 seconds), then check docker compose ps again
A step fails with "No model is available for task … on this account", and the log says "System models: none configured"No default LLM configured, and the account has no model of its own for that taskEither set LLM_BASE_URL / LLM_MODEL in .env (or AZURE_OPENAI_ENDPOINTS) and restart, or add the account's own model on the Models page and choose it as the default for the task. Steps before the first step that needs a model still run; the transaction then fails at that step, and can be re-run once a model is added
Classification or extraction failsThe default LLM is unreachable or the model name is wrongVerify LLM_BASE_URL is reachable from the container and LLM_MODEL matches a model the server serves; or add/select a model on the Models page
Model test shows "failed" in the UI even though the HTTP log says 200; processing keeps pausing; logs show Name or service not known and circuits OPEN (model_id=…)The LLM endpoint hostname is not resolvable/reachable from inside the container (for example LLM_BASE_URL=http://ollama:11434 with no ollama service)Set LLM_BASE_URL to an address the container can reach (host.docker.internal for a same-machine LLM, or the host's IP) per Default LLM, ensure the LLM listens on 0.0.0.0, then docker compose up -d (this also clears the tripped circuit)
The assistant answers every message with "AI assistant unavailable", or (on older builds) "Method Not Allowed"No default LLM was configured when the container started, so the assistant did not startSet LLM_BASE_URL / LLM_MODEL in .env (or ASSISTANT_ENABLED=true if you turned it off), restart, and check the logs for "Assistant routes registered"
"No OCR engine available"Both OCR engines disabled, or PaddleOCR failed to start (common under emulation)Confirm RAPID_OCR_ENABLED=true and TESSERACT_OCR_ENABLED=true, restart, and check docker compose logs docaifabric | grep -i ocr. Under emulation, Tesseract is the reliable engine
Saved model keys or other secrets stopped working after a changeCREDENTIAL_ENCRYPTION_KEY was replaced without the rotation steps, or TENANT_MODELS_ENCRYPTION_KEY changed on an installation that has onePut the old key in CREDENTIAL_ENCRYPTION_KEY_PREVIOUS (or restore TENANT_MODELS_ENCRYPTION_KEY) and docker compose up -d: the start re-encrypts the secrets under the current key. Or re-enter them
No microphone button in the assistant, or dictation does nothingVoice input is off by default, the Compose file lacks the AUDIO_TRANSCRIPTION_* lines, or the address is not a secure originFollow Enable voice input and check docker compose logs docaifabric | grep -i transcription
Saving a model API key, a single sign-on client secret, the SMTP password or a connector credential is refused with "the deployment has no encryption key configured"CREDENTIAL_ENCRYPTION_KEY is not set, or your Compose file predates itGenerate it as shown in Step 4, set it in .env, add - CREDENTIAL_ENCRYPTION_KEY=${CREDENTIAL_ENCRYPTION_KEY:-} to the docaifabric service's environment: block if it is missing, then docker compose up -d
Logs show Fernet key must be 32 url-safe base64-encoded bytesCREDENTIAL_ENCRYPTION_KEY was generated without the tr '+/' '-_' step, or was cut shortGenerate it again with the exact command in Step 4. Nothing can be saved under a malformed key, so nothing is lost
Business rules misbehave on a MacApple Silicon emulationSet RULE_EXECUTOR_USE_VM=true in .env and restart. This turns off the rule sandbox: acceptable when everyone who writes rules or SLA scripts is trusted with the server

Next steps​