title: Bouncer placement description: Decide where to place the Bouncer (sidecar vs reverse-proxy, ingress vs egress) with a decision matrix, copy-paste manifests, and real-world scenarios. audience: Platform engineers and solution architects timeEstimate: 30 minutes prerequisites:
- Control Plane installed (Kickstart or Helm)
- Familiarity with your traffic path (ingress, egress, or both)
- Day-1 posture uses BOUNCER_ENFORCEMENT_MODE=shadow
Bouncer placement
Choose where Control Core inspects traffic before you write controls. Placement is a topology decision — sidecar vs reverse-proxy, and ingress vs egress vs bidirectional — not an application-specific fork of the Bouncer binary.
Time estimate: ~30 min to pick a topology, apply a Day-1 shadow overlay, and verify no bypass path remains.
Day-1 default: set BOUNCER_ENFORCEMENT_MODE=shadow so traffic always reaches upstream while you collect decisions and fingerprints. Graduate to enforce after shadow discovery.
Core question
Where does traffic need to be inspected?
Answer that before choosing Helm values. Every viable design places the universal Bouncer on the path that carries the requests (and optionally responses) your controls must see:
| You need to evaluate… | Place the Bouncer… |
|---|---|
| Client → API / service (north–south) | On the ingress path |
| App / agent → external LLM, SaaS, partner API (east–west egress) | On the egress path |
| Both directions for the same workload | Bidirectional (two placements or one Bouncer with both routes) |
Controls (Rego) decide allow, deny, mask, and obligations. Placement only decides which packets the Bouncer can see.
Decision matrix
| Scenario | Topology | Recommended placement | Why |
|---|---|---|---|
| Single REST API (VM or small Compose stack) | Reverse-proxy | One Bouncer in front of the API | Simplest DNS / LB swap; one pair (sandbox + production) |
| Kubernetes microservices fleet | Sidecar (per critical pod) or reverse-proxy at mesh edge | Sidecar for high-value services; gateway reverse-proxy for shared entry | Per-pod isolation vs shared edge — pick by blast radius |
| Existing API gateway / Ingress already in path | Reverse-proxy (or sidecar behind gateway) | Insert Bouncer as next hop after gateway, or replace upstream target | Reuse TLS / WAF at the edge; Bouncer owns PBAC |
| AI / LLM provider traffic | Egress reverse-proxy or egress sidecar | Bouncer between app and provider base URL | Controls see prompts, model IDs, and egress PII before they leave |
| Legacy monolith (DNS / VIP swap) | Reverse-proxy | Point VIP / DNS at Bouncer :8080; upstream = monolith private host | Zero app code change; classic modernization pattern |
| Multi-tenant SaaS (shared edge, many backends) | Reverse-proxy (1:n) | One or few edge Bouncers; TARGET_HOST / routes per backend group | One Control Plane; scale Bouncers by tenant or product line |
| Financial low-latency / UDS co-location | Sidecar | Co-locate in pod; TARGET_HOST=127.0.0.1 (or loopback UDS when configured) | Keeps hop local; still Day-1 shadow |
| Defense / air-gapped | Sidecar or reverse-proxy (offline Policy Bridge) | Same patterns inside the enclave; no public egress required for enforcement | Offline OPAL / last-good bundle; SPIFFE optional per site policy |
Troubleshooting: If traffic never appears in Control Plane audit after deploy, the Bouncer is usually off the path (clients still hit the app port). Confirm Service / LB
targetPortis 8080, not the app container port. Full reference: Troubleshooting.
Ingress vs egress vs bidirectional
Ingress (north–south)
Evaluates inbound requests (and often responses) before they reach your protected resource.
Click to enlarge
What it evaluates: subject, action, resource path/method, headers, body obligations (masking), environment attributes from PIPs.
Example — healthcare consent (ingress): A clinic API receives chart reads. Ingress Bouncer evaluates consent and role attributes from PIPs before the request hits the records service. Day-1: BOUNCER_ENFORCEMENT_MODE=shadow to prove would-deny without blocking care workflows.
Egress (outbound)
Evaluates traffic leaving your workload toward an external or shared upstream (LLM, partner API, shared tool).
Click to enlarge
What it evaluates: outbound URL/host metadata, model or tool identifiers, prompt/body fields for PII obligations, caller identity.
Example — financial LLM egress PII: Trading research agents call an LLM API. An egress Bouncer sits between the agent and the provider so controls can redact account numbers from prompts before they leave the VPC.
Bidirectional
Same workload needs both inbound governance and outbound tool/LLM governance.
Click to enlarge
Example — AI agent runner: Ingress sidecar (or edge reverse-proxy) governs who may invoke the agent; egress sidecar governs which tools/models the agent may call and what data may leave. Use two Bouncer registrations (or clearly named pairs) sharing one Control Plane.
Sidecar deep guide
When to use
- Pod- or task-local enforcement with minimal cross-service coupling
- Different controls per microservice without a shared gateway choke point
- Latency-sensitive financial services that keep the hop on loopback
- AI runners that need a dedicated egress sidecar next to the agent process
What BOUNCER_TYPE=sidecar means
Technically, sidecar means the Bouncer shares the workload network namespace and forwards to a localhost (or pod-local) upstream:
| Setting | Typical sidecar value |
|---|---|
BOUNCER_TYPE | sidecar |
TARGET_HOST | 127.0.0.1 (Helm: targetHost) |
TARGET_PORT | App listen port inside the pod (Helm: targetPort) |
| Client entry | Service / Ingress → Bouncer 8080 |
The app continues to bind its own port; clients must never reach that port from outside the pod.
Copy-paste Kubernetes Pod + Service
apiVersion: v1
kind: Pod
metadata:
name: payments-api
labels:
app: payments-api
spec:
containers:
- name: app
image: your-registry/payments-api:stable
ports:
- containerPort: 8000
name: app
# App listens on 8000; only the Bouncer should be Service-exposed.
- name: cc-bouncer
image: controlcoreio/cc-bouncer-envoy:<tag>
ports:
- containerPort: 8080
name: bouncer
env:
- name: BOUNCER_TYPE
value: sidecar
- name: BOUNCER_ENFORCEMENT_MODE
value: shadow
- name: TARGET_HOST
value: "127.0.0.1"
- name: TARGET_PORT
value: "8000"
- name: ENVIRONMENT
value: sandbox
- name: PAP_API_URL
value: "https://control-plane.example.internal"
- name: API_KEY
valueFrom:
secretKeyRef:
name: cc-bouncer-sandbox
key: api-key
- name: BOUNCER_ID
value: bouncer-payments-sandbox
- name: RESOURCE_NAME
value: Payments API
---
apiVersion: v1
kind: Service
metadata:
name: payments-api
spec:
selector:
app: payments-api
ports:
- name: http
port: 80
# CRITICAL: target the Bouncer listener, not the app port
targetPort: 8080
Common mistake: Setting Service
targetPortto the app port (e.g.8000). That bypasses the Bouncer entirely. Always usetargetPort: 8080(Bouncer). Keep the app port unexposed on the Service.
Troubleshooting: Pod Running but clients get unfiltered responses —
kubectl get svc payments-api -o yamland confirmtargetPort: 8080. Check Bouncer logs for registration:kubectl logs <pod> -c cc-bouncer. Full reference: Troubleshooting.
Scenario: financial Kubernetes microservices fleet
Deploy a sidecar Bouncer on each payment / ledger service that must be independently controlled. Share one Control Plane; use consistent RESOURCE_NAME pairs (sandbox + production). Keep BOUNCER_ENFORCEMENT_MODE=shadow until gap reports and simulator review are green. For ultra-low latency, keep TARGET_HOST=127.0.0.1 and co-locate; tune separately in Bouncer performance tuning.
Scenario: AI agent runner (ingress + egress sidecars)
| Role | BOUNCER_TYPE | TARGET_HOST | Traffic |
|---|---|---|---|
| Ingress sidecar | sidecar | 127.0.0.1 → agent listen port | Clients → agent |
| Egress sidecar | sidecar | Provider or internal gateway host | Agent → LLM / MCP |
Register both with distinct BOUNCER_ID values. Author controls against each resource independently — do not hardcode agent paths in deployment manifests; let Rego match on generic request attributes.
Reverse-proxy deep guide
When to use
- Legacy hosts where you cannot inject a sidecar
- Shared edge for many backends (SaaS, internal tools)
- ALB / NLB / Ingress already terminates TLS and needs a single PBAC hop
- DNS / VIP cutover modernization
Compose example
services:
cc-bouncer:
image: controlcoreio/cc-bouncer-envoy:<tag>
ports:
- "8080:8080"
environment:
BOUNCER_TYPE: reverse-proxy
BOUNCER_ENFORCEMENT_MODE: shadow
TARGET_HOST: app
TARGET_PORT: "8000"
ENVIRONMENT: sandbox
PAP_API_URL: https://control-plane.example.internal
API_KEY: ${CC_SANDBOX_API_KEY}
BOUNCER_ID: bouncer-edge-sandbox
RESOURCE_NAME: Edge API
depends_on:
- app
app:
image: your-registry/customer-api:stable
# No host port publish — only reachable on the Compose network
expose:
- "8000"
VM example
- Run the Bouncer package (systemd or Docker) listening on
:8080. - Set
BOUNCER_TYPE=reverse-proxy,TARGET_HOST=<private monolith IP>,TARGET_PORT=<app port>,BOUNCER_ENFORCEMENT_MODE=shadow. - Point the VIP / DNS A/AAAA record at the Bouncer host.
- Restrict security groups so the monolith port is private-only.
Troubleshooting: After DNS cutover, if some clients still bypass controls, search for hardcoded IPs or alternate CNAMEs still aimed at the monolith. Validate with
dig/nslookupfrom client networks. Full reference: Troubleshooting.
Scenario: insurance legacy DNS swap
Point api.carrier.example at the reverse-proxy Bouncer. Upstream remains the AS/400 or JVM monolith on a private VLAN. No application rewrite. Shadow mode first; promote controls after discovery.
Scenario: AWS ALB → ECS Fargate
| Layer | Points to |
|---|---|
| ALB target group | Bouncer task :8080 |
Bouncer TARGET_HOST | Private Cloud Map / Service Connect name of the app task |
| App task security group | Allow only Bouncer SG on the app port |
Use BOUNCER_TYPE=reverse-proxy and Day-1 BOUNCER_ENFORCEMENT_MODE=shadow. Do not register the app task directly on the ALB.
Scenario: shared internal tools
One reverse-proxy Bouncer fronts a group of internal admin tools (ticketing, wiki, CI metadata APIs). Route by host or path at the edge load balancer into the Bouncer; configure upstreams via deployment values — keep path semantics in controls, not in Bouncer code.
Bypass prevention checklist
Sidecar
- Service / Ingress
targetPort= 8080 (Bouncer), never the app port - App container port not published on a second Service or NodePort
- Frontend and server-side base URLs resolve to the Bouncer Service
- NetworkPolicy (or equivalent) denies pod-external traffic to the app port
- Synthetic check: Bouncer URL healthy; direct app URL unreachable from outside the pod network
Reverse-proxy
- Public DNS / LB / ALB targets only the Bouncer
- Upstream app has no public listener / public target group
- Security groups / firewalls allow clients → Bouncer only; Bouncer → app private
- No alternate CNAME or IP shortcut to the app
- Synthetic check: bouncer path succeeds; direct app path fails from client plane
Helm values quick reference
Map each scenario to Helm / env. Upstream host is Helm targetHost → env TARGET_HOST. Always start with shadow; then follow Shadow discovery quickstart.
| Scenario | BOUNCER_TYPE | Upstream (TARGET_HOST) | Shadow next step |
|---|---|---|---|
| Single REST API | reverse-proxy | App Service DNS / host | Quickstart |
| K8s microservices (per pod) | sidecar | 127.0.0.1 | Quickstart |
| Existing gateway | reverse-proxy | Gateway’s private upstream or app behind gateway | Quickstart |
| AI / LLM egress | reverse-proxy or sidecar | Provider or internal AI gateway host | Quickstart |
| Legacy DNS swap | reverse-proxy | Private monolith host | Quickstart |
| Multi-tenant SaaS edge | reverse-proxy | Per-backend group host / VIP | Quickstart |
| Financial low-latency | sidecar | 127.0.0.1 | Quickstart |
| Defense air-gapped | sidecar or reverse-proxy | Enclave-local upstream | Quickstart |
Example Helm overlay fragment:
bouncerSandbox:
env:
- name: BOUNCER_TYPE
value: sidecar
- name: BOUNCER_ENFORCEMENT_MODE
value: shadow
targetHost: "127.0.0.1"
targetPort: 8000
Related guides
- Shadow discovery quickstart — Day-1 shadow deploy and telemetry
- Shadow compliance discovery — full fingerprint → enforce lifecycle
- Network Bouncer configuration — sync intervals, verification, bypass table
- Multiple Bouncers — pairs and fleet scale
- Bouncer deployment — registration and pairing deep dive
- Custom (Kubernetes / Helm) — chart install
Next steps: Pick a row from the decision matrix → apply the Helm/Compose fragment with BOUNCER_ENFORCEMENT_MODE=shadow → run the shadow discovery quickstart.