tower-prod) and assume PostgreSQL is available either inside the cluster or as a managed service reachable from pods.
Architecture overview
A standard production install consists of an Ingress / Route, a Tower Service backed by the Tower Deployment, a Tower Sidecar Deployment behind its own Service, and one PostgreSQL database per component:Required resources and dependencies
Reference manifests
The manifests below use literal values for clarity. In practice they are commonly templated via Helm, Kustomize or a deploy script that performs envsubst beforekubectl apply. Variables that vary per environment are called out as <placeholder>.
Namespace
ConfigMap (non-sensitive configuration)
Secret
For demonstration the manifest below usesstringData; in real deployments the values are loaded from a secret manager (Vault, Sealed Secrets, External Secrets Operator).
ConfigMap — environments.json seed (optional)
The Tower seeds the environments table on every startup from a JSON file. The seeder upserts by id, so editing the file and rolling the Deployment is the right way to update environment URLs after the initial bootstrap.
Field reference
The seed schema uses kebab-case keys (JacksonPropertyNamingStrategies.KEBAB_CASE):
The
enabled column is set to true automatically on insert. To temporarily disable an environment without deleting it, edit the row directly in the database.
Deployment
Service
Ingress (vanilla Kubernetes / Traefik)
cert-manager operator, annotate the Ingress accordingly:
Sidecar component (required)
Tower requires the Sidecar container for its core workflow features — process state, data index queries, jobs, and audit-trail SVG rendering. A Tower deployment without a matching Sidecar will start and accept logins, but workflow-driven endpoints will silently no-op. Deploy both together. A minimal sidecar deployment in the same namespace:ALETYX_OIDC_CALLBACK_SECRET. Rotation requires updating both Secrets and rolling both Deployments.
Once both are running, point Tower at the Sidecar Service in the Tower ConfigMap:
The Sidecar image expects BPMN process diagrams (
*.svg) at ${KOGITO_SVG_FOLDER_PATH} (default /svgs) for the audit-trail UI to render process pictures. If you ship custom workflows, mount a ConfigMap containing the rendered SVGs at that path. Refer to the Sidecar package documentation for the full list.Database connectivity
For an in-cluster PostgreSQL, refer to it via the standard service DNS pattern (<service>.<namespace>.svc.cluster.local). For a managed database (Cloud SQL, RDS, Azure Database), use the host endpoint directly. In both cases:
- Ensure pods have outbound access to the database. On clusters with default-deny
NetworkPolicy, add a rule that permits Tower pods to the database service / IP range. - Place the password in the
tower-secret. Never embed it in the URL or in the ConfigMap. - For TLS connections, append the standard JDBC parameters to the URL (
?ssl=true&sslmode=require).
OpenShift-specific considerations
Routes vs. Ingress
OpenShift supports both. Most OpenShift teams preferRoute, which is the native primitive and handles TLS termination at the cluster router automatically:
Ingress (e.g., behind a shared ingress controller), the manifest from the Kubernetes section works on OpenShift unchanged provided the cluster has an IngressClass for that controller.
Security Context Constraints (SCC)
The Tower image runs asnonroot and tolerates a read-only root filesystem with /tmp as emptyDir. This satisfies the OpenShift default restricted-v2 SCC without modification. No SCC binding is required.
Image streams
If your team uses ImageStreams to pin and promote images across environments, a typical setup is:image-lookup annotation:
Persistent volumes
Tower itself does not require persistent storage — all state is in PostgreSQL. The Sidecar component writes only to/tmp and PostgreSQL.
If you mount the seed JSON via PVC instead of ConfigMap (rare, mostly relevant for very large environment lists), use accessModes: [ReadOnlyMany] to allow scaled deployments.
High availability
Running multiple Tower replicas is supported provided:- The OIDC flow uses stateless tokens (default with both Entra and Keycloak — no session affinity required).
- The shared callback secret is identical on every replica (it is, since it lives in a Secret).
- The Decision Control health probes are tolerant of being run by multiple replicas (they are; each replica probes independently).
PodDisruptionBudget to keep at least one replica during voluntary disruptions:
Operational commands
Diagnostic patterns
For diagnostics that require a shell, the Tower image is distroless and ships no shell of its own. Use
kubectl debug with a container image your organization trusts (an ephemeral debug container targeting the Tower pod), or run the diagnostic command from a separate pod in the same namespace.
Troubleshooting
Cannot log in
- Verify the OIDC provider’s registered redirect URI matches
${APP_HOST}/auth/callbackexactly — including the scheme (https://) and any trailing path. - Tail the Tower pod logs while attempting to log in:
- Confirm the OIDC provider is reachable from inside the cluster (network policies, firewall, DNS).
Environments not loading
The Tower and Sidecar images are distroless and ship no shell, sokubectl exec is not available. Use kubectl run to launch a short-lived helper pod in the same namespace, or kubectl debug --copy-to with a tools image, and run the checks from there.
- Verify the Tower Sidecar Deployment is
1/1 Running— Tower depends on it for environment state. If the Sidecar is missing or crash-looping, the environments page surfaces no rows. - From a helper pod in the same namespace, check the Sidecar Service is reachable at the URL Tower uses (the value of
ALETYX_DC_TOWER_SIDECAR_URL): - Verify the Sidecar can reach its database, again from a helper pod:
- The
environmentstable is seeded from/app/config/environments.jsonon first boot. If the table is empty in an existing deployment, confirm the seed file is mounted (see chapter 4 — ConfigMapenvironments.jsonseed).
Tasks not appearing
- Verify the signed-in user holds a role in
ALETYX_OIDC_RBAC_ROLES_ALLOWED_*forACCESSor the more specific action — empty allow-lists deny everything when RBAC is enabled. - Confirm the underlying workflow ran and produced tasks: the Sidecar’s Data Index logs show task creation events.
- Tail the Tower pod logs for errors emitted by the task-fetch loop. Slow / unreachable Sidecar HTTP shows up as timeouts.