Skip to content

Kubernetes

Deploy Team Server with its OCI Helm chart. The chart creates one Team Server Deployment and one Service, which defaults to ClusterIP. Operate PostgreSQL, application configuration, and credentials separately from the Helm release.

Prerequisites

  • Kubernetes 1.25 or later
  • Helm 3.12 or later and configured kubectl
  • curl and jq for resolving the selected release channel
  • Cluster permissions to create a namespace, Deployment, Service, optional Ingress, ConfigMap, and Secret
  • A reachable PostgreSQL database and a role that can run migrations and read and write Team Server data
  • A Team Server license
  • An OIDC application and public HTTPS hostname for production

The examples select the testing channel and resolve its current Team Server chart version from the channel manifest. See Release Channels before selecting another channel.

Prepare the Namespace and Configuration

Resolve the chart version and create the namespace:

CHANNEL=testing
CHART_VERSION=$(curl -sSf "https://repo.endurasecurity.com/${CHANNEL}/release.json" \
  | jq -er '.products.teamServer.chart.version') || exit $?

kubectl create namespace endura-team-server

Save the production configuration example as production.yaml. Keep the required maintenance jobs and add the discovery jobs and OIDC providers you use.

Create the ConfigMap:

kubectl create configmap endura-team-server-config \
  --namespace endura-team-server \
  --from-file=production.yaml \
  --dry-run=client -o yaml | kubectl apply -f - || exit $?

If a private identity provider or integration endpoint uses an internal CA, use this complete ConfigMap command instead. Include its PEM bundle in the same ConfigMap and reference config/<file-name> from settings.http.ca_certificates:

kubectl create configmap endura-team-server-config \
  --namespace endura-team-server \
  --from-file=production.yaml \
  --from-file=corporate-ca.pem=/secure/path/corporate-ca.pem \
  --dry-run=client -o yaml | kubectl apply -f - || exit $?

Create the Application Secret

Create a protected environment file containing:

  • DATABASE_URL for the independently operated PostgreSQL service
  • ENDURA_CREDENTIAL_ENCRYPTION_KEY
  • ENDURA_LICENSE_KEY
  • Distinct JWT_SENSOR_SECRET and JWT_USER_SECRET values
  • SERVER_URL and TENANT_NAME
  • Every OIDC or built-in authentication credential referenced by production.yaml
install -m 0600 /dev/null team-server.env
${EDITOR:-vi} team-server.env

kubectl create secret generic endura-team-server-secrets \
  --namespace endura-team-server \
  --from-env-file=team-server.env

Generate ENDURA_CREDENTIAL_ENCRYPTION_KEY with openssl rand -base64 32; it must decode to exactly 32 bytes. Generate each JWT secret with openssl rand -base64 48; each decoded JWT value must contain at least 32 bytes, and the JWT values must differ. Keep credential values out of Helm values and shell history. The chart reads this Secret but does not create, update, or delete it.

For a non-production evaluation, the Secret may contain ENDURA_AUTH_USERNAME and ENDURA_AUTH_PASSWORD instead of OIDC credentials. The database still remains outside the Helm release.

Configure Helm Values

The default Service is ClusterIP. For production HTTPS ingress, install an Ingress controller and find its class with kubectl get ingressclass. Create values-production.yaml, replacing your-ingress-class with that installed class:

ingress:
  enabled: true
  className: your-ingress-class
  hosts:
    - host: team-server.example.com
      paths:
        - path: /
          pathType: Prefix
  tls:
    - secretName: endura-team-server-tls
      hosts:
        - team-server.example.com

Create the referenced TLS Secret before installation. The ingress terminates TLS, so production.yaml should omit settings.tls. Keep SERVER_URL on HTTPS. Team Server derives cookie transport security from the HTTPS settings.tenant.base_url; the ingress does not need to rewrite cookie attributes.

The schema rejects unknown values. One replica runs web, workers, and scheduler without leader election; no-surge upgrades stop it before replacement, causing a short interruption.

An empty image.tag uses the chart’s immutable appVersion; image.digest selects image.repository@image.digest and takes precedence over the tag. Set priorityClassName to an existing PriorityClass when Team Server must receive higher scheduling priority.

Install

helm install endura-team-server \
  "oci://ghcr.io/endurasecurity/helm/${CHANNEL}/endura-team-server" \
  --version "$CHART_VERSION" \
  --namespace endura-team-server \
  --values values-production.yaml || exit $?

The chart creates no ServiceAccount or RBAC resources and disables ServiceAccount token mounting because Team Server does not call the Kubernetes API.

Verify the Deployment

kubectl rollout status deployment/endura-team-server \
  --namespace endura-team-server --timeout=300s || exit $?

For a local check, start port-forwarding in one terminal and leave it running:

kubectl port-forward \
  --namespace endura-team-server \
  service/endura-team-server 5150:5150

In a second terminal:

curl -f http://127.0.0.1:5150/_readiness

Expect HTTP 200 and {"ok":true}; for ingress, check public HTTPS. /_readiness removes database-unready Pods from traffic. Startup/liveness use process-only /_health, avoiding restarts solely for database outages.

