Quick Deploy — Control Plane, Bouncer, and First Control

Audience: DevOps / Platform engineers + Control authors Time: under 15 minutes for the UI-first Shadow path · under 60 minutes for full Custom / Helm

This guide is your starting point. Shadow mode is the recommended starting posture. After the Control Plane is up, the Control Plane UI wizard generates the exact Bouncer command — no hand-edited .env.bouncer. Advanced Helm fine-tuning lives under Custom (Kubernetes / Helm).


1. Start the Control Plane (one command)

Compose (Kickstart package):

unzip controlcore-kickstart.zip && cd controlcore-kickstart
cp .env.example .env   # fill the few REQUIRED secrets once
docker compose up -d

Helm (Custom):

helm upgrade --install controlcore ./controlcore \
  -n controlcore --create-namespace \
  -f values.quickstart.yaml \
  -f values-shadow.yaml

values.quickstart.yaml has eight fields. Full values.yaml is optional — see Custom / Enterprise.

Troubleshooting: If health checks fail, run docker compose ps / kubectl -n controlcore get pods and confirm PostgreSQL + Redis are Ready. Full reference: Troubleshooting.

2. Open the Control Plane and follow the wizard

  1. Sign in (first login may require password change).
  2. You are redirected to Getting Started (/onboarding/bouncer) until a Bouncer heartbeats.
  3. Wizard steps:
    1. Choose mode — Shadow (default) or Full Intercept
    2. Describe resource — name + target host:port
    3. Copy the generated docker run / Helm snippet — paste in a terminal (only step outside the browser)
    4. Live wait — UI polls until the Bouncer registers
    5. Verify — required health checks (optional components do not block)
    6. Compliance packs — optional, skippable

The API behind steps 2–3 is POST /api/peps/quick-provision (returns bouncer_id, scoped API key, and the ready-to-paste command). Shadow feature flags are enabled automatically on an empty first boot.

Troubleshooting: If the Live wait step never advances, check Bouncer logs for PAP_API_URL / API_KEY errors and confirm the Control Plane URL is reachable from the Bouncer host.

3. Confirm audit

Send traffic through the Bouncer and open Operations → Audit. In Shadow mode you will see observation / would-deny events without blocking.


Classic staged path (~60 min)

The sections below remain for operators who prefer manual .env filling or need every knob documented. Prefer the UI-first path above for first install.

This guide takes you from zero to a working setup — Control Plane running, a Bouncer protecting a resource, and your first control (or shadow observation) with a visible audit decision. Once finished, the Deploy & Operate section covers Custom extended deploy (Kubernetes / Helm), scaling, and reusing your existing SIEM.

Going straight to production / Custom? See Custom (Kubernetes / Helm) and Installation (Helm / DevOps reference). Custom deploy is how you keep Splunk (or another SIEM) as the system of record and leave Grafana/Langfuse off.

Need more than sandbox + production? Add up to 3 additional environments any time from Settings → Environments. See Environments for the full add-env runbook and the unified per-env API Key.

Sovereignty defaults (Quick deploy)

  • Observability and audit stay inside your host / VPC — no foreign telemetry egress by default.
  • Grafana and Langfuse are optional Compose/Helm profiles — leave them disabled for a sub-1-hour path; compliance evidence uses Control Plane audit APIs (and SIEM when you configure it).
  • Images and libraries are pulled online from your approved / Canadian-region registries.

By the end of this guide you will have:

  1. A running Control Plane on your infrastructure
  2. A Bouncer intercepting traffic to a resource you own
  3. Your first control authored and deployed to sandbox — either enforcing (Paths A/C) or shadow audit (Path B: observe / SHADOW_WOULD_DENY without blocking)
  4. A visible audit decision confirming the path

Choose your path (~60 min total)

Pick the path that matches your primary outcome. All three share Stages 1 and 4; Stage 2 and Stage 3 differ by topology and first control template. Path B (shadow mode) is the recommended first step for every new deployment — observe would-deny telemetry before switching to enforce.

