EU-residency customer setup (DeepSeek V4 + self-hosted Ollama / vLLM)¶
This guide walks a sovereign-customer compliance team through deploying Bernstein under the EU-residency profile: DeepSeek V4 running on RFC-1918 / loopback infrastructure inside the customer's perimeter, with Bernstein's residency guard enforcing that no token leaves the boundary.
Audience¶
- Compliance officer signing off on Article-12 evidence.
- Platform engineer who'll deploy Ollama / vLLM and point Bernstein at it.
- Internal audit pulling proof that the inference call never crossed the EU boundary.
What the residency guard actually enforces¶
Bernstein's OllamaAdapter calls _is_self_hosted_endpoint(base_url) on every spawn(). The guard fires when either of the following is true:
- The requested model is in
_EU_RESIDENCY_MODELS(currentlydeepseek-v4-flash,deepseek-v4-pro). - The adapter was constructed with
eu_residency=True.
When the guard fires, Bernstein refuses to spawn against any URL whose host is not on the self-hosted allowlist:
| Allowed host shape | Example |
|---|---|
| Loopback IPs | 127.0.0.1, ::1, 0.0.0.0 |
| Loopback hostname | localhost |
| RFC-1918 private ranges | 10.x.x.x, 192.168.x.x, 172.16-31 |
*.internal / *.local | vllm.internal:8000 |
*.svc / *.cluster.local | ollama.svc.cluster.local:11434 |
Any other host (public IP, hosted-API hostname, unrecognised FQDN) is rejected with a structured RESIDENCY_VIOLATION error that names both the offending endpoint and the model that triggered the guard.
One flag: the sovereign profile¶
The residency posture composes four independent surfaces (air-gap network profile, residency policy, endpoint certification, and the storage / catalog / compliance defaults). Assembling them by hand is error-prone: one missed setting -- a cloud storage sink left configured, a non-certified remote endpoint in a role, a catalog left online -- violates the constraints silently, because nothing states the intended posture, so nothing can detect drift from it.
--profile sovereign composes those pieces with one flag and turns the active posture into a signed, verifiable claim:
$ bernstein run --profile sovereign --seed eu_residency.yaml
Sovereign posture attested: sha256:68d8804db375b53aa6550c81a5f2f22e...
What the flag does:
- Pins deny-all egress (the air-gap network posture and runtime socket guard), offline catalog mode, local storage backends, strict EU residency, and the compliance pack.
- Attests the posture: the effective-policy document is projected deterministically from your config, signed with the install's Ed25519 sovereign identity, and anchored in the HMAC audit chain. The document's canonical-JSON SHA-256 is the posture identity. An auditor recomputes that hash from your config snapshot alone and matches it against the chain-anchored attestation -- no need to trust the running install.
- Refuses drift: at every spawn the orchestrator recomputes the posture from the live config. If it diverges from the attested hash (for example a cloud storage sink was added after attestation), the spawn is blocked and a signed drift record naming the exact diverging keys is written to the chain. Drift is caught at spawn time, not at audit time.
Declare the residency regions in bernstein.yaml so they are part of the config snapshot the posture hash is computed from:
# bernstein.yaml
sovereign:
enabled: true
regions: [eu-central, eu-west]
enforce_strict: true
# Egress is deny-all by default. To reach a self-hosted EU model server,
# list it here (host / host:port / CIDR) -- NOT via --allow-network -- so the
# runtime network policy and the signed attestation come from the same config
# and cannot diverge. Every entry must be self-hosted or EU-region.
allowed_egress: ["10.0.0.5:11434"]
storage:
backend: memory # local sink only; a remote backend is drift
Under --profile sovereign, --allow-network is rejected: egress is declared in config so a deny-all attestation can never coexist with a runtime that quietly allows a destination.
Verify the posture at any time:
$ bernstein doctor sovereign # green when every axis is satisfied
$ bernstein audit verify # re-verifies the signed attestation offline
bernstein doctor sovereign renders the live posture hash against the chain-anchored attestation and fails on any drift; bernstein audit verify re-checks the attestation's Ed25519 signature and the recorded posture hash from the chain alone. The manual deployment recipe below remains valid for the underlying components, but the sovereign profile replaces the hand-assembly of the four surfaces with the single flag.
Deployment recipe¶
1. Stand up an Ollama / vLLM endpoint inside the perimeter¶
Two supported shapes, pick the one that matches your hardware budget:
a) Single-GPU Ollama for deepseek-v4-flash¶
deepseek-v4-flash is a 284B / 13B-active MoE model that fits on a single H100 / A100. Pull and serve via Ollama:
# On a host inside the EU perimeter (RFC-1918 IP):
ollama pull deepseek-v4-flash
ollama serve # listens on 0.0.0.0:11434 by default
Bind Ollama to a private interface only:
b) vLLM tensor-parallel for deepseek-v4-pro¶
deepseek-v4-pro is 1.6T / 49B-active and does NOT fit on a single GPU. Deploy via vLLM tensor-parallel:
# On a multi-GPU host inside the EU perimeter:
python -m vllm.entrypoints.openai.api_server \
--model deepseek-ai/deepseek-v4-pro \
--tensor-parallel-size 8 \
--host 10.0.0.5 \
--port 8000
Both Ollama and vLLM expose the OpenAI-compatible /v1/chat/completions endpoint that aider/litellm route through.
2. Generate the EU-residency profile¶
Drop a eu_residency_profile.yaml next to bernstein.yaml:
# eu_residency_profile.yaml
profile: eu-residency
adapter: ollama
ollama:
base_url: http://10.0.0.5:11434 # or http://vllm.internal:8000/v1
eu_residency: true # turns the guard on for ALL models
data_residency:
allowed_regions: [eu-west, eu-central]
enforce_strict: true # any region drift = abort
lineage:
customer_signing_enabled: true
kms_adapter: env # or 'file' / 'hsm' (see note below)
kms_adapter_env_var: LINEAGE_SIGNING_KEY
regulatory_class_default: production_detection_rule
network_policy:
profile: airgap # implicit deny-all egress
The combination matters:
ollama.eu_residency: true-- adapter-level guard on every model, not justdeepseek-v4-*.data_residency.enforce_strict: true-- region drift halts the run rather than warning.network_policy.profile: airgap-- the runtime socket guard blocks any unintended outbound dial.kms_adapter: hsmis only viable when a customer-providedHSMKMSAdaptersubclass (PKCS#11 / Cloud-KMS) is on the classpath. Without one, the orchestrator aborts at config-load time. For non-production smoke tests, setBERNSTEIN_ALLOW_HSM_STUB=1to opt in to the documentation stub explicitly. See regulatory-lineage.md for the integration shape.
3. Verify the deployment with bernstein doctor airgap¶
Before processing any production traffic, run the air-gap doctor:
Expected output (every row PASS):
Air-gap doctor: PASSED
Status Check Detail
PASS airgap profile active BERNSTEIN_PROFILE_MODE=airgap
PASS network policy deny-all explicit deny-all (none)
PASS runtime socket guard active socket.socket.connect is patched
PASS policy blocks declared endpoints N declared endpoint(s) all blocked
PASS MCP catalog all-off no user MCP config
PASS memo store on local disk no shared cache; airgap pins memo to <path>
PASS audit chain HMAC valid N entries verified, chain intact
PASS no external hostnames in runtime no public endpoint references found
If any row reads FAIL, the suggested fix in the report is the canonical remediation. The doctor exits non-zero on any FAIL row, so wiring it into a deploy-time CI gate is one line.
4. Spot-check the residency guard with a deliberate failure¶
Compliance teams should occasionally probe the guard. Set the base URL to a public hosted API (https://api.deepseek.com) and confirm the spawn refuses:
$ OLLAMA_API_BASE=https://api.deepseek.com bernstein run \
--adapter ollama --model deepseek-v4-flash --task probe-residency
RuntimeError: RESIDENCY_VIOLATION: model 'deepseek-v4-flash' requires a
self-hosted endpoint under the eu-residency profile, got
'https://api.deepseek.com'. Set OLLAMA_API_BASE / OLLAMA_HOST to a
self-hosted (e.g. vLLM, Ollama on a private/EU node) endpoint and retry.
The exit code is non-zero. Wire this scenario into your weekly compliance smoke pipeline -- a passing guard is observable evidence for the auditor.
Expected log lines¶
A well-configured EU-residency run emits (one per line) the following in .sdd/audit/<YYYY-MM-DD>.jsonl (the log is daily-rotated):
event=adapter.spawn adapter=ollama model=deepseek-v4-flash base_url=http://10.0.0.5:11434
event=residency.gate.passed model=deepseek-v4-flash host=10.0.0.5 verdict=self_hosted
event=lineage.signed regulatory_class=production_detection_rule kms=env
event=network.policy.allow host=10.0.0.5 port=11434 source=adapter:ollama
A misconfigured run (or an attacker-flipped base URL) emits:
event=residency.gate.violation model=deepseek-v4-flash base_url=https://api.deepseek.com
event=adapter.spawn.aborted reason=RESIDENCY_VIOLATION
The audit chain HMAC covers every line so a tampered log is detectable via bernstein audit verify and bernstein doctor airgap.
What to hand the auditor¶
Three artefacts settle the Article-12 evidence story:
.sdd/audit/<YYYY-MM-DD>.jsonl-- the residency-gate decisions for the audit window (daily-rotated), with the HMAC chain intact.MANIFEST.customer.jsonin the deployed wheelhouse -- the customer countersignature plus the org cosign signature, proving the running code was both vendor-signed AND customer-signed.bernstein doctor airgap --jsonsnapshot taken at audit start AND audit end -- proves the network policy and runtime socket guard stayed deny-all throughout.
Common operator mistakes¶
| Symptom | Likely cause |
|---|---|
RESIDENCY_VIOLATION on a private host | Host on a public IP without *.internal / *.local suffix |
RESIDENCY_VIOLATION on 172.32.5.5 | Outside the RFC-1918 172.16.0.0/12 band |
signature unverified on customer sig | .bernstein/trust/customer-keys/ empty or missing the right key |
Doctor reports MCP catalog all-off=FAIL | Residual config from a previous non-airgap session; remove it |
See also¶
docs/compliance/regulatory-lineage.md-- regulatory_class taxonomy and lineage v2 schema.scripts/build_airgap_wheelhouse.py-- offline wheel bundle builder.bernstein wheelhouse countersign --help-- customer-side countersignature CLI.