Deployment
opensecstack supports two primary deployment topologies: a single-host Docker Compose stack (Tier 1) for standard deployments, and a multi-host Kubernetes deployment with optional Helm chart (Tier 2) for elevated or production environments. For environment requirements and toolchain setup, see Installation.
Deployment options
| Option | Best for | Location |
|---|---|---|
| Docker Compose | Local dev, NGOs, mid-sized enterprises, research labs | deploy/docker-compose.yml |
| Kubernetes (plain YAML) | Production clusters, multi-region, zero-trust environments | deploy/k8s/ |
| Helm chart | Parameterised production installs, GitOps workflows | deploy/helm/opensecstack/ |
Docker Compose (Tier 1)
The deploy/docker-compose.yml file brings up all released platforms on a single Docker network with two segments: frontend (internet-facing services) and backend (databases, caches, internal APIs). TLS terminates at an upstream ingress (Traefik, nginx, or Caddy); inter-service traffic is plain HTTP inside the trusted Docker network.
"cm"># Copy and populate the environment file cp deploy/.env.example deploy/.env "cm"># Start all services docker compose -f deploy/docker-compose.yml up -d "cm"># Check health of every container docker compose -f deploy/docker-compose.yml ps "cm"># View logs for a specific service docker compose -f deploy/docker-compose.yml logs -f apiguard-api "cm"># Stop without destroying data volumes docker compose -f deploy/docker-compose.yml down "cm"># Full teardown including volumes (destructive) docker compose -f deploy/docker-compose.yml down -v
A separate dev override file, deploy/docker-compose.dev.yml, is available for local development with mounted source directories.
Kubernetes / Helm (Tier 2)
For multi-host or zero-trust environments, the Kubernetes manifests in deploy/k8s/ deploy each platform as a Deployment with readiness and liveness probes, ClusterIP services, and nginx Ingress resources.
"cm"># Apply namespace first kubectl apply -f deploy/k8s/namespace.yaml "cm"># Create secrets (never commit real values) kubectl create secret generic opensecstack-db-secrets \ --namespace opensecstack \ --from-literal=POSTGRES_PASSWORD='<strong-password>' kubectl create secret generic opensecstack-app-secrets \ --namespace opensecstack \ --from-literal=APIGUARD_JWT_SECRET='<random-min-32-chars>' \ --from-literal=NIS2_JWT_SECRET='<random-min-32-chars>' \ --from-literal=REDIS_PASSWORD='<strong-password>' "cm"># Apply all manifests recursively kubectl apply -f deploy/k8s/ -R -n opensecstack "cm"># Watch pods come up kubectl get pods -n opensecstack -w
Helm
cp deploy/helm/opensecstack/values.prod.yaml.example \ deploy/helm/opensecstack/values.prod.yaml "cm"># Edit values.prod.yaml — set image tags, replica counts, ingress hosts helm install opensecstack deploy/helm/opensecstack/ \ --namespace opensecstack --create-namespace \ --values deploy/helm/opensecstack/values.prod.yaml "cm"># Rolling upgrade helm upgrade opensecstack deploy/helm/opensecstack/ \ --namespace opensecstack \ --values deploy/helm/opensecstack/values.prod.yaml
apiguard.opensecstack.local and nis2compass.opensecstack.local. Update the host: field in each ingress manifest before applying to production.Port matrix
All API services expose /health and /metrics on their main port.
| Platform | API port | Dashboard | Notes |
|---|---|---|---|
| APIGuard | 8080 | 3000 | Public-facing scanning engine |
| NIS2 Compass | 8090 | 3001 | Python + Flask |
| ThreatFlow | 8091 | — | Threat intelligence (STIX 2.1) |
| IRFlow | 8083 | — | Incident response orchestrator |
| SIN Community | 8089 | — | Developer knowledge hub; Meilisearch on :7700 (internal) |
| CITADEL | 8099 | — | Internal-only; governance engine |
| sinauth | 8100 | 5173 | OAuth 2.0 / OIDC identity provider |
| VertGuard | 8091 | 3009 | AI-attack defence; ML gRPC side-car on :50051 (internal) |
| OpenCSIRT | 8088 | 3088 | Python advisory sub-system on :8089 (internal) |
| OpenScrub | 8087 | 3087 | XDP/eBPF DDoS mitigation; hostNetwork on edge nodes |
| PostgreSQL | 5432 | — | One DB instance per platform for isolation |
| Redis | 6379 | — | Queue and cache (internal) |
Environment and secrets
All runtime configuration is driven by environment variables. The canonical template is deploy/.env.example. Copy it to deploy/.env before starting the stack and fill in every required value.
Key variables to set for a minimal deployment:
"cm"># Database POSTGRES_PASSWORD=<strong-password> NIS2_DB_PASSWORD=<strong-password> SIN_DB_PASSWORD=<strong-password> SINAUTH_DB_PASSWORD=<strong-password> "cm"># Redis REDIS_PASSWORD=<strong-password> "cm"># JWT signing secrets (generate with: openssl rand -hex 32) APIGUARD_JWT_SECRET=<random-min-32-chars> NIS2_JWT_SECRET=<random-min-32-chars> NIS2_SECRET_KEY=<random-min-32-chars> COMMUNITY_JWT_SECRET=<random-min-32-chars> COMMUNITY_PASSWORD_PEPPER=<random-min-32-chars> "cm"># CITADEL HMAC secrets CITADEL_API_KEY=<random-min-32-chars> IRFLOW_CITADEL_KEY_SECRET=<random-min-32-chars> IRFLOW_WEBHOOK_APIGUARD_SECRET=<random-min-32-chars> IRFLOW_WEBHOOK_CITADEL_SECRET=<random-min-32-chars> IRFLOW_WEBHOOK_THREATFLOW_SECRET=<random-min-32-chars> "cm"># Meilisearch MEILI_MASTER_KEY=<random-min-32-chars> "cm"># sinauth (OIDC issuer URLs) SINAUTH_ISSUER=https://auth.example.com SINAUTH_SITE_URL=https://auth.example.com
deploy/.env to source control. For production, replace env var injection with a secret manager (HashiCorp Vault, AWS Secrets Manager, or GCP Secret Manager) and rotate HMAC secrets quarterly and the CITADEL anchor key yearly. See Security for the full deployment-tier control matrix.CITADEL wiring
CITADEL is the governance layer and is intentionally deployed separately — its compose file lives under .citadel/docker-compose.yml. Every platform that emits governance events must be pointed at the CITADEL API:
environment: - APIGUARD_CITADEL_URL=http://citadel-api:8099 - APIGUARD_CITADEL_API_KEY=${CITADEL_API_KEY:-}
When running the full stack with both compose files on the same Docker network, use the service hostname citadel-api and port 8099 as the base URL. All platform-to-CITADEL calls are HMAC-SHA256 signed with the shared CITADEL_API_KEY secret and include a ±5-minute replay window.
If CITADEL is unreachable, all MARSHAL-gated actions across the ecosystem are blocked and platforms return HTTP 503 to callers — they never silently proceed.
Network zones
| Zone | Contains | Access |
|---|---|---|
| Public | Ingress / WAF, dashboards (APIGuard UI, NIS2 Compass UI) | Internet-facing |
| Application | Platform API servers (ports 8080–8100) | Ingress + internal services only |
| Governance | CITADEL (:8099) | Only IRFlow, APIGuard, NIS2 Compass, ThreatFlow |
| Data | PostgreSQL (:5432), Redis (:6379) | Owning platform only |
| Observability | Prometheus, Grafana, Jaeger | Internal ops network only |
For Tier 2 multi-host deployments, a service mesh (Istio or Linkerd) enforces mTLS between every pair of platforms, and CITADEL runs in active/passive HA with an external leader lock (Consul Lease or Kubernetes Lease) to keep the WORM chain single-writer.