Skip to main content
Decision Control Tower ships as a single OCI-compatible container image that bundles the Spring Boot backend and the static frontend bundle. The same image is used across all deployment targets — local Container, Kubernetes, OpenShift — and is configured exclusively through environment variables. This chapter documents the runtime requirements, the full configuration surface, and how to operate the container in each common scenario.

Image

Default tag scheme: <registry>/aletyx/decision-control-tower:<version>. Customers receiving this image through Aletyx distribution will be issued the registry endpoint and pull credentials separately. The image is signed using cosign. Validate the signature before promoting an image into a production registry; the signing key is published alongside each release.

Runtime requirements

The image’s JAVA_TOOL_OPTIONS is set to use -Xms2g -Xmx2g -XX:MaxMetaspaceSize=256m. The container must be allocated enough memory for the heap, metaspace, code cache, native overhead and Tomcat threads. Allocating less than 2.5 GiB of memory will cause OOMKilled events under any GC pressure. If you need a smaller footprint (for example, an evaluation deployment on a constrained host), override JAVA_TOOL_OPTIONS to set a smaller heap:

Networking and ports

Tower binds to 0.0.0.0 on BACKEND_PORT by default. There is no internal admin port — /actuator/health is exposed on the same port and is the only Actuator endpoint reachable without authentication.

Filesystem

A read-only root filesystem is supported provided /tmp is mounted as a writable tmpfs or emptyDir (Kubernetes). In OpenShift, the default restricted-v2 SCC permits this configuration without modification.

Configuration surface (environment variables)

Variables are grouped by concern. All variables are optional unless marked required; defaults are shown for reference.

Server

Database

See chapter 2 for full details.

OIDC / Security

See chapter 1 for provider-specific details.

Resilience (Feign / circuit breaker / retry)

Tunes the outbound HTTP client (Feign) and the resilience4j circuit breaker and retry policy applied to all Tower → Sidecar / Data Index / Decision Control calls. The defaults below are sensible for most deployments — adjust only when an upstream service has known latency characteristics.

Sidecar and Data Index

When deploying both Tower and Sidecar in the same Kubernetes cluster, set these to the in-cluster Service hostname (e.g. http://decision-control-tower-sidecar.<namespace>.svc.cluster.local) — never the public Cloudflare / load-balancer hostname.

Environments seed

The file format and behavior are documented in chapter 4 (sidecar / static-config section).

Email Notification

Frontend

Health probe loop (Tower → Decision Control instances)

Task list

Logging

Secrets and credential management

The image accepts secret values via plain environment variables. Never embed secrets in the container image, in command-line arguments visible to other processes, or in version-controlled configuration. Use the secret-management primitive of your platform:
  • Container / Podman: pass secrets via --env-file <file> where the file is restricted (chmod 600) and stored under a directory not backed up to version control. For longer-running deployments use Container secrets or Podman’s --secret flag.
  • Kubernetes: use a Secret and reference it via envFrom or per-key valueFrom.secretKeyRef. Secret encryption at rest must be enabled on the cluster (--encryption-provider-config). If your platform integrates an external secret store (HashiCorp Vault, AWS Secrets Manager, Azure Key Vault), surface the values as environment variables — Tower itself only consumes environment variables.
  • OpenShift: use a Secret with restrictedVolume or the OpenShift Vault integration. Inherit the tenant’s encryption-at-rest policy.
Secrets that must be provided in production:

Worked examples

Production-shaped: PostgreSQL + Entra + sidecar

Where /etc/tower/tower.env (mode 0600, owned by the deployment user) contains:

Custom port

If 8080 conflicts with another service on the host, remap consistently:

Sidecar (required companion container)

The Sidecar is a separate container image (decision-control-tower-sidecar) that Tower calls for process state, data index queries, jobs, and the SVG audit-trail rendering. A Tower deployment without a matching Sidecar will start and accept logins, but workflow-driven endpoints will silently no-op — deploy both together. The Sidecar needs its own PostgreSQL database (distinct from Tower’s), the URL of the Tower instance it should call back into, and the shared callback secret that matches Tower’s ALETYX_OIDC_CALLBACK_SECRET:
Where /etc/tower/sidecar.env (mode 0600) contains:
In the Tower tower.env shown earlier, point ALETYX_DC_TOWER_SIDECAR_URL and ALETYX_DC_TOWER_DATA_INDEX_URL at this Sidecar — http://sidecar.internal:8181 for the host-port mapping above, or the in-cluster Service hostname on Kubernetes (see chapter 4 under “Sidecar component (required)”).

Health and verification

The container exposes a single unauthenticated health endpoint. Both Kubernetes liveness and readiness probes target it:
A healthy response is HTTP 200 with body:
If the backend cannot reach the database, the overall status becomes DOWN and the endpoint returns a non-200 status. Diagnose by checking the logs for a Hibernate / Hikari connection error. The OIDC bootstrap endpoint:
returns the frontend’s expected OIDC client configuration. Useful for verifying that the OIDC environment variables were read correctly without needing to exercise a full login flow.

Operational considerations

  • The image runs as a non-root user. Volume mounts must be readable by the user defined in the base image.
  • imagePullPolicy (Kubernetes / OpenShift): use IfNotPresent for tagged immutable releases (e.g. a specific version such as 1.0.0) and Always only for floating tags (:latest, :main, :stage, :dev).
  • Image pinning by digest is recommended for production:
    Extract the digest of a running pod with:
  • Graceful shutdown: Tower honors SIGTERM and finishes in-flight requests before exiting. Default Tomcat shutdown timeout is 30 seconds; allow at least that on container stop policies.
  • Cold-start time: 6–10 seconds on a 2-core / 2 GiB allocation. The first authenticated request after startup may take longer because the JVM lazily warms up the OIDC HTTP client and the database connection pool. This is expected; subsequent requests return promptly.