Coming soon. Depotex is in active development, so features and these docs may still change.

Install with Helm

Install, license, scale, upgrade and remove Depotex on Kubernetes with its Helm chart, step by step

Helm chart: TBD. The address of the published chart repository and the public images will be announced here. Until then, use the chart (deploy/helm/depotex) and the prime-secrets.sh script from the Depotex distribution you received, and images built from it and pushed to your own registry. Ask support if you don't have them.

The chart deploys the Depotex server (a StatefulSet) and web app (a Deployment), with PostgreSQL, Redis and MinIO built in for a quick start, each of which can be switched off for a managed service. It never creates secrets: a script creates them beforehand and writes the installation's keys to a file you keep. This page walks through an installation from an empty namespace to a running, licensed cluster; Docker & Kubernetes is the reference for the images, network policies and monitoring.

1. Requirements

Needed
Kubernetes1.27 or later, with a default StorageClass (or set storageClass per volume)
Toolskubectl and Helm 3 on your workstation, with rights to create Secrets, StatefulSets, Services, Ingresses and NetworkPolicies in the namespace
IngressAn ingress controller (ingress-nginx, Traefik, a cloud load balancer controller…) and a DNS name for Depotex; TLS from cert-manager or your own certificate
ImagesThe server and web app images in a registry your nodes can pull from (backend.image, frontend.image)
LicenseRequested once the installation ID is known (step 4); see Licensing
For a clusterA Pro license or higher, Redis, and shared artifact storage (S3, MinIO, Azure or a ReadWriteMany volume)

Resource requests in the chart are a small starting point (server 250m CPU and 512 MiB, limit 2 GiB; web app 100m and 256 MiB, limit 1 GiB). See Plan an installation before production.

2. 1. Prepare values

Start from one of these. Everything not set keeps the chart's default (the full list is under Values reference below).

Evaluation: built-in PostgreSQL, Redis and MinIO, one server.

# my-values.yaml
publicUrl: https://depotex.example.com
backend:
  image: { repository: registry.example.com/depotex/depotex-backend, tag: "<version>" }
frontend:
  image: { repository: registry.example.com/depotex/depotex-frontend, tag: "<version>" }
ingress:
  className: nginx
  annotations:
    nginx.ingress.kubernetes.io/proxy-body-size: "0"
    nginx.ingress.kubernetes.io/proxy-read-timeout: "3600"
    nginx.ingress.kubernetes.io/proxy-send-timeout: "3600"
    cert-manager.io/cluster-issuer: letsencrypt
  tls: { enabled: true }

Production: managed PostgreSQL, Redis and S3, three servers (shipped as values-external.yaml).

publicUrl: https://depotex.example.com
backend:
  replicas: 3          # cluster mode: Pro license and Redis
  tmpSize: 20Gi        # room to stage backups
  resources:
    requests: { cpu: "2", memory: 4Gi }
    limits: { memory: 8Gi }
  metrics: { enabled: true, serviceMonitor: { enabled: true } }
postgresql: { enabled: false }
redis: { enabled: false }
minio: { enabled: false }
storage:
  backend: s3
  bucket: my-depotex-artifacts
  s3: { region: eu-west-1 }
ingress:
  className: nginx
  annotations:
    nginx.ingress.kubernetes.io/proxy-body-size: "0"
    nginx.ingress.kubernetes.io/proxy-read-timeout: "3600"
    nginx.ingress.kubernetes.io/proxy-send-timeout: "3600"
  tls: { enabled: true, secretName: depotex-tls }
networkPolicy:
  ingressNamespaceSelector:
    kubernetes.io/metadata.name: ingress-nginx

