Here’s something that keeps security teams up at night: you’ve got five departments, one OpenClaw instance, and suddenly everyone’s got everyone else’s permissions. Oops.
If you’re deploying OpenClaw across a team or organization, this is the moment where the architecture matters. Not the flashy kind of architecture—the kind that keeps your compliance officer from scheduling an emergency meeting. We’re talking trust boundaries, resource isolation, and the unglamorous but essential work of keeping departments from stepping on each other’s toes (intentionally or otherwise).
Let’s walk through why a shared agent becomes a shared liability, then show you how to fix it with per-department gateway instances and Docker Compose orchestration.
The Shared Agent Problem: Permission Creep
Imagine this scenario: you deploy a single OpenClaw instance as your company’s central AI agent. Marketing connects to it. Finance connects to it. Engineering connects to it. On the surface? Efficient. One gateway, one agent, one dashboard.
But here’s what you’ve actually built: a permission wildcard.
The OpenClaw agent, by default, inherits the credentials and API access of its host environment. If the gateway’s running under a service account that has access to your finance database, your marketing team’s agents can touch that database too. Not because anyone wants them to—just because they share the same underlying execution context.
Now layer in the fact that prompt injection attacks are real. A customer support agent (Marketing department) gets fed malicious input that somehow escalates to database queries (Finance department’s domain). Or an intern accidentally misconfigures an agent’s system prompt, and suddenly it’s making API calls it shouldn’t.
This isn’t hypothetical. This is why security-conscious organizations moved away from shared services decades ago.
The principle is old and well-tested: least privilege isolation. Each department gets exactly what it needs, nothing more. No cross-department credential sharing. No shared execution context. No “well, we’ll hope nobody exploits the permission overlap.”
Here’s what makes it worse: the damage happens silently. Your shared gateway serves requests from all departments through the same credentials. If a Marketing agent accidentally queries Finance data (due to a misconfigured prompt or a successful injection attack), there’s no audit trail separating which department did what. You’ve got one set of logs from one gateway showing mixed activity from multiple departments. When the inevitable security audit happens and someone asks “can you prove Marketing didn’t access Finance data?”, your answer is “we have logs from a shared gateway, but they’re all mixed together.” That’s not evidence. That’s plausible deniability, and it’s not defensible.
The economics get worse too. One department’s resource-hungry operation (a large batch job, a complex query loop) consumes all available bandwidth or memory. Now all departments suffer. Engineering’s time-sensitive debugging waits while Marketing’s reports run. Finance’s rate limits get exhausted before anyone else gets a turn. It’s like having one water main serving the entire building—when one tenant opens all their faucets, everyone else gets low pressure.
Multi-department deployments aren’t inherently complex. The complexity comes from sharing. Isolation makes it simple.
The Architecture Solution: Per-Department Gateway Instances
Here’s the fix: instead of one gateway serving everyone, run separate gateway instances—one per department. Think of each as its own sandboxed environment with its own credential set, resource limits, and monitoring.
Each gateway instance:
- Runs in its own container with isolated network namespace
- Has dedicated service account credentials (Finance agents use a finance-scoped service account, etc.)
- Listens on its own port (gateway-1 on 8001, gateway-2 on 8002, etc.)
- Gets its own monitoring and audit logs
- Can be updated independently without affecting other departments
From a trust boundary perspective, you’ve just drawn a line. Marketing’s agents live inside gateway-1. Finance’s agents live inside gateway-2. They don’t share runtime context. They don’t share credentials. If one gets compromised, the blast radius is department-scoped, not company-scoped.
Let’s see how this looks in practice.
Understanding Shared Gateways and Shared Permissions: The Hidden Risk
Before we jump into solutions, let’s be real about what happens when you run one gateway for everyone. It’s not just about access control—it’s about the entire execution model.
When your OpenClaw gateway starts, it reads environment variables, loads secrets, and establishes connections to databases, APIs, and external services. All of that context becomes available to every agent running through that gateway. It’s like giving everyone in the company the same master keycard.
Here’s the technical reality: OpenClaw agents inherit the networking and identity context of their host process. If the gateway process is running as a user with database access, any prompt injection or misconfiguration could escalate to database-level operations. If the gateway’s container has mounted volumes with sensitive data, any agent can theoretically access those volumes. If the gateway has API credentials hardcoded in environment variables, those credentials are available to every request that flows through it.
And here’s where it gets worse: shared gateways create shared audit trails. When Finance queries the revenue database through the same gateway as Marketing, how do you audit which department accessed what? You’d need to inject department identifiers into every request, and then you’re doing identity management outside the gateway. That’s fragile. That’s where mistakes happen.
The principle is simple but powerful: isolation is security. Not in the “security through obscurity” sense, but in the “architectural boundary” sense. If Finance’s gateway can only run Finance agents, and those agents can only use Finance credentials, then a compromise in Finance is scoped to Finance. The blast radius is containable.
Think about incident response: it’s 3 AM, someone discovers suspicious activity in a Finance database. In a shared gateway scenario, your first question is “which department did this?” You don’t know. It could be any of them. So you shut everything down—all departments lose access while you investigate. In an isolated gateway scenario, you immediately know it was the Finance gateway. You isolate that one. Marketing, Engineering, HR keep working. Your incident response time drops from hours to minutes.
There’s also the trust factor between departments. When Marketing’s engineers can theoretically access Finance data (because they share a gateway), Finance teams lose confidence in the architecture. They’ll start requesting their own infrastructure, their own tools, their own isolated setups. Before you know it, you’ve got more infrastructure to maintain, not less. Isolation from the start prevents this political complexity. Each department owns its gateway. Each department trusts its boundary. No cross-department politics about who gets to touch what.
Docker Compose: Multiple Gateways, Single Configuration
Here’s a Docker Compose file that orchestrates three department gateways. This is real-world deployable:
version: "3.9"
services:
# Marketing Gateway
openclaw-gateway-marketing:
image: openclaw/gateway:latest
container_name: openclaw-gateway-marketing
environment:
- GATEWAY_NAME=marketing-gateway
- GATEWAY_PORT=8001
- LOG_LEVEL=info
- SERVICE_ACCOUNT_KEY=/run/secrets/marketing_sa_key
- ALLOWED_MODELS=claude-opus,claude-sonnet
- MAX_TOKENS_PER_REQUEST=100000
- RATE_LIMIT_REQUESTS_PER_MIN=30
ports:
- "8001:8001"
volumes:
- ./config/marketing-gateway.json:/app/config/gateway.json:ro
- ./logs/marketing:/var/log/openclaw
secrets:
- marketing_sa_key
networks:
- openclaw-net
restart: unless-stopped
healthcheck:
test: ["CMD", "curl", "-f", "https://automateanddeploy.com:8001/health"]
interval: 30s
timeout: 10s
retries: 3
# Finance Gateway
openclaw-gateway-finance:
image: openclaw/gateway:latest
container_name: openclaw-gateway-finance
environment:
- GATEWAY_NAME=finance-gateway
- GATEWAY_PORT=8002
- LOG_LEVEL=info
- SERVICE_ACCOUNT_KEY=/run/secrets/finance_sa_key
- ALLOWED_MODELS=claude-opus
- MAX_TOKENS_PER_REQUEST=50000
- RATE_LIMIT_REQUESTS_PER_MIN=15
ports:
- "8002:8002"
volumes:
- ./config/finance-gateway.json:/app/config/gateway.json:ro
- ./logs/finance:/var/log/openclaw
secrets:
- finance_sa_key
networks:
- openclaw-net
restart: unless-stopped
healthcheck:
test: ["CMD", "curl", "-f", "https://automateanddeploy.com:8002/health"]
interval: 30s
timeout: 10s
retries: 3
# Engineering Gateway
openclaw-gateway-engineering:
image: openclaw/gateway:latest
container_name: openclaw-gateway-engineering
environment:
- GATEWAY_NAME=engineering-gateway
- GATEWAY_PORT=8003
- LOG_LEVEL=debug
- SERVICE_ACCOUNT_KEY=/run/secrets/engineering_sa_key
- ALLOWED_MODELS=claude-opus,claude-sonnet,claude-haiku
- MAX_TOKENS_PER_REQUEST=200000
- RATE_LIMIT_REQUESTS_PER_MIN=60
ports:
- "8003:8003"
volumes:
- ./config/engineering-gateway.json:/app/config/gateway.json:ro
- ./logs/engineering:/var/log/openclaw
secrets:
- engineering_sa_key
networks:
- openclaw-net
restart: unless-stopped
healthcheck:
test: ["CMD", "curl", "-f", "https://automateanddeploy.com:8003/health"]
interval: 30s
timeout: 10s
retries: 3
# Central monitoring (optional but recommended)
prometheus:
image: prom/prometheus:latest
container_name: openclaw-prometheus
ports:
- "9090:9090"
volumes:
- ./config/prometheus.yml:/etc/prometheus/prometheus.yml:ro
- prometheus_data:/prometheus
networks:
- openclaw-net
restart: unless-stopped
networks:
openclaw-net:
driver: bridge
secrets:
marketing_sa_key:
file: ./secrets/marketing-sa-key.json
finance_sa_key:
file: ./secrets/finance-sa-key.json
engineering_sa_key:
file: ./secrets/engineering-sa-key.json
volumes:
prometheus_data:
Notice what’s happening here:
Isolation by environment variables: Each gateway gets its own SERVICE_ACCOUNT_KEY, which points to a department-specific service account. The Marketing gateway can’t use the Finance service account key because it’s mounted as a secret that only Marketing’s container can access. This is enforced at the Docker level—the container literally doesn’t have access to secrets it wasn’t granted. If an attacker compromised the Marketing gateway and tried to read finance_sa_key, the file system would return “permission denied.” That’s real isolation.
Resource constraints per department: Finance has lower token limits than Engineering because financial operations are typically more conservative. Marketing gets a higher rate limit because customer-facing queries might be bursty. Without these constraints, you’re back to the shared resource problem: one department’s spike tanks everyone else’s performance.
Independent logging: Each department logs to its own directory. If Finance’s agents are doing something weird, you can audit that department’s logs independently without wading through Marketing’s noise. More importantly, logs are separate artifacts with separate audit trails. When compliance asks “show me all Finance access,” you hand over /logs/finance/ and you’re done. Clean, provable, auditable.
Health checks: Each gateway has its own health endpoint. Your orchestration layer (Kubernetes, Nomad, whatever) can monitor each independently. If the Finance gateway is unhealthy, the system alerts on Finance only. Marketing and Engineering stay green. No cascading failures. No “why is Marketing slow?” “Because Finance’s gateway crashed.” That scenario is eliminated.
Port isolation: You’re not running all gateways on the same port. This is crucial for network-level isolation too. Each gateway listens on a unique port, and your firewall rules can reflect that. “Only Finance clients can connect to port 8002” is a one-line rule. With a shared gateway on port 8001, you’d need application-level authentication to enforce that boundary, which is significantly more fragile.
Docker Networking: The Hidden Layer of Isolation
Docker networking is where the real magic happens for multi-gateway setups. When you define a custom bridge network like openclaw-net, you’re not just creating a network—you’re creating an isolation boundary that the kernel enforces.
Here’s what’s actually happening under the hood: Docker’s bridge driver sets up network namespaces. Each container gets its own namespace, complete with its own network stack, routing tables, and iptables rules. When you connect multiple containers to the same custom bridge, they can communicate with each other through the bridge interface, but they’re isolated from other networks unless explicitly configured otherwise.
But here’s the key insight: you want your department gateways on the same bridge network so they can talk to each other (for cross-department queries or aggregation), but you want to prevent any of them from talking directly to the host network or to containers on other networks.
Let’s extend the Docker Compose setup to show this explicitly:
networks:
openclaw-net:
driver: bridge
driver_opts:
com.docker.network.bridge.name: br-openclaw
com.docker.network.bridge.enable_icc: "true" # Inter-container communication ON
com.docker.network.bridge.enable_ip_masquerade: "true"
ipam:
config:
- subnet: 172.25.0.0/16
gateway: 172.25.0.1
That enable_icc: "true" setting is important. It means containers on this bridge can talk to each other, which is what you want for inter-gateway communication (like Finance querying a shared data warehouse through a common API). But they can’t talk to containers on other bridges or to the host network unless you explicitly expose ports.
And the port exposure is controlled: "8001:8001" means only on localhost by default. If you wanted Marketing’s gateway to be accessible from a specific client IP, you’d do "192.168.1.100:8001:8001". The specificity matters.
The subnet (172.25.0.0/16) is important too. It keeps your OpenClaw network traffic isolated to a specific IP range, which makes firewall rules cleaner and audit logs more readable. You know that anything with a source IP of 172.25.x.x came from an OpenClaw gateway.
Resource Management and Port Allocation
When you’re running multiple gateway instances, resource management becomes practical, not theoretical.
Each container gets CPU and memory limits. Here’s a realistic setup:
services:
openclaw-gateway-marketing:
# ... existing config ...
deploy:
resources:
limits:
cpus: "2"
memory: 2G
reservations:
cpus: "1"
memory: 1G
openclaw-gateway-finance:
# ... existing config ...
deploy:
resources:
limits:
cpus: "1.5"
memory: 1.5G
reservations:
cpus: "0.75"
memory: 750M
openclaw-gateway-engineering:
# ... existing config ...
deploy:
resources:
limits:
cpus: "4"
memory: 4G
reservations:
cpus: "2"
memory: 2G
This means if Engineering’s agents go haywire and consume all available CPU, Finance’s gateway still has its reserved 0.75 CPU cores. The isolation is real.
Let’s talk about what happens when you DON’T have these limits. Without resource reservations, one department’s runaway query can starve the others. If Marketing launches a huge token-heavy batch operation, it could consume all the server’s RAM, and Finance’s health checks start failing because there’s no memory left for the Finance gateway to serve requests. Now you’ve got a cascading failure: Finance’s monitoring alerts fire, Engineering teams get paged, and the root cause was Marketing’s experiment.
With reservations, Finance gets its guaranteed 750MB of RAM no matter what Marketing does. Engineering gets its guaranteed 2 CPU cores. The system becomes predictable. You can size the host machine knowing that if you have three departments with those requirements, you need at least 3.75 CPU cores and 4.75GB RAM available.
For memory, the strategy matters too. The limits are hard caps—if a container tries to exceed them, the kernel OOM-kills it. The reservations are soft—the scheduler tries to keep that much memory free, but if the machine is desperate, it’ll use reserved memory too. For a production setup, your actual host should be sized so that you never hit the limits section. The limits are your emergency brake, not your normal operating mode.
Port allocation matters too. You need a strategy that’s:
- Deterministic: port 8000 + department_id
- Documented: spreadsheet or config file that maps departments to ports
- Firewall-aware: only expose the ports your internal network actually needs
- DNS-registered: department gateways get DNS entries so services don’t hardcode IPs
Here’s a simple mapping with DNS:
- Marketing: gateway.marketing.internal:8001 → 8001
- Finance: gateway.finance.internal:8002 → 8002
- Engineering: gateway.engineering.internal:8003 → 8003
- Operations: gateway.ops.internal:8004 → 8004
- HR: gateway.hr.internal:8005 → 8005
Your internal services connect to the appropriate gateway. Marketing’s CRM integration hits http://gateway.marketing.internal:8001. Finance’s query system hits http://gateway.finance.internal:8002. No cross-department accidental access because the network topology itself enforces the boundary. And if you need to migrate Finance’s gateway to a different server, you just update the DNS entry. No code changes.
The DNS approach also helps with horizontal scaling. If Finance’s load grows and you need two Finance gateways, you can have gateway.finance.internal resolve to both 100.64.1.2:8002 and 100.64.1.3:8002, and a load balancer distributes the traffic. Your Finance clients don’t care—they still hit the same DNS name.
Service Account Credentials: The Trust Boundary in Practice
This is where theory meets reality. Each gateway needs credentials that let it do its job, and nothing more.
For a Finance gateway, you’d create a service account that has:
- Read access to the finance database
- Read-only access to GL tables
- Query permissions on cost center hierarchies
- NO access to customer data, employee records, or marketing systems
The credentials are baked into the container at runtime via Docker secrets. The gateway process reads them on startup and uses them for all API calls. The actual secret file never appears in logs, environment variable dumps, or error messages.
If someone tries to escalate from the Finance gateway to read customer data, the underlying service account credentials simply don’t have permission. It’s not a runtime check—it’s built into the IAM layer.
This is where organizational governance becomes technical infrastructure. Your security team defines the permission matrix. Your DevOps team implements it in service accounts. The gateways enforce it.
Inter-Gateway Communication: When Departments Talk to Each Other
Here’s a real-world scenario: Finance needs to query Marketing’s lead-scoring agents to understand customer acquisition cost. But Finance’s agents run on the Finance gateway, which only has Finance credentials. How does cross-department data flow work?
There are three approaches, each with trade-offs:
Approach 1: Dedicated Cross-Department API
You run a separate “integration gateway” that both Finance and Marketing trust. Finance makes API calls to the integration gateway, which then calls Marketing’s agents on behalf of Finance with specific scoped permissions. The integration gateway acts as a broker, adding audit trails and permission checks. This is the most controlled but requires building and maintaining the broker infrastructure.
Approach 2: Explicit Cross-Department Service Accounts
You create special service accounts that have read-only access across both departments. The Finance gateway uses this service account (with a separate credential set, not shared with the marketing-specific account) to query Marketing’s data. The audit logs show that Finance accessed Marketing data via this specific cross-department account. Clear, traceable, but requires careful permission management—you need to regularly audit what this account actually accesses.
Approach 3: Event-Driven Architecture with Message Queues
Finance publishes events to a message queue when certain thresholds are hit. Marketing subscribes to those events and updates its own internal models. No direct gateway-to-gateway calls. The data flows through RabbitMQ or Kafka, which both systems treat as a neutral authority. This is the least coupled approach but requires event schema management and eventual consistency—Finance’s queries might lag Marketing’s data by a few seconds.
For most teams, Approach 2 (explicit cross-department service accounts) is the sweet spot. You get clarity without huge infrastructure overhead.
Scaling Horizontally: Load Balancing Across Instances
When one department’s usage spikes, you might need multiple gateway instances for that department. Say Marketing is running a big campaign and needs more concurrency. You can scale just the Marketing gateway without touching Finance.
A simple load balancer (nginx, HAProxy, or a cloud load balancer) can distribute incoming requests:
upstream openclaw-marketing {
server localhost:8001;
server localhost:8011;
server localhost:8021;
}
upstream openclaw-finance {
server localhost:8002;
}
upstream openclaw-engineering {
server localhost:8003;
server localhost:8013;
}
server {
listen 80;
location /marketing/ {
proxy_pass http://openclaw-marketing;
proxy_set_header Host $host;
proxy_set_header X-Department marketing;
}
location /finance/ {
proxy_pass http://openclaw-finance;
proxy_set_header Host $host;
proxy_set_header X-Department finance;
}
location /engineering/ {
proxy_pass http://openclaw-engineering;
proxy_set_header Host $host;
proxy_set_header X-Department engineering;
}
}
Notice that X-Department header. That’s audit gold. Every request passing through the load balancer gets tagged with its source department, so your logs and monitoring systems always know which department made which call. That matters when compliance comes knocking.
Monitoring and Observability Across Departments
With separate gateways, your monitoring needs to be department-aware. Each gateway should expose Prometheus metrics like:
openclaw_requests_total{department="marketing"}: total requests from Marketingopenclaw_latency_seconds{department="finance"}: query latency for Financeopenclaw_errors_total{department="engineering"}: errors in Engineeringopenclaw_tokens_consumed{department="marketing"}: token usage by department
This is how you answer questions like “are we hitting rate limits?” (yes, Finance is, but only Finance). “Which department’s queries are slowest?” (Finance, probably because they’re more complex). “Did that deploy break something for Marketing?” (no, their error rate’s fine, but Engineering spiked).
With a single shared gateway, all that data gets muddled together. With per-department gateways, every metric tells you which department it came from.
Deployment and Rollout Strategy
Here’s the practical path:
Phase 1: Deploy the infrastructure (Docker Compose with all three gateways) to a staging environment. Test that requests to port 8001 don’t bleed into port 8002’s data. Verify that each gateway’s health checks work.
Phase 2: Migrate one department (probably Engineering—they’re most tolerant of this stuff) to use the isolated gateway. Monitor for a week. Make sure nothing breaks.
Phase 3: Migrate the remaining departments. Stagger it by a few days so if something goes wrong, you’re not fixing three departments at once.
Phase 4: Decomission the old shared gateway once you’re confident everything’s stable.
The rollback is simple: point departments back to the old shared gateway if needed. You haven’t deleted it yet, so traffic can flow backwards if something breaks.
Compliance and Audit Trails: Making Boundaries Enforceable
Here’s the thing about architecture that actually matters: it becomes evidence. When your compliance officer asks “can you prove that Finance didn’t access Marketing data last quarter?”, you need more than a pinky promise. You need logs.
With per-department gateways, every request is tagged with its source (the gateway), and every gateway has its own audit trail. This is powerful.
Logging Strategy:
Each gateway should log to a separate, immutable location:
services:
openclaw-gateway-finance:
# ... existing config ...
volumes:
- ./logs/finance:/var/log/openclaw
- /mnt/audit-finance:/var/log/audit # Separate immutable mount
The audit volume points to a read-only, time-locked storage. Once a log entry is written, it can’t be modified or deleted—not even by root. This is where you’d mount an S3 bucket or a syslog server that enforces immutability.
What to Log:
- Timestamp (millisecond precision)
- Source IP (the client connecting to the gateway)
- Request content (sanitized—don’t log passwords)
- Response status (success/failure)
- Database tables accessed (from service account audit logs)
- Errors and exceptions
- Authentication failures
Example log entry:
{
"timestamp": "2026-03-17T14:23:15.234Z",
"gateway": "finance-gateway",
"source_ip": "100.64.2.15",
"user": "finance-analyst-1",
"action": "query_ledger",
"tables_accessed": ["gl_transactions", "cost_centers"],
"status": "success",
"response_time_ms": 245,
"tokens_used": 1200
}
Now when compliance asks “did anyone from Marketing touch the GL transactions table?”, you query your audit logs for gateway != finance-gateway AND tables_accessed contains "gl_transactions". The answer should be no.
Retaining Logs for Compliance:
Most regulations require 3-7 year log retention. That’s expensive if you’re storing locally. Use a tiered approach:
- Hot tier (30 days): Local storage on the VPS, fast queries
- Warm tier (90 days): Cloud storage (S3, Blob Storage), slower but cheaper
- Cold tier (7 years): Archive storage (Glacier, Archive Blob), rarely accessed but cheap
Set up automated log rotation:
#!/bin/bash
# rotate-logs.sh - run via cron daily
# Archive logs older than 90 days to S3
aws s3 sync /var/log/openclaw/archive/ \
s3://openclaw-audit-logs/$(date -d "90 days ago" +%Y/%m)/
# Delete local logs older than 90 days
find /var/log/openclaw -type f -mtime +90 -delete
Compliance Queries You’ll Need:
- “Show me all access to Finance data in March 2026”
bash
grep "finance-gateway" /var/log/audit/*.log | grep "2026-03"
- “Did anyone outside Engineering access our codebase deployment system?”
bash
grep "tables_accessed.*deployments" /var/log/audit/*.log | grep -v engineering-gateway
- “What’s the error rate for each department this month?”
bash
jq 'select(.timestamp > "2026-03-01" and .status == "error") | .gateway' /var/log/audit/*.log | sort | uniq -c
Your compliance team should be able to run these queries themselves. If they can’t, you haven’t solved the compliance problem—you’ve just pushed it onto your engineering team.
The Real Win: When your security audit happens, you don’t scramble to piece together evidence. You just hand over the audit logs and say “everything’s in there.” The boundaries aren’t just architectural—they’re evidentiary. That’s what makes them real.
When This Architecture Wins
Per-department gateways matter when:
- You have actual multi-tenant concerns (departments with conflicting compliance needs)
- You need audit trails that show which department did what
- Resource contention is real (one department’s usage affects another’s)
- You need different rate limits for different departments
- Credential compromise in one department shouldn’t impact others
If you’ve got a 10-person startup using OpenClaw for internal tooling? Probably overkill. Run the single shared gateway and move on with your life.
If you’ve got hundreds of users across multiple departments with different compliance needs? This isn’t optional. You need the boundary.
The architecture is a tool. Use it when the problem warrants it.
Troubleshooting Multi-Gateway Deployments
Real-world problems you’ll hit:
Problem: Finance gateway keeps crashing but Marketing is fine
Root cause: Finance’s service account doesn’t have permissions, so every query times out and eventually the container runs out of memory.
Solution: Check the service account permissions in your IAM system. Verify Finance’s SA actually has access to the databases it’s trying to query. Test locally first:
# SSH into Finance gateway
docker exec -it openclaw-gateway-finance /bin/bash
# Try a test query with verbose logging
curl -v -H "Authorization: Bearer $FINANCE_SA_TOKEN" \
https://finance-db.internal/api/tables
Problem: Cross-department data leaking
You see Finance queries accessing Marketing tables. This should be impossible, but here it is.
Root cause: You accidentally gave the Finance service account too-broad permissions, or there’s a shared database account being used.
Diagnosis:
# Check what SA each gateway is using
docker inspect openclaw-gateway-finance | jq '.Config.Env' | grep SERVICE_ACCOUNT
# Verify the SA's actual permissions in your IAM
aws iam get-user-policy --user-name finance-sa --policy-name read-permissions
Problem: One department’s spike killed everyone else
Marketing’s batch job consumed all memory, and now Finance can’t connect to the database.
Root cause: No resource limits (you skipped the deploy.resources section in Docker Compose).
Solution: Add resource limits immediately. Restart Docker Compose:
docker-compose down
docker-compose up -d # Uses updated limits
If you already have limits set, increase the host’s resources. You’re undersized.
Problem: Audit logs are filling up the disk
You set up immutable audit logging, and now /var/log/audit is 500GB and the VPS is full.
Root cause: Log rotation script isn’t running, or you’re logging too much detail.
Solution:
# Check if logrotate is configured
sudo cat /etc/logrotate.d/openclaw-audit
# If missing, create it
sudo bash -c 'cat > /etc/logrotate.d/openclaw-audit << EOF
/var/log/audit/*.log {
daily
rotate 30
compress
delaycompress
missingok
notifempty
}
EOF'
# Test it manually
sudo logrotate -f /etc/logrotate.d/openclaw-audit
# Or reduce log verbosity in your gateway config
# (log only errors, not every request)
EOF
These are the real problems. Plan for them.
The Bottom Line
A shared agent is a shared liability. Once you cross that line into multi-department deployment, the complexity isn’t in running multiple gateways—it’s minimal with Docker Compose. The complexity is in the governance: who gets what credentials, how do you audit cross-department boundaries, what happens when one department compromises the other’s trust.
Per-department gateway instances solve that by making the boundary real, enforceable, and auditable. Your security team gets what it needs. Your operations team gets a straightforward deployment. Your departments get isolation.
Deploy it, monitor it, and stop worrying about whether Finance’s queries can accidentally touch Marketing’s data.