Deployment Guide
This guide covers deploying Enprivacy 3.0 into your own environment, including container configuration, recommended encryption settings, and signature verification.
Prerequisites
Section titled “Prerequisites”Enprivacy 3.0 is distributed as Open Container Initiative (OCI) / Docker container images and is designed to run on most cloud and on-premises environments. Before deploying, ensure you have:
- Container runtime — Docker Engine 24+ with Compose v2, or a Kubernetes cluster / cloud ‘container application’ service (e.g. AWS ECS, Azure Container Apps, Google Cloud Run).
- Operating system (for self-managed hosts) — a 64-bit Linux distribution such as Ubuntu 22.04+ or Debian 12.
- PostgreSQL 16+ (18+ recommended) with the
hstoreandpgvectorextensions. This may be a managed service or a self-hosted container — see Database Setup below. - Blob / object storage — an S3-compatible service (managed, or self-hosted such as MinIO or Garage) for document uploads and redacted output.
- GPU (LLM service only) — a T4-equivalent or better GPU, unless you use an external/hosted LLM provider (e.g. AWS Bedrock, Azure OpenAI, Google Vertex).
For full per-service resource requirements and sizing, see the Quick Start Guide.
Container Image
Section titled “Container Image”All Enprivacy 3.0 release images are published to the GitHub Container Registry:
ghcr.io/enprivacyhq/enprivacy:v<version>For production, pin the image by digest (ghcr.io/enprivacyhq/enprivacy@sha256:…)
rather than by tag — the digest is the provenance handle that signature
verification and SBOM attestation attach to.
The registry is private. Enprivacy issues a read-only pull token — a GitHub
personal access token (classic) scoped to read:packages, owned by an
Enprivacy bot account — together with that account’s username. Log in with
both, and note the token’s expiry date so it is replaced before pulls start
failing:
echo "$GHCR_TOKEN" | docker login ghcr.io -u <username> --password-stdinVerifying Image Signatures
Section titled “Verifying Image Signatures”All release images are signed with Cosign using keyless (GitHub OIDC) signing — there is no long-lived public key. Verification checks that the image was signed by Enprivacy’s release workflow:
cosign verify \ --certificate-identity-regexp \ "https://github.com/enprivacyhq/enprivacy-platform/.github/workflows/release.yml" \ --certificate-oidc-issuer https://token.actions.githubusercontent.com \ ghcr.io/enprivacyhq/enprivacy:v<version>Each GitHub Release’s notes embed the exact command for that release. Every release also ships source and image SBOMs (SPDX JSON), attested in the registry via Cosign — see Supply Chain Security.
Container UID
Section titled “Container UID”The container runs as the non-root user rails, UID 1000 / GID 1000. Any
volumes you mount (e.g. /rails/storage for local file storage) must be
writable by that UID.
Services and Resource Requirements
Section titled “Services and Resource Requirements”A standard deployment comprises five services, a PostgreSQL database, and blob storage:
| Service | Purpose | GPU |
|---|---|---|
| Web | End-user and administrative interfaces; public Job interfaces | No |
| Job | Background job and task processing | No |
| Inference adapter | Embedding, reranking and entity detection — mandatory; see Inference Adapter | No |
| LLM | System LLM — a managed endpoint (e.g. Azure AI Foundry, AWS Bedrock) or self-hosted vLLM | Only if self-hosted (T4-equivalent) |
| OCR | Text extraction via Docling | No |
The Web and Job services support horizontal scaling and are stateless — all state is held in the database or blob storage. See the Quick Start Guide for minimum and recommended CPU/RAM/disk per service.
Model data
Section titled “Model data”The Web and Job services load no machine-learning models. The Inference Adapter runs them, and its image carries the weights, so there is no model cache to mount or stage.
The one data set the platform image holds itself is the address data used by
the address refiners (~1.9 GB), baked in at /rails/models. A volume mounted
there replaces it, which is only needed for an image built without it; the
published image needs nothing mounted.
A self-hosted LLM downloads and caches its own models; see its documentation.
Database Setup
Section titled “Database Setup”Enprivacy 3.0 requires an always-on PostgreSQL database (16+, 18+ recommended) that holds state for the Web and Job services.
-
Create a database schema.
-
Create a database user with a password and all privileges over that schema. The service uses this user to prepare the schema on first run, to apply schema updates during upgrades, and for day-to-day operations.
-
Enable the required extensions:
hstoreandpgvector.PostGIS and pgRouting were required by earlier releases and are no longer used. If an existing database has them, they can be dropped — on a managed service that also means they can come off the extension allow-list.
The Web service prepares the schema automatically on boot (creating the database if absent and applying pending migrations). If a dedicated migration job owns schema changes — for example when the runtime database user has no DDL rights — set SKIP_DB_PREPARE=1 on the Web service and run bin/rails db:prepare from the migration job instead.
For improved security, rotate the database password using either a single-user or dual-user configuration — see the Operations Guide.
Deployment Models
Section titled “Deployment Models”Enprivacy 3.0 supports several deployment models; see the Quick Start Guide for full detail.
- Cloud managed services (recommended) — deploy each service via the cloud provider’s container application service or Kubernetes, using managed PostgreSQL and managed object storage. For Azure, a complete Terraform reference architecture is available — see Deploy on Azure with Terraform.
- Self-hosted containers — run the database (with the required extensions) and an S3-compatible store (e.g. MinIO or Garage) as containers.
- All-In-One (demonstration only) — run the Web, Job, OCR, and Database services in a single environment with local-disk durable storage and an external LLM service. Not recommended for production.
Configuration
Section titled “Configuration”All configuration is supplied through environment variables. Enprivacy 3.0 is single-tenant per install: these values are set once per deployment, not per workspace.
Required to boot
Section titled “Required to boot”| Variable | Purpose |
|---|---|
SECRET_KEY_BASE |
Rails session/signing secret (generate: openssl rand -hex 64) |
EP_ENCRYPTION_PRIMARY_KEY |
Application-level encryption of sensitive database columns. Rotatable as a comma-delimited list: append the new key last (it becomes the active key; the older keys keep decrypting existing data) |
EP_ENCRYPTION_INDEX_KEY |
HMAC key for the one-way blind indexes that make encrypted identity fields searchable. Stable for the life of the data — never rotate without re-indexing |
EP_ENCRYPTION_KEY_DERIVATION_SALT |
As above (key derivation). A single value — never rotatable |
DATABASE_URL |
PostgreSQL connection URL (postgres://user:pass@host:5432/dbname?sslmode=require) |
EP_HOST |
The public hostname users reach the app on — drives link generation and the OAuth callback URL |
EP_LLM_URL |
OpenAI-compatible endpoint for the platform’s system LLM |
EP_OCR_URL |
Endpoint of the OCR (Docling) service |
EP_INFERENCE_URL / EP_INFERENCE_API_KEY |
The Inference Adapter’s endpoint and API key. Web and Job refuse to start without both — see Inference Adapter |
Authentication
Section titled “Authentication”| Variable | Default | Purpose |
|---|---|---|
EP_AUTH_CLIENT_ID / EP_AUTH_CLIENT_SECRET |
— | OIDC client credentials, provided by Enprivacy. Required for sign-in. |
EP_AUTH_URL / EP_AUTH_ISSUER |
https://auth.enprivacy.com |
OIDC provider base and expected issuer. Leave at default unless directed otherwise. |
EP_ALLOWED_ORGANIZATION_CODES |
empty (no gating) | Comma-separated allowlist of organisation codes permitted to sign in, provided by Enprivacy. |
EP_SUPER_ADMIN_EMAILS |
empty | Comma-separated emails bootstrapped as super administrators. |
System LLM
Section titled “System LLM”| Variable | Default | Purpose |
|---|---|---|
EP_LLM_URL |
— (required) | OpenAI-compatible endpoint for the platform’s system agents |
EP_LLM_PROVIDER |
enprivacy |
LLM client to use: enprivacy (self-hosted gateway), azure (Azure AI Foundry / Azure OpenAI), openai, anthropic, bedrock, … |
EP_LLM_MODEL |
openai/gpt-oss-20b |
Model (or Azure deployment name) the system agents request |
EP_LLM_API_KEY |
— | Credential for the system agents’ own LLM deployment — kept isolated from any provider-global keys |
For Azure AI Foundry, point EP_LLM_URL at the account’s OpenAI-compatible surface (https://<account>.openai.azure.com/openai/v1), set EP_LLM_PROVIDER=azure, and supply the account key as EP_LLM_API_KEY. The system agents use these settings in an isolated context, so any AZURE_OPENAI_* variables you configure for other Azure models are unaffected — and the Foundry account serving the system agents should be dedicated to this workload (nothing else pointed at it). The Azure Terraform deployment wires all of this automatically.
Object storage
Section titled “Object storage”Set ACTIVE_STORAGE_SERVICE to select the backend, then its credentials:
| Backend | Variables |
|---|---|
amazon (S3 / S3-compatible) |
AWS_REGION, S3_BUCKET_NAME, S3_ACCESS_KEY_ID, S3_SECRET_ACCESS_KEY |
azure (Azure Blob) |
AZURE_STORAGE_ACCOUNT_NAME, AZURE_STORAGE_CONTAINER; keyless by default — with AZURE_STORAGE_ACCESS_KEY unset the app authenticates with the host’s managed identity (grant it Storage Blob Data Contributor; optionally pick an identity with AZURE_STORAGE_PRINCIPAL_ID). Supplying the access key switches to shared-key auth. |
local (disk) |
none — mount durable storage at /rails/storage |
Email (optional)
Section titled “Email (optional)”Outbound mail is disabled by default (EP_MAIL_DELIVERY_METHOD=none). To
enable SMTP delivery, set EP_MAIL_DELIVERY_METHOD=smtp plus
EP_MAIL_SMTP_ADDRESS, EP_MAIL_SMTP_USER_NAME, EP_MAIL_SMTP_PASSWORD,
EP_MAIL_FROM_ADDRESS, and EP_MAIL_DEFAULT_URL_HOST. Additional knobs
(port, STARTTLS, authentication mode, reply-to) are available; ask Enprivacy
for the provider-specific examples (AWS SES, SendGrid, Postmark, Resend,
Office 365).
Tuning and proxy
Section titled “Tuning and proxy”| Variable | Default | Purpose |
|---|---|---|
WEB_CONCURRENCY |
1 | Web server worker processes — raise to match vCPUs on larger containers |
RAILS_MAX_THREADS |
3 | Threads per worker (and per-process DB pool size) |
JOB_CONCURRENCY |
1 | Background job worker processes (Job service) |
HTTP_WRITE_TIMEOUT / HTTP_IDLE_TIMEOUT |
30 / 60 | Set both to 90 — long synchronous LLM turns need it on every proxy hop, kept below Cloudflare’s ~100 s edge timeout |
RAILS_LOG_LEVEL |
info |
Log verbosity |
Network-restricted deployments
Section titled “Network-restricted deployments”Web, Job and the Inference Adapter download nothing at runtime: the adapter’s models and the address data ship inside the images. Only a self-hosted LLM needs its models staged in advance. See Air-Gapped Deployments.
Recommended Encryption Settings
Section titled “Recommended Encryption Settings”Database
Section titled “Database”Object Storage
Section titled “Object Storage”Filesystem
Section titled “Filesystem”Upgrading
Section titled “Upgrading”Some releases rename names the database stores, and deploy in a maintenance window: pause the queues, drain, stop Web and Job, migrate, start, resume. The release notes say when; the procedure is Maintenance-Window Upgrades.