Installation (Kubernetes)
Last updated: October 26, 2026
1. Requirements
xltrail can be installed on a Kubernetes cluster via Helm chart. You’ll need the following prerequisites:
- Kubernetes cluster (tested versions: 1.18.14 - 1.29.9)
- kubectl , the Kubernetes CLI
- Helm , the Kubernetes package manager (v3)
You can start with a single node, but it is recommended to use one with at least 4 CPU cores and 4GB RAM, such as Standard_D4s_v3 on Azure.
2. Install ingress-nginx
The ingress is required to make xltrail accessible from outside your cluster. xltrail expects the community-managed ingress-nginx . Install it by running the following:
kubectl create namespace ingress-nginx
helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
helm repo update
helm upgrade --install ingress-nginx --set controller.allowSnippetAnnotations=true ingress-nginx/ingress-nginx --namespace ingress-nginxThis will provision a managed Load Balancer and might take a minute or two. Once ready, you can get the external IP of your Load Balancer via:
kubectl get services/ingress-nginx-controller -n ingress-nginxIn your DNS configuration, create an A record (e.g., xltrail.mycompany.com) that points to this IP. Depending on your provider, you may also want to configure a static IP that resolves to the Load Balancer’s IP and point your A record to this IP instead. For Azure, see: https://docs.microsoft.com/en-us/azure/aks/static-ip .
Internal LoadBalancer
If you want to expose your ingress to an internal network, you will have to configure your Helm chart accordingly. For example, on Azure, you need to create a file internal-ingress.yaml with the following content:
# internal-ingress.yaml
controller:
service:
loadBalancerIP: 10.240.0.42 # TODO: Change to your internal IP
annotations:
service.beta.kubernetes.io/azure-load-balancer-internal: "true"
service.beta.kubernetes.io/azure-load-balancer-internal-subnet: "your-subnet"Then run the following installation command instead:
helm upgrade --install ingress-nginx --set controller.allowSnippetAnnotations=true ingress-nginx/ingress-nginx --namespace ingress-nginx -f internal-ingress.yamlFor more details, see: https://docs.microsoft.com/en-us/azure/aks/ingress-internal-ip
3. Install cert-manager
This step is only required if you want to secure access to xltrail via TLS certificates that need to be renewed automatically on a regular basis, like for example those from Let’s Encrypt. If your certificates are delivered by an internal team, we’ll see in the next section how you can upload them as secrets to your cluster.
If you want to use TLS certs that can be renewed by cert-manager, install it like so, see also the cert-manager docs :
kubectl create namespace cert-manager
helm repo add jetstack https://charts.jetstack.io
helm repo update
helm upgrade --install cert-manager jetstack/cert-manager --set crds.enabled=true --namespace cert-managerAfter installing cert-manager, you’ll need to create a certificate issuer. Create a file called cert-issuer.yaml with the following content (make sure to fill in your email address).
This example shows how to configure a certificate of type ACME with Let’s Encrypt as the certificate authority, but you can use a different configuration according to your needs by following the cert-manager’s docs under Configuration .
# cert-issuer.yaml
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: xltrail-cert-issuer
namespace: cert-manager
spec:
acme:
# The ACME server URL
server: https://acme-v02.api.letsencrypt.org/directory
# Email address used for ACME registration
email: YOUR@EMAIL
# Name of a secret used to store the ACME account private key
privateKeySecretRef:
name: xltrail-acme-private-key
# Enable the HTTP-01 challenge provider
solvers:
- http01:
ingress:
class: nginxThen, run the following command to create the certificate issuer:
kubectl apply -f cert-issuer.yamlAt this point you haven’t issued any certificates yet, they will be issued when you install xltrail with the option --set ingress.tls.enabled=true, as we’ll see below.
4. Install xltrail
4.1 Namespace
Start by creating a namespace for xltrail. We’ll be using xltrail throughout these instructions, but you may want to call it differently, e.g., xltrail-prod etc.
kubectl create namespace xltrailSince the remaining commands will have to be run in this namespace, the easiest way is to set your current context to this namespace:
kubectl config set-context --current --namespace=xltrailConfirm that the namespace has been correctly set:
kubectl config view --minify | grep namespace:Instead of changing the current context, you could also add the
--namespace=xltrailargument to the commands under 4.2 and 4.3.
4.2 Secrets
Run the following commands to create the required secrets.
-
Docker Registry (make sure to replace both
<USERNAME>and<PASSWORD>with the actual values provided by email)kubectl create secret docker-registry xltrail-registry-credentials --docker-server=registry.gitlab.com --docker-username=<USERNAME> --docker-password=<PASSWORD> -
Secret Key
kubectl create secret generic xltrail-secret-key --from-literal=SECRET_KEY=$(head -c 512 /dev/urandom | LC_ALL=C tr -cd 'a-zA-Z0-9' | head -c 64) -
Postgres
kubectl create secret generic xltrail-postgresql-password --from-literal=POSTGRES_PASSWORD=$(head -c 512 /dev/urandom | LC_ALL=C tr -cd 'a-zA-Z0-9' | head -c 64)In case you’re using an externally hosted PostgreSQL instance, provide the password instead of generating a random one.
-
Minio
kubectl create secret generic xltrail-minio-secret-key --from-literal=MINIO_SECRET_KEY=$(head -c 512 /dev/urandom | LC_ALL=C tr -cd 'a-zA-Z0-9' | head -c 64) -
Redis (when using your own Redis instance)
kubectl create secret generic xltrail-redis-url --from-literal=REDIS_URL=redis://:password@host:port/dbThis is new in version 4.1.9. If you are using an external Redis instance, you can provide the connection string here. The connection string should be in the format
redis://:password@host:port/db.You also need to add the redis.urlSecretName property to your
values.yamlfile. This must point to the secret you created above.... redis: urlSecretName: xltrail-redis-url ...You can also connect over a TLS/SSL encrypted Redis connection (Redis Secure) by using the
rediss://scheme instead ofredis://. When using redis:// with self-signed certificates, you will need to add thessl_cert_reqs=CERT_NONEquery parameter to your redis url:rediss://<...>/<db>?ssl_cert_reqs=CERT_NONE** NOTE: Only Redis non-clustered mode is supported. **
-
TLS certificates (optional)
Only upload TLS certificates that you manage manually. If you use cert-manager or want to access xltrail via http rather than https, this is not required. See also: https://kubernetes.github.io/ingress-nginx/user-guide/tls/kubectl create secret tls xltrail-tls --key <YOUR-INGRESS-TLS.key> --cert <YOUR-INGRESS-TLS.crt> -
OIDC client secret
The client secret of the client you registered for xltrail at your identity provider, see Authentication (OIDC).kubectl create secret generic xltrail-oidc-client-secret --from-literal=OIDC_CLIENT_SECRET=<CLIENT-SECRET> -
SMTP Password (optional)
If you intend to use an SMTP server to send email notifications, set the SMTP Password here.kubectl create secret generic xltrail-smtp-password --from-literal=SMTP_PASSWORD=<PASSWORD>
You should back up the generated secrets, so you can move them to a new cluster if you have to. For example, if you want to retrieve the password of the Postgres user, you could do:
kubectl get secret xltrail-postgresql-password -ojsonpath='{.data.POSTGRES_PASSWORD}' | base64 --decode ; echo4.3 xltrail application
With nginx-ingress and cert-manager installed and the secrets created, you can now install xltrail with the following commands:
helm repo add xltrail https://xltrail.com/charts
helm repo update
helm show values xltrail/xltrail --version 5.1.0 > values.yaml
# Set the OIDC values in values.yaml, see "OIDC settings" below
helm upgrade --install xltrail xltrail/xltrail --version 5.1.0 -f values.yaml --set ingress.host=<HOST> --set ingress.tls.enabled=trueSet <HOST> to the domain you configured under point 1, e.g., xltrail.mycompany.com. Without the OIDC settings, xltrail starts but nobody can sign in. If you skipped point 2 (cert-manager), you will need to disable TLS via --set ingress.tls.enabled=false. For advanced configuration, see “6. Advanced Configuration via values.yaml” below.
Note that the installation will take a couple of minutes. Confirm that all pods show their status as Running by running the following command:
kubectl get podsAt this point, consider resetting your current context again via:
kubectl config set-context --current --namespace=default5. Sign in
With all pods running, type your domain name (e.g., xltrail.mycompany.com) into a web browser and sign in through your identity provider. There is no built-in admin user.
- Users who should be administrators need the admin claim at your IdP (
oidcAdminClaim/oidcAdminValue), see Authentication (OIDC). - There is no invitation step: assign users to the xltrail application at your IdP; their account is created the first time they sign in, as long as a seat is free.
- Once you are signed in, you can add new projects and/or Git integrations, see the Git Projects guide.
6. Advanced Configuration via values.yaml
For a more detailed configuration of your installation, instead of using --set statements as we did above, you can adjust the configuration via the Helm chart’s values.yaml file. Start by exporting the file like this:
helm show values xltrail/xltrail > values.yamlAfter changing the values, you can install/upgrade the helm chart as follows:
helm upgrade --install xltrail xltrail/xltrail -f values.yamlSample values.yaml
The values.yaml below is a starting point for chart 5.1.0 (xltrail 4.4.0). Sign-in is OIDC only: the chart has no password, LDAP or admin account settings. Replace the <...> placeholders, create the secrets it refers to (see 4.2) and uncomment the optional blocks you need. Values you leave out fall back to the chart defaults (helm show values xltrail/xltrail --version 5.1.0). The subsections below describe the OIDC, TLS and MinIO settings in more detail.
global:
imagePullPolicy: Always
logLevel: info
# Registry pull secret, used for all xltrail images and MinIO
registryCredentialsSecretName: xltrail-registry-credentials
# If no storageClassName is provided, it uses the default storageClassName of your
# Kubernetes provider as provided by: kubectl get storageclass
storageClassName:
xltrail:
# Secret with key SECRET_KEY
secretKeySecretName: xltrail-secret-key
# baseUrl: /xltrail
# authTokenExpiry: 30d
# OpenID Connect, see https://docs.xltrail.com/self-hosting/authentication
# Register at the identity provider:
# redirect URI: https://<host><baseUrl>/login/oidc/callback
oidcIssuerUrl: https://login.microsoftonline.com/<tenant-id>/v2.0
oidcClientId: <application-client-id>
# Secret with key OIDC_CLIENT_SECRET
oidcClientSecretSecretName: xltrail-oidc-client-secret
oidcScopes: openid profile email
oidcAdminClaim: roles
oidcAdminValue: xltrail-admin
# Link existing users by email even when the IdP does not mark it verified
# oidcTrustEmail: "false"
# Seconds a sign-in may take before it has to be restarted
# oidcStateTtl: "600"
# Where /logout/done redirects (a path is relative to baseUrl); unset shows a
# signed-out page
# logoutRedirectUrl: https://intranet.example.com
# Timeout in seconds for outgoing HTTP requests (IdP, Git providers)
# httpRequestTimeout: "10"
# If your identity provider or Git provider uses certificates from an internal CA,
# upload the CA certificates like this:
# kubectl create secret generic xltrail-ca-certificates --from-file=<cert1>.crt --from-file=<cert2>.crt
# Note that the extension must be ".crt". Then uncomment the next line:
# caCertificatesSecretName: xltrail-ca-certificates
# This is only relevant if you use the Git integration. If your Git repos sync
# correctly when disabled (0), disable it for increased security, otherwise
# leave it at 1. Sometimes 1 is required, e.g. if you're using a self-signed
# certificate to serve Git repositories over HTTPS
# gitSslNoVerify: 1
# HTTPS_ENABLED follows ingress.tls.enabled. Set httpsEnabled: "1" when TLS
# terminates in front of the ingress (e.g. at a load balancer) so xltrail
# still builds https:// URLs
# httpsEnabled: "1"
# SMTP settings (smtpPassword is the name of a secret with key SMTP_PASSWORD)
# smtpHost: smtp.mycompany.com
# smtpPort: 587
# smtpSenderEmail: xltrail@mycompany.com
# smtpSenderName: xltrail
# smtpUsername: <smtp-username>
# smtpPassword: xltrail-smtp-password
git:
repositories:
# This is where the Git repos are stored
storageSize: 50Gi
storageClassName:
crontab:
# This runs "git gc" to prevent repos from growing
- "15 0 * * * python /server/scripts/git_gc.py"
nginx:
imageRegistry: registry.gitlab.com
imageRepository: xltrail/xltrail/nginx
server:
imageRegistry: registry.gitlab.com
imageRepository: xltrail/xltrail/server
redis:
imageRegistry: registry.gitlab.com
imageRepository: xltrail/xltrail/redis
# External Redis: secret with key REDIS_URL, see 4.2. When set, the
# bundled Redis is not deployed
# urlSecretName: xltrail-redis-url
minio:
# MinIO is an object storage used for diff caching and inter-container communication
# Mirror of docker.io/minio/minio, which no longer exists. Pulled with
# global.registryCredentialsSecretName
imageRegistry: registry.gitlab.com
imageRepository: xltrail/xltrail/minio
imageTag: RELEASE.2023-10-16T04-13-43Z
storageSize: 10Gi
storageClassName:
accessKey: xltrail
# Secret with key MINIO_SECRET_KEY
secretKeySecretName: xltrail-minio-secret-key
minioBrowser: "off"
postgresql:
external: false
# Secret with key POSTGRES_PASSWORD
passwordSecretName: xltrail-postgresql-password
imageRegistry: docker.io
imageRepository: postgres
imageTag: 14.1-alpine
# The following 2 lines are only used with an internal database via "external: false"
storageSize: 10Gi
storageClassName:
# External PostgreSQL: set "external: true" and uncomment the following lines
# (same indentation as everything else under postgresql)
# Supported sslMode: disable or require
# host: postgres.mycompany.com
# port: 5432
# sslMode: require
# database: xltrail
# username: xltrail
pgbouncer:
# Optional. When enabled, a PgBouncer client-side connection pooler is used.
enabled: false
ingress:
# Ingress rules, requires an existing installation of ingress-nginx
enabled: true
# Ingress class of the installed controller
className: nginx
host: xltrail.mycompany.com
annotations:
cert-manager.io/cluster-issuer: xltrail-cert-issuer
tls:
# TLS requires cert-manager with a certificate issuer that matches the above
# annotation (see 3.), or a TLS secret you created yourself (see 4.2).
# Without cert-manager or a TLS secret, set enabled: false
enabled: true
secret: xltrail-tlsWith ingress.host and ingress.tls.enabled set in the file, the --set options from 4.3 are no longer needed:
helm upgrade --install xltrail xltrail/xltrail --version 5.1.0 -f values.yamlOIDC settings
Since version 4.4.0 (chart 5.1.0), users sign in through your OpenID Connect identity provider only. Register xltrail at your IdP with the redirect URI https://<HOST>/login/oidc/callback as described in Authentication (OIDC), create the xltrail-oidc-client-secret secret (see 4.2) and set the following values under xltrail in your values.yaml:
xltrail:
oidcIssuerUrl: https://login.microsoftonline.com/<tenant-id>/v2.0
oidcClientId: <application-client-id>
# Secret with key OIDC_CLIENT_SECRET
oidcClientSecretSecretName: xltrail-oidc-client-secret
oidcScopes: openid profile email
oidcAdminClaim: roles
oidcAdminValue: xltrail-admin
# Optional:
# oidcTrustEmail: "false"
# oidcStateTtl: "600"
# logoutRedirectUrl: https://intranet.example.com
# httpRequestTimeout: "10"| Value | Setting |
|---|---|
xltrail.oidcIssuerUrl | OIDC_ISSUER_URL |
xltrail.oidcClientId | OIDC_CLIENT_ID (required when oidcIssuerUrl is set) |
xltrail.oidcClientSecretSecretName | Secret with key OIDC_CLIENT_SECRET |
xltrail.oidcScopes | OIDC_SCOPES (default openid profile email) |
xltrail.oidcAdminClaim / xltrail.oidcAdminValue | OIDC_ADMIN_CLAIM / OIDC_ADMIN_VALUE |
xltrail.oidcTrustEmail | OIDC_TRUST_EMAIL |
xltrail.oidcStateTtl | OIDC_STATE_TTL |
xltrail.logoutRedirectUrl | LOGOUT_REDIRECT_URL |
xltrail.httpRequestTimeout | HTTP_REQUEST_TIMEOUT |
xltrail.extraEnv | Any other app setting, passed to the app unchanged |
If your IdP uses a certificate from an internal CA, add the CA via xltrail.caCertificatesSecretName; connections to the IdP use the system trust store, see Authentication (OIDC).
TLS
On Kubernetes, TLS always terminates at the ingress or further upstream: the chart sets TLS_TERMINATED_UPSTREAM itself, and HTTPS_ENABLED follows ingress.tls.enabled. If TLS terminates in front of the ingress (e.g. at a load balancer) while ingress.tls.enabled is false, set xltrail.httpsEnabled: "1" so xltrail still builds https:// URLs, including the OIDC redirect URI.
MinIO image
Since chart 5.1.0, MinIO is pulled from registry.gitlab.com/xltrail/xltrail/minio:RELEASE.2023-10-16T04-13-43Z with the registry pull secret global.registryCredentialsSecretName (default xltrail-registry-credentials), because docker.io/minio/minio no longer exists. Existing installations on charts up to and including 5.0.0 need a one-time manual step, see Upgrading: Helm.