PathBest forStage 2 focusStage 3 focus
Path B — Compliance discovery (shadow)Recommended first — map gaps before blockingShadow Bouncer with BOUNCER_ENFORCEMENT_MODE=shadowCompliance pack observation controls (no blocking; audit shows would-deny)
Path A — Legacy ModernizationOn-prem APIs, monoliths, DNS/VIP swapBOUNCER_TYPE=reverse-proxy in front of the legacy hostAccess-control template for authenticated API traffic
Path C — AI GovernanceLLM, agents, MCP workloadsEgress Bouncer between app and model providerAI governance template (prompt guardrails, token budget)

Which deployment mode?

Your situationRecommended modeGuide
New to Control CoreShadow — observe and fingerprint without blockingDeployment modes — Shadow
Kubernetes fleetSidecar — co-locate PEP with critical podsDeployment modes — Sidecar
Legacy on-prem / VMReverse proxy — DNS or VIP swap to Bouncer :8080Deployment modes — Reverse proxy

After the Bouncer container starts, it auto-registers with the Control Plane within ~30 seconds. Verify in Settings → PEPs (status Active) or:

curl -s -H "Authorization: Bearer <token>" "http://localhost:8082/health/bouncers" | jq .

Troubleshooting: If the Bouncer does not appear after 60 seconds, check docker compose logs bouncer for PAP_API_URL or API_KEY errors. Full reference: Deployment modes, Troubleshooting.


Before you start

RequirementMinimum
Linux VM (or local Docker)2 vCPU, 4 GB RAM
Docker Engine20.10+
Docker Compose2.0+
Ports available3000, 8080, 8082, 7000
GitHub repo (for controls)Any private repo + Personal Access Token
Deployment packageDownload from info@controlcore.io or your account

Troubleshooting: Unsure which ports are in use? Run ss -tlnp | grep -E '3000|8080|8082|7000' before starting. If any port is taken, map the service to a free port in your .env file using the _PORT override vars.


Stage 1 — Deploy the Control Plane (~20 min)

TL;DR — 6 commands to a running Control Plane

# 1. Extract your package
unzip controlcore-kickstart.zip && cd controlcore-kickstart

# 2. Copy and fill the env template
cp .env.example .env
# Edit .env: set DATABASE_*, REDIS_*, SECRET_KEY, JWT_SECRET_KEY,
#             CC_BUILTIN_ADMIN_PASS, GITHUB_TOKEN, POLICY_REPO_URL,
#             and CONTROL_PLANE_PUBLIC_URL

# 3. Start the stack
docker compose up -d

# 4. Wait for health (run until all return "healthy")
watch docker compose ps

# 5. Verify the Control Plane API
curl -s http://localhost:8082/api/v1/health | jq .status

# 6. Open the console
open http://localhost:3000

Expected output of step 5: "ready" — if you see this, Stage 1 is done.

Detailed steps

Step 1 — Fill .env values (~5 min)

The minimum required variables are:

# Control Plane identity
CONTROL_PLANE_PUBLIC_URL=https://controlplane.yourcompany.com   # or http://localhost:3000 for local

# Database (PostgreSQL)
DATABASE_URL=postgresql://ccuser:ccpassword@db:5432/controlcore

# Cache (Redis)
REDIS_URL=redis://:redispassword@redis:6379/0

# Secrets (generate with: openssl rand -hex 32)
SECRET_KEY=<32-char-random>
JWT_SECRET_KEY=<32-char-random>

# Admin password (Kickstart default; change after first login)
CC_BUILTIN_ADMIN_PASS=<set-via-secret-manager>

# GitHub controls repo
GITHUB_TOKEN=<your-github-pat>
POLICY_REPO_URL=https://github.com/your-org/your-controls-repo
POLICY_REPO_BRANCH=main

Troubleshooting: If docker compose up -d fails with port already allocated, the port is in use. Add CONTROL_PLANE_UI_PORT=3001 (or similar) to .env to use a different port. See Deployment Guide for full port mapping vars.

Step 2 — Start the stack (~5 min)

docker compose up -d
docker compose ps   # all services should be "healthy" within 60–90 seconds

