The repository you clone
Galtea gives your team read access to a private Git repository holding the chart references, the values templates and the secret templates for your deployment. You clone it once and run every command from its root, so the versions you deploy and the overrides you filled in stay together and are reviewable.
Two files reach each release, in order: the committed template, then your
local/ override. That
is why most commands below carry two -f flags.
Nothing you fill in is ever committed to a Galtea repository. Keep your clone in your own Git
hosting if you want the overrides under review, which Galtea recommends.
There is one repository per cloud, because the values differ enough that a single set of
templates would be mostly conditionals. The Azure repository is the validated AKS path. On AWS,
the charts and the values are the ones Galtea runs in its own production daily. Galtea shares the
repository matching the target cloud, and confirms it and its timeline, at the start of a
deployment.
What is the same on both clouds
The charts, the release names, the order, and the shape of every command. Each numbered step is onehelm upgrade --install, so a failure is isolated to one component and re-running a step is
safe.
Every command below omits --version. The repository pins one per release, and a version written
into this page would be wrong within weeks.
0. Namespace
1. Registry authentication
Galtea distributes container images and Helm charts from a private registry on AWS Elastic Container Registry, and issues the credentials to pull from it. That is true whichever cloud you deploy on, so an Azure deployment still authenticates against an AWS registry to pull. The credentials are read-only and scoped to your deployment.2. Secrets
Fill in the templates insecrets/ and apply them. They carry the database URL, the object
storage credentials, the model provider keys, the SMTP credentials and the initial administrator
password. Nothing else in the install prompts you for a credential.
3. Image pull credentials
A scheduled job exchanges your registry credentials for a short-lived token and writes it into a Kubernetes secret the other releases use to pull images. It exists because the ECR token expires every 12 hours, so a static pull secret would break the next time a pod is rescheduled.4. Message broker
RabbitMQ, installed through an operator. This is the only step with cluster-scoped resources, because the operator’s custom resource definitions are cluster-wide. Two ways to run it, and the choice is about your change process rather than the result:rabbitmq, because the cluster’s generated
service name is what the workers and the APIs are configured to reach.
The operator generates the broker credentials itself, so after the cluster is up you copy them
into the secret the platform reads:
5. Cache and result store
Redis with Sentinel for failover, plus a proxy the platform connects to. This is an unmodified public chart, so it comes from its maintainer rather than from the Galtea registry:redis. The workers and the APIs are configured to reach
redis-redis-ha-haproxy, which is a name the chart derives from the release name. Rename the
release and every consumer loses the cache with no obvious error.
Stateful, so the storage class applies here too.
6. Model gateway
LiteLLM, the single place that holds your model provider configuration and routes every call. Nothing else in the platform talks to a provider directly, so this is the one release to change when you rotate a provider key. Adding or swapping a model is wider: each worker and API names the models it calls in its own values, so those need updating too. See Changing the model set.local/ file is where the provider endpoint goes, and your own model list if it differs
from the shipped one. That is independent of the cloud you
deploy on: an installation on AWS can serve models from Azure OpenAI and the reverse.
Cloud-specific details covers that separation, and the model residency
question that comes with it.
7. Workers
The processes that run evaluations and generation. They pull the platform image as a chart dependency, so dependencies are built once first:8. API and dashboard
--wait makes Helm wait for the API to be ready and for the self-check in step 11. The long
timeout covers the database migration, which runs first.
If Helm seems stuck, check which Job it is waiting for, in another terminal. The migration Job
runs first, and a long migration is normal: check it with
kubectl get job galtea-service-apis-migration -n galtea. The self-check Job is created only after
the migration completes and the API is ready. From then on, read
kubectl logs -n galtea job/galtea-service-apis-self-check. A passing check logs its verdict
within about 30 seconds. A failing one first retries every 15 seconds for about 3.5 minutes,
logging each failed attempt, and then logs the verdict, even when Helm 4 keeps waiting.
This step runs the database schema migration. It is the only one that changes your database,
which is why the upgrade guidance is to back up before it and to keep the APIs and the workers on
the same version.
This is where your ingress and TLS settings live, so it is the second per-cloud file.
9. Task monitor
Leek, which shows the queue and the history of every task. Recommended, not required: skip it and the platform works, while diagnosing a stuck evaluation becomes a matter of reading logs.10. Verify
Running. Then sign in and run one evaluation end to end, which is the
only check that exercises the API, the broker, a worker and the model gateway together.
11. Self-check
ARunning pod does not prove that the platform can reach its models, its storage or its queues.
The self-check proves it. After every install or upgrade of the APIs, a Kubernetes Job makes one
real call to each dependency of each service. When a critical dependency does not answer, the
release fails. The check is on by default, so the command in step 8 already runs it. Its log is
how you read the result of the install.
It needs an API image that ships the self-check, so ask Galtea which platform version includes
it. With an older image the Job fails, because the script it runs is missing.
Run step 8 with --wait --timeout 35m, as shown there. Helm then waits for the API to be ready
and for the Job. Keep the long timeout: the database migration runs first and can take many
minutes.
Read the result. The log prints one row per dependency, then the verdict:
DETAIL says what to fix. A degraded row, such as the mail relay, is only a warning and never
fails the release.
When the check fails, nothing is rolled back. Helm marks the release failed, but the new
version stays applied, and helm history still shows the previous revision as deployed. Fix
the dependency and run step 8 again.
To turn the self-check off, set api.selfCheckJob.enabled: false in
local/05-galtea-service-apis.yaml. A model name the gateway does not serve is then found only
when a request uses it.
Two cases look like a hang:
- The Job reads the same secrets as the API. If one of them is missing, the Job never starts and
writes no log. Run
kubectl get events -n galteato see which secret is not found. - With Helm 4, a failing Job is reported only when
--timeoutruns out, although the log has the verdict much sooner: a failing check retries every 15 seconds for about 3.5 minutes, logging each failed attempt, and then logs the verdict. Read the log while Helm is still waiting.
api.selfCheckCronJob.enabled: true. The check then runs again every day. To run it on
demand:
What differs between the two clouds
Everything in this table is a values change. No different chart, no different command, no different order.
Managed database and managed object storage are what Galtea recommends on both clouds.
Self-hosted, S3-compatible alternatives work, and move backups, failover and patching to your
team.
The storage class is the difference that touches most releases
Three releases keep data on a disk: the broker, the cache and the task monitor. Each takes a storage class name, and that name is cloud-specific:Pending with the reason in the events
of the volume claim rather than in the pod’s own logs, which is where people look first.
The difference that is easiest to get wrong
The workers select S3 over Blob Storage by whetherAWS_S3_BUCKET_NAME has a value, not by
STORAGE_PROVIDER alone. On Azure the key must be present and empty. Leaving a stale bucket name
there sends the workers to S3 while the API writes to Blob Storage, and uploads then appear to
succeed and are unreadable afterwards.
AWS, in values/05-galtea-service-apis.yaml and values/04-galtea-service-workers.yaml:
AWS_REGION and AWS_S3_BUCKET_NAME when the provider is aws_s3, and
AZURE_STORAGE_ACCOUNT_NAME and AZURE_STORAGE_CONTAINER_NAME when it is azure_blob. It
refuses to start if one is missing, naming the variable, rather than failing later on the first
upload.
Object storage must be reachable from your users’ browsers
On both clouds. File uploads use a signed URL, so the browser sends the file straight to the storage endpoint and never through the API. Two consequences:- The network your users sit on has to resolve and reach that endpoint.
- The storage account or bucket has to allow a cross-origin request from your dashboard’s origin.
When your users cannot reach object storage
Some networks cannot meet the two conditions above: the storage endpoint is not reachable from where users sit, or a security policy forbids direct links to the storage host. For those, turn onSTORAGE_PROXY_TRANSFER_ENABLED. The API then hands out links to itself instead of signed storage
URLs, and streams every upload and download to and from storage. Browsers and the SDK only ever
talk to the API host.
It is off by default, and it should stay off whenever users can reach storage. With it on, every
file byte passes through the API pods, so they use more bandwidth and CPU. The dashboard needs no
change. SDK users need a release that supports these links: an older SDK cannot download a file
through them. The workers are unchanged, because they run inside the cluster and keep reaching
storage directly.
The same applies to the AI endpoints you connect for evaluation. When a test case attaches a file,
the platform sends your endpoint a download link, valid for 15 minutes. With the flag on, that link
points at the API host too, so each connected endpoint must be able to reach the API URL, not only
receive requests from the platform.
In values/05-galtea-service-apis.yaml:
STORAGE_TRANSFER_TOKEN_SECRET: a random value of at least 32 characters that
signs the links, for example the output of openssl rand -hex 32. Add it to the
galtea-api-secrets secret (secrets/06-api-secrets.yaml) and uncomment its reference under
api.envValuesFrom, the same way CREDENTIAL_ENCRYPTION_KEY is read, never as a plain value. The
API refuses to start with the flag on and either API_URL or the secret missing.
The API reads the secret once, when it starts. After you change it, restart the API pods (Reloader
does this for you where it is installed). Once the new pods serve, every link signed with the old
value stops working, so an open page may need a reload.
Security considerations
Every file link the API hands out is a bearer credential: whoever holds it reads or writes that one file without signing in. The platform bounds that, and your deployment has to do its share.- Lifetime. A download link works for at most one hour, and an upload link for at most 15 minutes and a single write: it cannot overwrite the file once stored. Every client asks for a fresh link each time it needs one.
- Revocation. There is no revoking a single link. Rotating
STORAGE_TRANSFER_TOKEN_SECRETrevokes every link at once, so treat it as the incident response step for a leaked link. The rotation only takes effect once the API pods restart: change the secret, then runkubectl rollout restart deployment/<api-deployment>unless Reloader restarts them for you. - Logs. The token travels in the URL’s query string. The API keeps it out of its own traces
and records each transfer in its audit log by file and organization, never by token. Your
ingress, service mesh and any TLS-inspecting proxy usually log the full URL: strip the
tokenquery parameter from those access logs, or limit who can read them. - Transport. Set
API_URLto anhttps://address. Over plain HTTP every link travels in clear text. - Upload size. The ingress in front of the API must accept a request body as large as
STORAGE_UPLOAD_MAX_BYTES. Many ingress controllers default to about 1 MiB and reject larger uploads before they reach the API. - Rate limit.
STORAGE_TRANSFER_RATE_LIMIT_MAX_REQUESTScounts per client IP. When your users reach the API through one corporate NAT or egress proxy, they all share one budget, so raise it to match.
Upgrades and rollback
Same command as the install, with a newer chart version. That is why every step above ishelm upgrade --install rather than helm install.
Before you call it done
- Sign in with the administrator account from your secrets file.
- Run one evaluation end to end and confirm a worker picks it up.
- Upload a file from a browser on your users’ network.
- Restore a database backup, to confirm the backup works rather than that it exists.
- Record the platform version you deployed.