Skip to content

Kubernetes

Deploy Team Server with its OCI Helm chart. Use the chart-managed PostgreSQL option only for evaluation; use a managed or otherwise production-operated PostgreSQL service for production.

Prerequisites

  • Kubernetes 1.25 or later
  • Helm 3.12 or later
  • kubectl access that can create namespaces, Deployments, Services, ConfigMaps, Secrets, and RBAC resources
  • A default StorageClass for the evaluation database
  • A Team Server license
  • An OIDC application and public HTTPS hostname for production

The examples use Team Server image 1.0.3 and Helm chart 0.1.0. Replace both only after reviewing the release you intend to deploy.

Prepare the Namespace and Configuration

kubectl create namespace endura-team-server

Save the production configuration example as production.yaml. Add the discovery jobs and OIDC providers you use.

For an evaluation deployment with built-in authentication, remove settings.oidc so the file does not reference unset OIDC variables.

Create the ConfigMap:

kubectl create configmap endura-team-server-config \
  --namespace endura-team-server \
  --from-file=production.yaml

If a private integration endpoint uses an internal CA, include the CA in the same ConfigMap and reference it 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=/path/to/corporate-ca.pem

Method 1: Evaluation Deployment

Generate two different JWT secrets:

openssl rand -base64 48
openssl rand -base64 48

Create the application Secret. Use a test-only built-in username and password:

kubectl create secret generic endura-team-server-secrets \
  --namespace endura-team-server \
  --from-literal=ENDURA_LICENSE_KEY='<license_key>' \
  --from-literal=JWT_SENSOR_SECRET='<first_base64_secret>' \
  --from-literal=JWT_USER_SECRET='<second_base64_secret>' \
  --from-literal=SERVER_URL='http://localhost:5150' \
  --from-literal=TENANT_NAME='Evaluation' \
  --from-literal=ENDURA_AUTH_USERNAME='developer' \
  --from-literal=ENDURA_AUTH_PASSWORD='<test_password>'

Create values-evaluation.yaml:

image:
  tag: "1.0.3"

service:
  type: ClusterIP

postgresql:
  enabled: true
  auth:
    postgresPassword: "<database_admin_password>"
    username: endura
    password: "<database_application_password>"
    database: endura
  primary:
    persistence:
      enabled: true
      size: 8Gi

Protect this file because it contains database credentials, then install:

chmod 600 values-evaluation.yaml
helm install endura-team-server \
  oci://ghcr.io/endurasecurity/helm/endura-team-server \
  --version 0.1.0 \
  --namespace endura-team-server \
  --values values-evaluation.yaml

Access the service through a local port forward:

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

Open http://localhost:5150 and sign in with the evaluation account.

Method 2: Production Deployment

Prepare a PostgreSQL database and a role that can run migrations and read and write Team Server data. Put its URI and the OIDC credentials in the application Secret:

kubectl create secret generic endura-team-server-secrets \
  --namespace endura-team-server \
  --from-literal=DATABASE_URL='postgresql://endura:<password>@postgres.example.com:5432/endura' \
  --from-literal=ENDURA_LICENSE_KEY='<license_key>' \
  --from-literal=JWT_SENSOR_SECRET='<first_base64_secret>' \
  --from-literal=JWT_USER_SECRET='<second_base64_secret>' \
  --from-literal=SERVER_URL='https://team-server.example.com' \
  --from-literal=TENANT_NAME='Example Organization' \
  --from-literal=GOOGLE_OIDC_CLIENT_ID='<client_id>' \
  --from-literal=GOOGLE_OIDC_CLIENT_SECRET='<client_secret>'

Create a TLS Secret using your issued certificate:

kubectl create secret tls endura-team-server-tls \
  --namespace endura-team-server \
  --cert=/path/to/tls.crt \
  --key=/path/to/tls.key

Create values-production.yaml:

replicaCount: 2

image:
  tag: "1.0.3"

service:
  type: ClusterIP

ingress:
  enabled: true
  className: nginx
  annotations:
    nginx.ingress.kubernetes.io/configuration-snippet: |
      proxy_cookie_flags ~ secure httponly samesite=lax;
  hosts:
    - host: team-server.example.com
      paths:
        - path: /
          pathType: Prefix
  tls:
    - secretName: endura-team-server-tls
      hosts:
        - team-server.example.com

resources:
  limits:
    memory: 4Gi
  requests:
    cpu: 500m
    memory: 2Gi

postgresql:
  enabled: false

The ingress terminates TLS, so production.yaml should omit settings.tls. Keep SERVER_URL on HTTPS. The ingress annotation shown above sets secure session-cookie attributes; use the equivalent for a different ingress controller.

