Installation Reference — Helm & Custom Deployment

Audience: DevOps engineers, platform engineers, SREs Time: ~45–90 min (first production deployment)

New to Control Core? Start with Quick deploy first — it walks you through the complete flow in 60 minutes using Docker Compose. Return to this page when you are ready for a production Helm deployment or need a detailed configuration reference.

This guide serves as the full configuration reference for deploying Control Core in production environments — Kubernetes with Helm, bare-metal, or cloud VMs. It covers all required and optional configuration values, TLS setup, secret management patterns, and validation steps.


Where this fits in the deployment journey

Quick deploy (Docker Compose, ~60 min)    ← start here for first evaluation
       ↓
Kickstart guide (full Docker Compose ops) ← detailed Compose reference
       ↓
This guide (Helm / production reference)  ← you are here
       ↓
Enterprise guide (HA, multi-region, scaling)

Preflight checklist

Before beginning a production deployment, confirm:

  • Kubernetes 1.24+ cluster (or Docker Engine 20.10+ for Compose) with sufficient resources
  • Helm 3.10+ installed on the operator workstation
  • PostgreSQL 13+ (managed service recommended — AWS RDS, Azure Database, GCP Cloud SQL)
  • Redis 6+ (managed service recommended — AWS ElastiCache, Azure Cache for Redis)
  • DNS entries provisioned for Control Plane and protected resource domains
  • TLS certificate strategy decided (ingress-controller-managed or customer-provided cert/key)
  • Private GitHub (or GitLab) repository created for your controls, with a personal access token
  • Secrets manager available (HashiCorp Vault, AWS Secrets Manager, Azure Key Vault, or Kubernetes Secrets)
  • Control Core deployment package received from info@controlcore.io

Troubleshooting: If you have not yet received your deployment package, contact info@controlcore.io with your company name and tier (Kickstart or Custom). Packages are provisioned within 1 business day.


Helm deployment

Add the Helm repository

helm repo add controlcore https://charts.controlcore.io
helm repo update

Review and configure values

Download the default values file to understand all configurable options:

helm show values controlcore/control-core > my-values.yaml

Edit my-values.yaml for your environment. The sections below describe the required and recommended values.

Control Plane values

controlPlane:
  # Public URL (HTTPS) — used for OIDC callbacks and API client configuration
  publicUrl: "https://controlplane.yourcompany.com"

  # Image (provided by Control Core — do not change tag unless instructed)
  image:
    repository: "registry.controlcore.io/control-plane-api"
    tag: "latest"     # pin to a specific version for production
    pullPolicy: IfNotPresent

  replicaCount: 2     # minimum 2 for production HA
  
  resources:
    requests:
      cpu: "500m"
      memory: "512Mi"
    limits:
      cpu: "2000m"
      memory: "2Gi"

  # All secrets injected from Kubernetes Secret or external secret manager
  secretRef: "controlcore-secrets"   # name of a pre-created K8s Secret

Database values

database:
  # Use external managed PostgreSQL — do not use the bundled dev DB in production
  external: true
  host: "your-db-host.rds.amazonaws.com"
  port: 5432
  name: "controlcore"
  user: "ccuser"
  # Password sourced from secretRef — never inline in values
  passwordSecretKey: "DATABASE_PASSWORD"

Redis values

redis:
  external: true
  host: "your-redis.cache.amazonaws.com"
  port: 6379
  passwordSecretKey: "REDIS_PASSWORD"

Controls repository (GitHub)

controlsRepo:
  url: "https://github.com/your-org/your-controls-repo"
  branch: "main"
  pollingIntervalSeconds: 30
  # Token sourced from secretRef
  tokenSecretKey: "GITHUB_TOKEN"

Bouncer values

Each Bouncer is deployed as a separate Helm release (or as a values override per environment):

bouncer:
  id: "bouncer-prod-api-01"
  name: "Production API Bouncer"
  type: "reverse-proxy"          # reverse-proxy | sidecar
  environment: "production"

  # Control Plane connection
  controlPlaneUrl: "https://controlplane.yourcompany.com"
  apiKeySecretKey: "BOUNCER_API_KEY"

  # Resource being protected
  resourceName: "Customer API"
  resourceType: "api"            # api | webapp | database | ai-agent | mcp-server
  targetHost: "customer-api-svc:8080"
  originalHostUrl: "https://api.yourcompany.com"

  # Fail-closed security posture
  securityPosture: "deny-all"

  replicaCount: 2
  resources:
    requests:
      cpu: "250m"
      memory: "256Mi"
    limits:
      cpu: "1000m"
      memory: "512Mi"

Troubleshooting: Bouncer pods stay in CrashLoopBackOff?

  • kubectl logs deploy/controlcore-bouncer — look for PAP_API_URL connection refused or API_KEY invalid
  • Confirm the Control Plane is healthy before deploying Bouncers
  • Confirm BOUNCER_API_KEY in the K8s secret matches the environment API key in Settings → Environments

Ingress values