Inspect logs and events when rollout or readiness fails:

kubectl logs deployment/endura-team-server \
  --namespace endura-team-server
kubectl get events \
  --namespace endura-team-server \
  --sort-by=.lastTimestamp

Set Up the First Administrator

Sign in to create the unscoped Viewer record. Find it by OIDC email or built-in <ENDURA_AUTH_USERNAME>@localhost (for example, developer@localhost), then promote its ID:

kubectl exec deployment/endura-team-server \
  --namespace endura-team-server -- \
  endura task user_get email:admin@example.com

kubectl exec deployment/endura-team-server \
  --namespace endura-team-server -- \
  endura task user_update id:42 role:administrator

Refresh or sign in again to load the new role.

Update Team Server

Before upgrading, add ENDURA_CREDENTIAL_ENCRYPTION_KEY to the Secret and check the plaintext credential blocker; a key alone does not convert old credentials.

Review Runtime Sensor compatibility, then resolve and pin the new chart version:

CHART_VERSION=$(curl -sSf "https://repo.endurasecurity.com/${CHANNEL}/release.json" \
  | jq -er '.products.teamServer.chart.version') || exit $?

helm upgrade endura-team-server \
  "oci://ghcr.io/endurasecurity/helm/${CHANNEL}/endura-team-server" \
  --version "$CHART_VERSION" \
  --namespace endura-team-server \
  --values values-production.yaml || exit $?

kubectl rollout status deployment/endura-team-server \
  --namespace endura-team-server --timeout=300s || exit $?

After editing local production.yaml, reapply the ConfigMap before restarting:

kubectl create configmap endura-team-server-config \
  --namespace endura-team-server \
  --from-file=production.yaml \
  --dry-run=client -o yaml | kubectl apply -f - || exit $?

kubectl rollout restart deployment/endura-team-server \
  --namespace endura-team-server
kubectl rollout status deployment/endura-team-server \
  --namespace endura-team-server --timeout=300s || exit $?

Include every CA file when reapplying the ConfigMap. Apply credential changes to the Secret before restarting.

To rotate the credential-encryption key:

  1. Generate a new value with openssl rand -base64 32.

  2. In the protected team-server.env source file, move the old value to ENDURA_CREDENTIAL_ENCRYPTION_PREVIOUS_KEYS and set ENDURA_CREDENTIAL_ENCRYPTION_KEY to the new value.

  3. Apply the Secret and restart the single-replica Deployment:

    kubectl create secret generic endura-team-server-secrets \
      --namespace endura-team-server \
      --from-env-file=team-server.env \
      --dry-run=client -o yaml | kubectl apply -f - || exit $?
    
    kubectl rollout restart deployment/endura-team-server \
      --namespace endura-team-server
    kubectl rollout status deployment/endura-team-server \
      --namespace endura-team-server
  4. Confirm that the logs report credentials validated and that /_readiness succeeds.

  5. Remove ENDURA_CREDENTIAL_ENCRYPTION_PREVIOUS_KEYS from team-server.env, then repeat the Secret update, restart, and validation in steps 3 and 4.

Keep the previous key until restart succeeds. JWT rotation separately revokes tokens signed with the old secret.

Backup and Uninstall

Use the database provider’s backup, recovery, and retention controls. Also back up production.yaml, the Helm values file, and the sources of all Kubernetes Secrets. A database backup cannot restore encrypted integration credentials without the credential-encryption keyring that was active when the backup was created.

helm uninstall endura-team-server --namespace endura-team-server

The external database, ConfigMap, application Secret, and TLS Secret remain after uninstall. Delete them only after confirming they are no longer needed.

Troubleshooting

Pod Reports CreateContainerConfigError

Confirm that the ConfigMap selected by config.name and the Secret selected by secrets.name exist in the release namespace. Confirm that the ConfigMap contains a production.yaml key.

Pod Reports CrashLoopBackOff

Inspect the Team Server logs for configuration and startup errors. Confirm that the application Secret contains DATABASE_URL, a valid ENDURA_CREDENTIAL_ENCRYPTION_KEY, and every environment value referenced by production.yaml. An unavailable previous key or a credential-authentication failure also prevents startup.

Database Is Unreachable

Confirm DNS, network policy, TLS requirements, credentials, and the PostgreSQL port from the Team Server Pod’s network path. /_readiness reports database dependency failure; inspect Pod restart counts and events for process failures.

Ingress Opens but Login Fails

Confirm that SERVER_URL, server.host, CORS, settings.tenant.base_url, the ingress hostname, and the OIDC callback use the same public HTTPS origin. The callback includes /api/authentication/{provider}/callback. Team Server adds HttpOnly and SameSite=Lax itself and uses the HTTPS tenant URL to add Secure, even when ingress terminates TLS.

Internal Endpoint Is Blocked

Add only the required identity-provider or integration CIDRs to settings.http.allowed_networks. Mount internal CA bundles in the configuration ConfigMap and list their config/<file-name> paths under settings.http.ca_certificates.