Install the chart:

helm install endura-team-server \
  oci://ghcr.io/endurasecurity/helm/endura-team-server \
  --version 0.1.0 \
  --namespace endura-team-server \
  --values values-production.yaml

Verify the Deployment

helm status endura-team-server --namespace endura-team-server
kubectl rollout status deployment/endura-team-server \
  --namespace endura-team-server
kubectl get pods,service,ingress --namespace endura-team-server
kubectl logs deployment/endura-team-server \
  --namespace endura-team-server

Verify readiness through the public endpoint:

curl -f https://team-server.example.com/_readiness

The response is HTTP 200 with {"ok":true}.

Set Up the First Administrator

The OIDC user must sign in once before its database record exists. Find its ID:

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

Assign the role:

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

Refresh the browser or sign in again.

Update Team Server

Review Runtime Sensor compatibility before a major update. Pin the new chart and image versions explicitly:

CHART_VERSION=0.1.0
IMAGE_VERSION=1.0.3
helm upgrade endura-team-server \
  oci://ghcr.io/endurasecurity/helm/endura-team-server \
  --version "$CHART_VERSION" \
  --namespace endura-team-server \
  --values values-production.yaml \
  --set image.tag="$IMAGE_VERSION"

kubectl rollout status deployment/endura-team-server \
  --namespace endura-team-server

The chart uses maxSurge: 0 and maxUnavailable: 1. With one replica, an update creates a short service gap. With two or more healthy replicas, pods are replaced one at a time.

Update Configuration

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

kubectl rollout restart deployment/endura-team-server \
  --namespace endura-team-server

Update Secrets

Recreate or patch endura-team-server-secrets, then restart the Deployment. Rotating JWT secrets invalidates tokens signed with the old values.

Backup and Restore

For production, use the external database provider’s backup, recovery, and retention controls. Also back up production.yaml, values-production.yaml, and the names and sources of Kubernetes Secrets.

For the evaluation database:

kubectl exec deployment/endura-team-server-postgresql \
  --namespace endura-team-server -- \
  sh -c 'pg_dump -U "$POSTGRES_USER" "$POSTGRES_DB"' > team_server.sql

Restore only into an empty or intentionally replaced evaluation database:

kubectl exec -i deployment/endura-team-server-postgresql \
  --namespace endura-team-server -- \
  sh -c 'psql -U "$POSTGRES_USER" "$POSTGRES_DB"' < team_server.sql

Monitoring

# Current logs
kubectl logs deployment/endura-team-server \
  --namespace endura-team-server --follow

# Previous container instance
kubectl logs deployment/endura-team-server \
  --namespace endura-team-server --previous

# Pod events and readiness
kubectl describe pods \
  --namespace endura-team-server \
  --selector app.kubernetes.io/instance=endura-team-server

The chart annotates pods for Prometheus scraping on port 5150. Confirm the annotations match your Prometheus discovery configuration.

Uninstall

Back up Team Server before uninstalling, especially when using the chart-managed database.

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

Delete configuration and application Secrets only after confirming that you no longer need the deployment:

kubectl delete configmap endura-team-server-config \
  --namespace endura-team-server
kubectl delete secret endura-team-server-secrets \
  --namespace endura-team-server

Namespace Deletion Removes All Namespaced Data

Deleting the namespace removes every remaining Team Server resource in it, including database claims. Confirm the backup and namespace name before running kubectl delete namespace endura-team-server.

Troubleshooting

Pod Is Not Ready

kubectl describe pods \
  --namespace endura-team-server \
  --selector app.kubernetes.io/instance=endura-team-server
kubectl logs deployment/endura-team-server \
  --namespace endura-team-server

Check configuration parsing, required Secrets, JWT validation, license validation, and database connectivity.

Database Is Unreachable

Run a temporary PostgreSQL client using the same network path and credentials as Team Server, or use the database provider’s connectivity test. Confirm NetworkPolicies, DNS, TLS, and port 5432.

Ingress Opens but Login Fails

Confirm that SERVER_URL, server.host, CORS, settings.tenant.base_url, the ingress hostname, and the OIDC callback all use the same public HTTPS origin. The callback must include /api/authentication/{provider}/callback.

Internal CI/CD Integration Is Blocked

Add only the required target CIDRs to settings.http.allowed_networks. Mount internal CA bundles in the configuration ConfigMap and list them under settings.http.ca_certificates.

Rollout Stalls

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

Check image availability, scheduling resources, configuration, Secrets, and readiness errors.