Skip to Content
Self-HostingKubernetesInstallation

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-nginx

This 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-nginx

In 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.yaml

For 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-manager

After 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: nginx

Then, run the following command to create the certificate issuer:

kubectl apply -f cert-issuer.yaml

At 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 xltrail

Since 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=xltrail

Confirm 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=xltrail argument 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/db

    This 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.yaml file. 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 of redis://. When using redis:// with self-signed certificates, you will need to add the ssl_cert_reqs=CERT_NONE query 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 ; echo

4.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=true

Set <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 pods

At this point, consider resetting your current context again via:

kubectl config set-context --current --namespace=default

5. 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.yaml

After changing the values, you can install/upgrade the helm chart as follows:

helm upgrade --install xltrail xltrail/xltrail -f values.yaml

Sample 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-tls

With 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.yaml

OIDC 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"
ValueSetting
xltrail.oidcIssuerUrlOIDC_ISSUER_URL
xltrail.oidcClientIdOIDC_CLIENT_ID (required when oidcIssuerUrl is set)
xltrail.oidcClientSecretSecretNameSecret with key OIDC_CLIENT_SECRET
xltrail.oidcScopesOIDC_SCOPES (default openid profile email)
xltrail.oidcAdminClaim / xltrail.oidcAdminValueOIDC_ADMIN_CLAIM / OIDC_ADMIN_VALUE
xltrail.oidcTrustEmailOIDC_TRUST_EMAIL
xltrail.oidcStateTtlOIDC_STATE_TTL
xltrail.logoutRedirectUrlLOGOUT_REDIRECT_URL
xltrail.httpRequestTimeoutHTTP_REQUEST_TIMEOUT
xltrail.extraEnvAny 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.