Troubleshooting: If a service stays starting after 2 min:

  • docker compose logs control-plane-api — look for DATABASE_URL or REDIS_URL errors
  • docker compose logs db — check PostgreSQL started correctly
  • Verify env values: docker compose config | grep DATABASE_URL Full reference: Troubleshooting — Deployment issues

Step 3 — First login (~2 min)

  1. Open http://localhost:3000 (or your CONTROL_PLANE_PUBLIC_URL)
  2. Log in with username admin and the password you set in CC_BUILTIN_ADMIN_PASS
  3. Go to ProfileChange Password — do this now

Step 4 — Create API keys (API-first preferred) (~3 min)

  1. API-first path: open http://localhost:8082/devdocs
  2. Call POST /developer-portal/token
  3. Call POST /developer-portal/api-keys/{environment}/generate for sandbox and production
  4. Optional UI path: Settings → Environments

Troubleshooting: If Settings → Environments shows no environments, check that the Control Plane API container (control-plane-api) is healthy. Run curl http://localhost:8082/api/v1/health — if it returns an error, check docker compose logs control-plane-api.

**Stage 1 done ** — You have a running Control Plane. The console is accessible and you have API keys.


Stage 2 — Deploy a Bouncer to protect a resource (~15 min)

A Bouncer is the enforcement component you place in front of the resource you want to protect. Requests go through the Bouncer, which enforces your controls, before reaching the resource.

You need: the Sandbox API key from Stage 1, and a running resource (API, app, or service) to protect. The Bouncer will act as a reverse proxy in front of it.

Bouncer configuration

Create a new .env.bouncer file (or a separate Compose file):

# Bouncer identity
BOUNCER_ID=bouncer-sandbox-01
BOUNCER_NAME=My First Bouncer
BOUNCER_TYPE=reverse-proxy         # or: sidecar
ENVIRONMENT=sandbox

# Connect to Control Plane
PAP_API_URL=http://localhost:8082  # use your CONTROL_PLANE_PUBLIC_URL
API_KEY=<your-sandbox-api-key>     # from Stage 1 Step 4

# Resource being protected
RESOURCE_NAME=My API               # human-readable label (used to pair Sandbox+Production)
RESOURCE_TYPE=api                  # api | webapp | database | ai-agent | mcp-server
TARGET_HOST=my-api:8080            # internal hostname:port of your resource
ORIGINAL_HOST_URL=https://api.yourcompany.com  # external URL your clients use

# Security posture (fail-closed by default)
SECURITY_POSTURE=deny-all

Start the Bouncer

docker compose -f docker-compose.bouncer.yml --env-file .env.bouncer up -d

# Verify the Bouncer is healthy
curl -s http://localhost:8080/health | jq .status

Expected: "ready"

Confirm registration in the Control Plane (or API)

  1. Open the console → Settings → PEPs
  2. Your Bouncer (My First Bouncer) should appear with status Active
  3. Go to Settings → ResourcesMy API should auto-appear

API-first check:

curl -s -H "Authorization: Bearer <token>" "http://localhost:8082/health/bouncers" | jq .
curl -s -H "Authorization: Bearer <token>" "http://localhost:8082/resources" | jq .

Troubleshooting: Bouncer not appearing after 30 seconds?

  • docker compose logs bouncer — look for PAP_API_URL connection errors
  • Verify PAP_API_URL is reachable from the Bouncer container: docker exec bouncer curl -s $PAP_API_URL/api/v1/health
  • Confirm API_KEY matches the Sandbox key in Settings → Environments Full reference: Bouncer Deployment, Troubleshooting

**Stage 2 done ** — A Bouncer is running and your resource is registered in the Control Plane.


Stage 3 — Author your first control (~20 min)

In Control Core, controls are the authorization rules that decide whether a request is allowed or denied. Controls are implemented as Rego policies — but you don't need to write Rego to get started. The Visual Builder generates it for you.

Option A — Visual Builder (fastest if you want UI)

Step 1 — Create a new control (~5 min)

  1. Go to ControlsCreate Control
  2. Choose Visual Builder
  3. On page 1: select the Resource (My API) and the Bouncer (My First Bouncer)
  4. Name: allow-authenticated-users
  5. Environment: Sandbox