ingress:
  enabled: true
  className: "nginx"          # or alb, traefik, etc.
  annotations:
    cert-manager.io/cluster-issuer: "letsencrypt-prod"
  hosts:
    - host: "controlplane.yourcompany.com"
      paths:
        - path: "/"
          pathType: Prefix
  tls:
    - secretName: "controlcore-tls"
      hosts:
        - "controlplane.yourcompany.com"

Deploy

# Create namespace
kubectl create namespace controlcore

# Create secrets (or use external-secrets operator / Vault agent)
kubectl create secret generic controlcore-secrets \
  --namespace controlcore \
  --from-literal=SECRET_KEY=$(openssl rand -hex 32) \
  --from-literal=JWT_SECRET_KEY=$(openssl rand -hex 32) \
  --from-literal=DATABASE_PASSWORD='<your-db-password>' \
  --from-literal=REDIS_PASSWORD='<your-redis-password>' \
  --from-literal=GITHUB_TOKEN='<your-github-pat>' \
  --from-literal=BOUNCER_API_KEY='<your-api-key>' \
  --from-literal=CC_BUILTIN_ADMIN_PASS='<your-admin-password>'

# Deploy Control Plane
helm install controlcore controlcore/control-core \
  --namespace controlcore \
  --values my-values.yaml \
  --wait

# Deploy Bouncer (separate release)
helm install controlcore-bouncer controlcore/bouncer \
  --namespace controlcore \
  --values my-bouncer-values.yaml \
  --wait

Troubleshooting: Helm install times out waiting for pods?

  • kubectl get pods -n controlcore — check pod status
  • kubectl describe pod <pod-name> -n controlcore — look for image pull errors or resource limits
  • Confirm registry credentials are configured: kubectl get secret controlcore-registry -n controlcore

Required configuration reference

Control Plane

VariableRequiredDescription
CONTROL_PLANE_PUBLIC_URLYesHTTPS URL of the Control Plane UI (used for OIDC callbacks)
DATABASE_URLYesPostgreSQL connection string
REDIS_URLYesRedis connection string
SECRET_KEYYes32-char random — used for internal encryption
JWT_SECRET_KEYYes32-char random — used for JWT signing
CC_BUILTIN_ADMIN_PASSYesInitial admin password (change after first login)
GITHUB_TOKENYesPersonal access token with repo scope for controls repository
POLICY_REPO_URLYesHTTPS URL of your controls GitHub repository
POLICY_REPO_BRANCHNoBranch to sync (default: main)
LOG_LEVELNoINFO (default) · DEBUG · WARNING · ERROR
CORS_ALLOWED_ORIGINSNoComma-separated list of allowed origins for the UI
OIDC_ENABLEDNofalse (default) — set to true to enable OIDC SSO
OIDC_PROVIDER_URLConditionalRequired when OIDC_ENABLED=true

Bouncer

VariableRequiredDescription
BOUNCER_IDYesUnique identifier for this Bouncer instance
BOUNCER_NAMEYesHuman-readable label (shown in Console)
BOUNCER_TYPEYesreverse-proxy or sidecar
ENVIRONMENTYessandbox or production
PAP_API_URLYesInternal URL of the Control Plane API
API_KEYYesEnvironment API key from Settings → Environments
RESOURCE_NAMEYesHuman-readable resource name (used to pair Sandbox + Production)
RESOURCE_TYPEYesapi · webapp · database · ai-agent · mcp-server
TARGET_HOSTYesInternal host:port of the protected resource
ORIGINAL_HOST_URLYesExternal URL your clients use to reach the resource
SECURITY_POSTURENodeny-all (default and recommended)
BOUNCER_ENABLE_POST_DECISION_ACTIONSNotrue (default) — set to false to disable action dispatch

Domain and TLS

  • Set CONTROL_PLANE_PUBLIC_URL to a valid HTTPS domain (not an IP) — this is used for OIDC redirect URIs and API client configuration
  • TLS must be terminated at or before the ingress — do not run the Control Plane API on plaintext HTTP in production
  • For protected resources, set ORIGINAL_HOST_URL to the external HTTPS URL clients use, and TARGET_HOST to the internal address (e.g. my-service:8080) — never expose internal addresses externally

Security requirements

  • TLS 1.2+ on all external and cross-zone traffic
  • No plaintext secrets in version control — use a secrets manager or Kubernetes Secrets
  • Database and Redis endpoints restricted to private networks (no public exposure)
  • Separate API keys per environment (Sandbox and Production never share a key)
  • Schedule key and secret rotation (recommended: every 90 days minimum)

Validation checklist

Run these checks after deployment to confirm a healthy installation:

# Control Plane health
curl -s https://controlplane.yourcompany.com/api/v1/health | jq .

# Expected: {"status": "ready", "database": "connected", "redis": "connected"}

# Bouncer health
curl -s https://api.yourcompany.com/health | jq .
# Expected: {"status": "ready", "policy-bridge": "connected"}

Then in the Console:

  • Login works with admin credentials
  • Settings → PEPs — Bouncer appears as Active
  • Settings → Resources — resource auto-registered
  • Settings → Controls Repository — GitHub sync status is healthy
  • Create a test control → deploy to Sandbox → send a request → audit decision visible

Troubleshooting: Any check failing? See Admin Troubleshooting and Troubleshooting.


Next steps