Air-gapped: set global.imageRegistry to your mirror (prepended to every image, the built-in services' too) and global.imagePullSecrets. The server needs no internet access for itself; proxy repositories need their upstreams, which can be another Depotex (see Upstreams). Set networkPolicy.backendInternetEgress: false and list what it may reach in networkPolicy.extraEgress.

3. 2. Create the secrets

kubectl create namespace depotex
deploy/helm/prime-secrets.sh init -n depotex -r depotex \
  --public-url https://depotex.example.com

The script generates every key and password, creates the Secrets <release>-app and <release>-infra in the namespace, and writes depotex-<namespace>-<release>-install-keys.txt, readable only by you. Options:

OptionUse
-n, -rNamespace and Helm release name (the release name is part of every resource name)
--public-urlThe Depotex address, as in publicUrl
--database-url 'postgres://…'A managed database instead of the built-in one; its role must own the database and have CREATEROLE (not superuser)
--redis-url 'rediss://…' / --no-redisA managed Redis, or none (single server only)
--storage-access-key, --storage-secret-keyCredentials for S3, external MinIO or Azure (account name and key). Leave them out for S3 with an IAM role for the service account
--admin-passwordChoose the first administrator's password instead of a generated one
--manifestPrint the Secret manifests instead of applying them: for Sealed Secrets, SOPS or an external secret store
--devAlso the test tools' secret (MailHog, Keycloak, OpenLDAP); never in production
--forceOverwrite existing secrets. Never on an installed instance: a new CONFIG_ENCRYPTION_KEY makes every stored setting unreadable

Keep the keys file outside the cluster, with your database backups. It holds CONFIG_ENCRYPTION_KEY, the setup and recovery keys and the first administrator's password. Without the encryption key the installation can't start and its backups can't be restored.

4. 3. Install

helm install depotex deploy/helm/depotex -n depotex -f my-values.yaml
kubectl -n depotex get pods -w

Pods wait in CreateContainerConfigError until the secrets exist. The server's init step waits for the built-in database. Without a license yet, the server starts in setup mode. helm status depotex -n depotex prints the next steps.

5. 4. License it

deploy/helm/prime-secrets.sh installation-id -n depotex -r depotex
# request a license for that ID, then:
deploy/helm/prime-secrets.sh license license.json -n depotex -r depotex

The installation ID is printed and recorded in the keys file. Send it to Vectored through vectored.dev/#contact or support@vectored.dev (see Get a license); the second command stores the license you receive in the app secret.

The installation ID comes from the server pod's identity, which comes from the release name. Don't rename the release of an installed instance, and keep fullnameOverride unchanged once set.

6. First-boot setup

setup.modeWhat happens
auto (default)Unattended setup as soon as the license is in the app secret; the setup wizard until then
passiveUnattended setup from the values and the secrets (fails until the license is there)
wizardYou open <publicUrl>/setup?token=<SETUP_TOKEN> and use the setup wizard
noneAlready set up: no setup file, and the server no longer receives the setup key

Unattended setup builds the setup file from the chart: the administrator from setup.admin.email and setup.admin.name with ADMIN_PASSWORD from the secret, the database, Redis and storage from the values and secrets.

helm upgrade depotex deploy/helm/depotex -n depotex -f my-values.yaml
kubectl -n depotex logs -f depotex-backend-0

When the log shows the server running in normal mode, sign in at publicUrl as the administrator with the password from the keys file. Then set setup.mode: none in your values and upgrade once more, so the server stops receiving the setup key.

7. 5. Check the installation

  • kubectl -n depotex get pods,pvc,ingress: every pod Running and Ready, every claim Bound.
  • Open Settings → Advanced → System check (Pro): database, Redis, storage, email, live updates and each server's health. A Live updates warning means the ingress isn't sending /api/ws to the server (the chart's ingress does this by default).
  • Open the ? menu → About: the version and which server answered.
  • Push and pull one package through a test repository (see Supported formats).

8. Values reference

ValueDefaultMeaning
publicUrlhttps://depotex.example.comThe address people open: redirects, single sign-on callbacks, email links, CORS, and the ingress host unless ingress.host is set
global.imageRegistryemptyRegistry prepended to every image (air-gapped mirrors)
global.imagePullSecrets[]Existing docker-registry secrets
nameOverride, fullnameOverrideemptyResource names; never change them on an installed instance
secrets.app, secrets.infra, secrets.dev<fullname>-app …Names of the secrets prime-secrets.sh created, when you named them yourself
serviceAccount.create, .name, .annotationstrueThe pods' service account; annotate it for IAM roles (EKS IRSA, GKE Workload Identity)
setup.modeautoauto, passive, wizard or none (above)
setup.admin.email, setup.admin.nameadmin@example.com, AdministratorThe first administrator in unattended setup
setup.initImage.*busybox:1.37Small image for the init steps (wait for the database, copy the setup file)
backend.image.*ghcr.io/openapi-studio/depotex-backend, tag = chart appVersionServer image: registry, repository, tag, digest, pullPolicy
backend.replicas1Servers. More than one is a cluster (Pro, Redis, shared storage). Keep 1 until setup has finished
backend.persistence.*enabled, ReadWriteOnce, 10GiPer-pod data: bootstrap file, search index, this server's log files. Never artifacts
backend.tmpSize2GiThe pod's /tmp, where backups are staged: larger than a compressed dump of your database
backend.trustedProxiesloopback and private rangesProxies whose X-Forwarded-For is trusted (TRUSTED_PROXIES); narrow it to your pod network
backend.logLevelinfodebug, info, warn or error
backend.env{}Any other environment variable, as NAME: value
backend.resourcesrequests 250m / 512Mi, limit 2GiServer resources
backend.nodeSelector, .tolerations, .affinity, .topologySpreadConstraints, .podAnnotationsemptyScheduling
backend.terminationGracePeriodSeconds45Time to finish requests and jobs on shutdown
backend.pdb.*enabled, maxUnavailable: 1PodDisruptionBudget
backend.metrics.enabled, .portfalse, 9090Prometheus metrics on their own port, never through the ingress
backend.metrics.prometheusNamespaceSelector{}Namespace labels allowed to scrape (empty: any namespace, that port only)
backend.metrics.serviceMonitor.*disabled, 30s, 10sServiceMonitor for the Prometheus Operator: enabled, interval, scrapeTimeout, labels
frontend.image.*ghcr.io/openapi-studio/depotex-frontendWeb app image
frontend.appDirapps/depotex/frontendThe app's directory inside the image
frontend.replicas2Web app pods (stateless)
frontend.env{}Web app variables, such as FRONTEND_API_BODY_LIMIT or CSP_ENFORCE
frontend.resources, scheduling, pdbrequests 100m / 256Mi, limit 1GiAs for the server
ingress.enabled, .className, .annotations, .hostenabledThe ingress; the host comes from publicUrl unless set
ingress.tls.enabled, .secretNamefalseTLS at the ingress
ingress.backendPaths[/api/ws]Paths sent straight to the server (live updates need WebSocket upgrades)
networkPolicy.enabledtrueOnly the server reaches PostgreSQL, Redis and MinIO; only the ingress controller reaches the apps
networkPolicy.ingressNamespaceSelector{}Namespace labels of the ingress controller
networkPolicy.backendInternetEgresstrueThe server may reach the internet (upstream registries, single sign-on, email, webhooks). false allows DNS, the built-in services and extraEgress only
networkPolicy.extraEgress[]Extra egress rules for the server
postgresql.enabledtrueBuilt-in PostgreSQL 17 (single instance). false for a managed database (--database-url)
postgresql.persistence.size, .resources, .config20Gi, 250m / 512Mi, max_connections: 200Built-in database sizing and postgresql.conf settings
redis.enabledtrueBuilt-in Redis 7.4 (single instance). false for a managed Redis, or none for a single server
redis.maxmemory, .persistence.size256mb, 2GiBuilt-in Redis sizing
storage.backendminiominio, s3, azure or local
storage.bucketdepotexBucket (S3, MinIO)
storage.minio.endpoint, .useSSLempty (built-in), falseAn external MinIO
storage.s3.regionus-east-1S3 region
storage.azure.containerdepotexAzure container; the access key is the account name, the secret key the account key
storage.local.existingClaim, .pathempty, /app/storageA ReadWriteMany claim mounted on every server pod
minio.enabled, .persistence.sizetrue, 100GiBuilt-in MinIO (single instance) with the bucket and a user limited to it
dev.*disabledTest tools (MailHog, Keycloak, OpenLDAP, a mock Okta) from values-dev.yaml. Never in production

9. Scale to a cluster

  1. Check the license — Pro or higher, with enough servers (Settings → Application → Licensing).
  2. Use Redis and shared storage — Built-in or managed Redis, and S3, MinIO, Azure or a ReadWriteMany volume. Local disk per pod doesn't work for a cluster.
  3. Size the database — Each server opens up to pool_max_conns connections: keep servers × pool_max_conns under PostgreSQL's max_connections. See Database connections.
  4. Scale — Set backend.replicas and helm upgrade. Pods start one after another and join the cluster; Settings → Clustering lists them.

Scaling down: lower backend.replicas. A removed pod shuts down cleanly and shows as left; it drops off the list after a day.

10. Upgrade

  1. Read the release notes — The changelog and security advisories.
  2. Back up — Run a backup in Settings → Backups and check it finished.
  3. Upgrade — Push the new images, set their tags, and helm upgrade depotex deploy/helm/depotex -n depotex -f my-values.yaml.
  4. Watch — kubectl -n depotex rollout status statefulset/depotex-backend. Server pods restart one at a time; database migrations run once, on the first new server, while the others keep serving.
  5. Check — System check, the About dialog's version, and a test download.

See Upgrades & maintenance for what happens during a rolling upgrade and how to roll back.

11. Uninstall

helm uninstall depotex -n depotex
  • Removes the pods, services and ingress. The server pods' PersistentVolumeClaims, the built-in services' volumes and the secrets prime-secrets.sh created are kept: delete them yourself when you mean to throw the installation away (kubectl -n depotex delete pvc,secret -l app.kubernetes.io/instance=depotex, after checking what that selects).
  • Artifacts in external storage (S3, Azure, an external MinIO) are never touched.
  • Re-installing with the same release name and the kept secrets and volumes brings the same installation back.

12. Troubleshooting

SymptomCause and fix
Pods in CreateContainerConfigErrorThe secrets don't exist yet, or have other names: run prime-secrets.sh init with the same -n and -r, or set secrets.app / secrets.infra
Server pod waiting in its init stepThe built-in PostgreSQL isn't ready (check its pod and PVC), or a managed database isn't reachable through the network policy
Pod PendingA PersistentVolumeClaim can't be bound: no default StorageClass, or the size isn't available
Setup doesn't start on its ownsetup.mode: auto waits for the license in the app secret: run prime-secrets.sh license …, then helm upgrade
413 on large uploadsThe ingress limits request bodies: nginx.ingress.kubernetes.io/proxy-body-size: "0" (or your controller's equivalent)
Large downloads or uploads cut offRaise the ingress read and send timeouts (3600 s in the examples)
Alerts arrive late; system check warns about live updates/api/ws doesn't reach the server with WebSocket upgrades: keep it in ingress.backendPaths, and allow upgrades on any proxy in front
Everyone shares one rate limit or address in the audit logbackend.trustedProxies doesn't cover the web app's and ingress controller's pod addresses
Every page answers 402 license_requiredThe license expired or was issued for another installation ID: see Licensing
The server starts in recovery modeSomething it needs is wrong (database, encryption key, storage, Redis, license): open the recovery link from its log, see Recovery Mode

Still stuck? Collect the details in Support and contact us.