Step 2 — Define a simple condition (~5 min)

In the Visual Builder:

  1. Click Add Condition
  2. Subject attribute: user.authenticated
  3. Operator: equals
  4. Value: true
  5. Effect: Allow

This control says: "allow requests where the user is authenticated."

What is user.authenticated? This attribute comes from the request JWT or session. If you connect an identity provider (Okta, Auth0, etc.) as a PIP later, you can use richer attributes like user.department or user.role. For now, user.authenticated is available from the request context without a PIP.

Step 3 — Generate Rego and save (~2 min)

  1. Click Preview Rego — review the generated code
  2. Click Save Draft

Step 4 — Deploy to Sandbox (~3 min)

  1. Click Deploy to Sandbox
  2. Confirm the deployment status shows Active

Troubleshooting: Deploy fails with "Policy Bridge not connected"?

  • Go to Settings → Controls Repository and confirm the GitHub repo is connected
  • Click Test connection — if it fails, check your GITHUB_TOKEN has repo scope
  • Full reference: Admin Troubleshooting

Option B — Start from a template (~5 min)

  1. Go to ControlsTemplates
  2. Browse the Scenario Starter Catalog (AI Security, Data Governance, API Security, etc.)
  3. Click any template → Use this template
  4. Customise the pre-filled conditions for your resource
  5. Deploy to Sandbox

Option C — API-first / OEM control deployment (~5 min)

Use this when parent workflows are agent-driven and tenants should not manage controls in UI.

# 1) Validate policy-as-code
curl -s -X POST "http://localhost:8082/policies-as-code/validate" \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d '{"policy_name":"allow-authenticated-users","policy_content":"package access\nimport rego.v1\ndefault allow = false\nallow if { input.user.authenticated == true }"}'

# 2) Deploy to sandbox
curl -s -X POST "http://localhost:8082/policies-as-code/deploy" \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d '{"policy_name":"allow-authenticated-users","environment":"sandbox","resource_id":"1","policy_content":"package access\nimport rego.v1\ndefault allow = false\nallow if { input.user.authenticated == true }"}'

Verify the control is enforcing

Send a test request through the Bouncer:

# Without authentication — should be DENIED
curl -i http://localhost:8080/any-endpoint

# With a valid bearer token — should be ALLOWED
curl -i http://localhost:8080/any-endpoint \
  -H "Authorization: Bearer <your-jwt>"

Stage 4 — Confirm the audit decision (~5 min)

  1. Go to ControlsAudit Logs (or Dashboard)
  2. You should see recent ACCESS_GRANTED and ACCESS_DENIED events for your resource
  3. Click any event to inspect the full decision: subject, resource, action, outcome, and matched control

Troubleshooting: No audit events visible?

  • Confirm traffic is going through the Bouncer (port 8080), not directly to your resource
  • Check docker compose logs bouncer | grep -i "decision"
  • Confirm the Bouncer's ENVIRONMENT=sandbox matches the environment shown in the console

**Stage 4 done ** — You have a real enforcement decision in audit logs.


You're done

Your Control Core stack is live:

ComponentStatus
Control PlaneRunning on your infrastructure
BouncerProtecting My API in Sandbox
First controlActive and enforcing
Audit decisionsVisible in the console

Next steps


Common first-run issues

SymptomLikely causeFix
control-plane-api container exits immediatelyMissing required env vardocker compose logs control-plane-api — look for KeyError or missing config
Console opens but login failsWrong admin passwordCheck CC_BUILTIN_ADMIN_PASS in .env; restart: docker compose restart control-plane-api
Bouncer shows "Disconnected" in consoleWrong PAP_API_URL or API_KEYVerify URL is reachable from Bouncer container; confirm API key matches Sandbox key
Control deploy fails ("Policy Bridge not connected")GitHub repo not connectedSettings → Controls Repository → Test connection → fix token scope
No audit events after sending requestsTraffic bypassing BouncerEnsure requests go to port 8080 (Bouncer), not directly to the resource
curl returns 403 for all requestsSECURITY_POSTURE=deny-all with no active controlDeploy and activate a control in Sandbox first