dexgate Multi-Host Customer Setup Tutorial
Full multi-host customer lab flow (runtime install, adapters, environment profiles, validation, troubleshooting). Do not start here for first paid success—use Minimum Production setup first, then expand.
# dexgate Multi-Host Customer Setup Tutorial
**Not the first paid success path.** Start with [Minimum Production setup](https://dexgate.ai/docs/dexgate/minimum-setup/) (one Linux Docker host + one agent host), then expand here.
This guide walks through a complete multi-host customer-style dexgate lab with:
- one central dexgate runtime host
- three environment hosts: `dev`, `qa`, and `prod`
- both OpenClaw and Codex installed in each environment
- customer-defined environment profiles and rules
Use this tutorial if you want to repeat the same multi-host setup we validated in the Azure lab, but in your own infrastructure.
## What You Will Build
You will end up with this topology:
- `dexgate-runtime-host`: central dexgate runtime
- `dev-host`: OpenClaw and Codex host applications plus both dexgate adapters, pointing to `environment=dev`
- `qa-host`: OpenClaw and Codex host applications plus both dexgate adapters, pointing to `environment=qa`
- `prod-host`: OpenClaw and Codex host applications plus both dexgate adapters, pointing to `environment=prod`
All three environment hosts point to the same central dexgate runtime.
## Recommended Model
Use:
- one tenant per customer workspace
- one gateway ID per environment
- both adapters in an environment share that environment gateway ID
Example:
- `dev -> gw-dev`
- `qa -> gw-qa`
- `prod -> gw-prod`
This gives you a clean model where dexgate licensing and policy behavior are enforced centrally, while each environment can behave differently.
## Prerequisites
Before you begin, make sure you have:
- a paid dexgate customer workspace
- access to the customer portal
- one Linux host for the central dexgate runtime
- one Linux host for each environment you want to test
- Docker and Docker Compose available on the central dexgate host
- `curl`, `sha256sum`, `unzip`, and `openssl` available on the central dexgate host
- outbound internet access on the hosts for package installation
Recommended OS:
- Ubuntu 24.04 LTS or similar Linux distribution
## Step 1: Purchase And Provision The Workspace
1. Go to the customer portal purchase flow.
2. Complete email verification.
3. Complete checkout.
4. Use the purchase email to set your console password.
5. Sign in to the customer portal.
At this point you should be able to open:
- `Dashboard`
- `Licenses`
- `Deployments`
- `Downloads`
## Step 2: Download The Runtime Bundle
In the portal:
1. Open `Downloads`.
2. Open the current `SDE Runtime` release.
3. Download these files:
- the three runtime tar files
- `sha256sums.txt`
- the customer config package
- the runtime secrets package
- the Linux bootstrap script
- the generated compose file
Put them together in one directory on your central dexgate host.
## Step 3: Install dexgate On The Central Host
Copy the downloaded files onto the central host, prepare `runtime.local.env`, then run the Linux bootstrap from the directory containing those files.
Example:
```bash
cd ~/dexgate
cp ./<your-org>-sde-runtime-<version>.env ./runtime.local.env
sed -i "s|^ASSESS_GATEWAY_ID=.*|ASSESS_GATEWAY_ID=<your-gateway-id>|" ./runtime.local.env
sed -i "s|^ASSESS_ENVIRONMENT=.*|ASSESS_ENVIRONMENT=<your-environment>|" ./runtime.local.env
bash ./<your-org>-sde-runtime-<version>-bootstrap.sh .
```
The bootstrap will:
- verify checksums
- unpack the runtime secrets package
- prepare `runtime.local.env`
- install entitlement and profile files
- load the Docker images
- start the runtime
- register the machine in dexgate Deployments
- start the background heartbeat loop
If you set `ASSESS_GATEWAY_ID` and `ASSESS_ENVIRONMENT` before you run the bootstrap, the generated smoke outputs should also include `authorize-smoke-response.json`.
## Step 4: Verify The Central Runtime
On the central host, run:
```bash
cd ~/dexgate
curl -s http://localhost:8001/healthz
```
Expected:
```json
{"status":"ok"}
```
Also check the running containers:
```bash
docker compose -f docker-compose.bundle.hardened.yml --env-file runtime.local.env ps
```
And check the deployment heartbeat files:
```bash
cat keygen-machine-activate-response.json
cat keygen-machine-heartbeat-response.json
tail -n 5 keygen-machine-heartbeat.log
```
Then open `Deployments` in the portal and confirm your runtime host appears there.
## Step 5: Install OpenClaw, Codex, And The Public Adapters On Each Environment Host
Run the following on each environment host.
### Install Node And npm
```bash
sudo apt-get update
sudo apt-get install -y nodejs npm curl ca-certificates
```
### Install Codex
```bash
sudo npm install -g @openai/codex
codex --version
```
### Authenticate Codex On This Host
A clean host must authenticate Codex before the hosted-runner validation path will reach dexgate.
If you already use interactive Codex login on this host, confirm that first:
```bash
codex login status
```
If the host is not already logged in, set `OPENAI_API_KEY` in the current shell and run:
```bash
printenv OPENAI_API_KEY | codex login --with-api-key
codex login status
```
### Install OpenClaw
```bash
curl -fsSL https://openclaw.ai/install.sh | bash
export PATH="$HOME/.local/bin:$HOME/.npm-global/bin:$PATH"
```
### Create A Shared Test Directory
```bash
mkdir -p ~/env-tests/shared
cd ~/env-tests/shared
npm init -y
npm install @dexgate/openclaw-trusted-mode@1.0.7 @dexgate/codex-trusted-mode@beta
```
### Verify The Tools
```bash
node --version
npm --version
codex --version
openclaw --version
npm list --depth=0 | grep '@dexgate/'
```
## Step 6: Configure Environment-Specific Policy Profiles
The customer can control environment behavior with two files on the central dexgate host:
- `ops/environment_profiles.json`
- `ops/environment_profile_rules.json`
### What These Files Do
- `environment_profiles.json` maps an environment label to a profile name
- `environment_profile_rules.json` defines what each profile allows or denies
This means customers can:
- use any environment labels they want
- add new profile names
- rename profiles
- change what a profile does
### Example Customer Configuration
Use this example if you want:
- `dev`: broad automation
- `qa`: conditional automation
- `prod`: narrow automation
`ops/environment_profiles.json`
```json
{
"your-tenant-id": {
"default_profile": "approval_lane",
"environments": {
"dev": "sandbox_plus",
"qa": "approval_lane",
"prod": "locked_core"
}
}
}
```
`ops/environment_profile_rules.json`
```json
{
"profiles": {
"sandbox_plus": {
"codex": {
"apply_patch": "allow",
"shell_command": "readonly"
},
"openclaw": {
"allowed_tools": ["*"],
"deny_code": "SANDBOX_PLUS_DENY",
"deny_reason": "Tool {tool_name} is blocked by the sandbox_plus profile."
}
},
"approval_lane": {
"codex": {
"apply_patch": "deny",
"shell_command": "readonly"
},
"openclaw": {
"allowed_tools": ["read_file", "list_dir", "search_files"],
"deny_code": "APPROVAL_LANE_DENY",
"deny_reason": "Tool {tool_name} requires broader automation than approval_lane allows."
}
},
"locked_core": {
"codex": {
"apply_patch": "deny",
"shell_command": "deny"
},
"openclaw": {
"allowed_tools": ["read_file"],
"deny_code": "LOCKED_CORE_DENY",
"deny_reason": "Tool {tool_name} is blocked by the locked_core profile."
}
}
}
}
```
Replace `your-tenant-id` with your actual tenant.
## Step 7: Restart The PDP After Changing Profiles
After editing the profile files, restart the central PDP service:
```bash
cd ~/dexgate
docker compose -f docker-compose.bundle.hardened.yml --env-file runtime.local.env restart sde-pdp
sleep 8
curl -s http://localhost:8001/healthz
```
## Step 8: Assign Gateway IDs And Environments Per Host
Use one gateway ID per environment host.
Recommended:
- dev host: `gateway_id=gw-dev`, `environment=dev`
- qa host: `gateway_id=gw-qa`, `environment=qa`
- prod host: `gateway_id=gw-prod`, `environment=prod`
All requests from the environment host should use:
- the same tenant ID
- the same PDP URL
- the host's own gateway ID
- the host's own environment label
## Step 9: Configure OpenClaw On Each Environment Host
Use the shipped configure command instead of editing `~/.openclaw/openclaw.json` manually.
Dev host example:
```bash
cd ~/env-tests/shared
npx --no-install openclaw-trusted-mode-configure \
--tenantId your-tenant-id \
--gatewayId gw-dev \
--environment dev \
--pdpUrl http://10.70.0.4:8001/v1/authorize \
--pdpAuthToken <PDP_AUTH_TOKEN from runtime-secrets.env> \
--certificationStatus LOCKDOWN_ONLY
```
Repeat on `qa` and `prod` with the matching `gatewayId` and `environment`.
The configure command writes:
- `plugins.allow`
- `plugins.entries.openclaw-trusted-mode`
- the governed `pdpUrl`, `tenantId`, `gatewayId`, `environment`, `toolPolicyMode`, and fail-closed settings
You can inspect the written file afterward:
```bash
cat ~/.openclaw/openclaw.json
```
## Step 10: Validate OpenClaw And Codex In Each Environment
In the examples below, replace `10.70.0.4` with your central dexgate host.
Load `PDP_AUTH_TOKEN` from the portal runtime secrets package before running direct PDP checks. Do not commit or paste the token into shared docs or tickets.
For the shipped OpenClaw validation command, set the governed values explicitly in the shell first. Today `openclaw-trusted-mode-check` is env-driven and does not inherit `tenantId`, `gatewayId`, `environment`, or `pdpUrl` from `~/.openclaw/openclaw.json`.
OpenClaw check example:
```bash
TENANT_ID=your-tenant-id GATEWAY_ID=gw-dev ENVIRONMENT=dev PDP_URL=http://10.70.0.4:8001/v1/authorize PDP_AUTH_TOKEN="<PDP_AUTH_TOKEN from runtime-secrets.env>" PDP_HEALTH_URL=http://10.70.0.4:8001/healthz npx openclaw-trusted-mode-check --json
```
### Dev Host
Expected behavior:
- OpenClaw `write_file`: allow
- OpenClaw `read_file`: allow
- Codex `functions.apply_patch`: allow
- Codex readonly shell: allow
OpenClaw `write_file`:
```bash
curl -s -X POST http://10.70.0.4:8001/v1/authorize \
-H 'Content-Type: application/json' \
-H "Authorization: Bearer $PDP_AUTH_TOKEN" \
-d '{"decision_sku":"openclaw.trusted_mode.authorize.v1","policy_variant":"guard-pro.v2026.02","tenant_id":"your-tenant-id","gateway_id":"gw-dev","environment":"dev","inputs":{"action_request":{"tool_name":"write_file","params":{"path":"/tmp/dev.txt"}}}}' | jq '{decision, deny_code, deny_reason, profile: .trace.environment_profile, environment: .trace.environment}'
```
Codex `functions.apply_patch`:
```bash
curl -s -X POST http://10.70.0.4:8001/v1/authorize \
-H 'Content-Type: application/json' \
-H "Authorization: Bearer $PDP_AUTH_TOKEN" \
-d '{"decision_sku":"codex.trusted_mode.authorize.v1","policy_variant":"codex-guard.v0.1.0","tenant_id":"your-tenant-id","gateway_id":"gw-dev","environment":"dev","inputs":{"request":{"toolName":"functions.apply_patch","command":""}}}' | jq '{decision, deny_code, deny_reason, profile: .trace.environment_profile, environment: .trace.environment}'
```
### QA Host
Expected behavior:
- OpenClaw `write_file`: deny
- OpenClaw `read_file`: allow
- Codex `functions.apply_patch`: deny
- Codex readonly shell: allow
OpenClaw `write_file`:
```bash
curl -s -X POST http://10.70.0.4:8001/v1/authorize \
-H 'Content-Type: application/json' \
-H "Authorization: Bearer $PDP_AUTH_TOKEN" \
-d '{"decision_sku":"openclaw.trusted_mode.authorize.v1","policy_variant":"guard-pro.v2026.02","tenant_id":"your-tenant-id","gateway_id":"gw-qa","environment":"qa","inputs":{"action_request":{"tool_name":"write_file","params":{"path":"/tmp/qa.txt"}}}}' | jq '{decision, deny_code, deny_reason, profile: .trace.environment_profile, environment: .trace.environment}'
```
Codex `functions.apply_patch`:
```bash
curl -s -X POST http://10.70.0.4:8001/v1/authorize \
-H 'Content-Type: application/json' \
-H "Authorization: Bearer $PDP_AUTH_TOKEN" \
-d '{"decision_sku":"codex.trusted_mode.authorize.v1","policy_variant":"codex-guard.v0.1.0","tenant_id":"your-tenant-id","gateway_id":"gw-qa","environment":"qa","inputs":{"request":{"toolName":"functions.apply_patch","command":""}}}' | jq '{decision, deny_code, deny_reason, profile: .trace.environment_profile, environment: .trace.environment}'
```
Readonly shell example:
```bash
curl -s -X POST http://10.70.0.4:8001/v1/authorize \
-H 'Content-Type: application/json' \
-H "Authorization: Bearer $PDP_AUTH_TOKEN" \
-d '{"decision_sku":"codex.trusted_mode.authorize.v1","policy_variant":"codex-guard.v0.1.0","tenant_id":"your-tenant-id","gateway_id":"gw-qa","environment":"qa","inputs":{"request":{"toolName":"functions.shell_command","command":"rg dexgate README.md"}}}' | jq '{decision, deny_code, deny_reason, profile: .trace.environment_profile, environment: .trace.environment}'
```
### Prod Host
Expected behavior:
- OpenClaw `write_file`: deny
- OpenClaw `read_file`: allow
- Codex `functions.apply_patch`: deny
- Codex shell: deny
OpenClaw `write_file`:
```bash
curl -s -X POST http://10.70.0.4:8001/v1/authorize \
-H 'Content-Type: application/json' \
-H "Authorization: Bearer $PDP_AUTH_TOKEN" \
-d '{"decision_sku":"openclaw.trusted_mode.authorize.v1","policy_variant":"guard-pro.v2026.02","tenant_id":"your-tenant-id","gateway_id":"gw-prod","environment":"prod","inputs":{"action_request":{"tool_name":"write_file","params":{"path":"/tmp/prod.txt"}}}}' | jq '{decision, deny_code, deny_reason, profile: .trace.environment_profile, environment: .trace.environment}'
```
Codex shell:
```bash
curl -s -X POST http://10.70.0.4:8001/v1/authorize \
-H 'Content-Type: application/json' \
-H "Authorization: Bearer $PDP_AUTH_TOKEN" \
-d '{"decision_sku":"codex.trusted_mode.authorize.v1","policy_variant":"codex-guard.v0.1.0","tenant_id":"your-tenant-id","gateway_id":"gw-prod","environment":"prod","inputs":{"request":{"toolName":"functions.shell_command","command":"rg dexgate README.md"}}}' | jq '{decision, deny_code, deny_reason, profile: .trace.environment_profile, environment: .trace.environment}'
```
## Step 11: Confirm The Deployment In The Portal
Open `Deployments` in the portal and confirm:
- the runtime machine appears
- the status is `Healthy`
- heartbeat timestamps are current
This confirms:
- the machine registered successfully
- the heartbeat loop is running
- the portal is tracking the runtime correctly
## Step 12: What This Setup Proves
If all steps pass, you have proven that dexgate supports:
- one central runtime per customer workspace
- multiple environments under one customer tenant
- both OpenClaw and Codex in each environment
- customer-defined environment names
- customer-defined profile names
- customer-defined profile behavior
- different dexgate behavior by environment without splitting into separate tenants
## Troubleshooting
### Bootstrap fails because a port is already in use
Stop the existing service using ports `8001-8003`, then rerun the bootstrap.
### Machine activation limit is reached
Deactivate an existing machine from `Deployments`, then rerun the bootstrap.
### OpenClaw or Codex commands are not found
Make sure your shell path includes:
```bash
export PATH="$HOME/.local/bin:$HOME/.npm-global/bin:$PATH"
```
### The portal shows `Awaiting first heartbeat`
Wait for the heartbeat loop to run, then refresh `Deployments`. If the runtime is healthy and the loop is running, the page should update to `Healthy`.
## Recommended Next Steps
After this tutorial, you can extend the same model to:
- more environments like `uat`, `staging`, `dr`, or `perf`
- additional customer-defined profiles
- gateway and environment limit testing under your license tier
- recorded internal demos or customer onboarding sessions